Some checks failed
CI / Backend (go 1.23.x) (pull_request) Successful in 52s
CI / Backend security scanners (pull_request) Failing after 39s
CI / Frontend (node 20) (pull_request) Successful in 2m13s
CI / gitleaks (secret scan) (pull_request) Failing after 8s
e2e-full / e2e-full (pull_request) Has been skipped
Follow-up to PR #8 (JWT revocation + refresh), addressing the two in-scope follow-ups called out in the completion-sequence summary on PR #11: 1. swagger.yaml pre-dated /api/v1/auth/refresh and /api/v1/auth/logout - client generators could not pick them up. 2. Those handlers were covered by unit tests on the WalletAuth layer and by the e2e-full Playwright spec, but had no HTTP-level unit tests - regressions at the mux/handler seam (wrong method, missing walletAuth, unregistered route) were invisible to go test ./backend/api/rest. Changes: backend/api/rest/swagger.yaml: - New POST /api/v1/auth/refresh entry under the Auth tag. Uses bearerAuth, returns the existing WalletAuthResponse on 200, 401 via components/responses/Unauthorized, 503 when the auth storage or the jwt_revocations table from migration 0016 is missing. Description calls out that legacy tokens without a jti cannot be refreshed. - New POST /api/v1/auth/logout entry. Same auth requirement; returns {status: ok} on 200; 401 via Unauthorized; 503 when migration 0016 has not run. Description names the jwt_revocations table explicitly so ops can correlate 503s with the migration. - Both slot in alphabetically between /auth/wallet and /auth/register so the tag block stays ordered. backend/api/rest/auth_refresh_internal_test.go (new, 8 tests): - TestHandleAuthRefreshRejectsGet - GET returns 405 method_not_allowed. - TestHandleAuthRefreshReturns503WhenWalletAuthUnconfigured - walletAuth nil, POST with a Bearer header returns 503 rather than panicking (guards against a regression where someone calls s.walletAuth.RefreshJWT without the nil-check). - TestHandleAuthLogoutRejectsGet - symmetric 405 on GET. - TestHandleAuthLogoutReturns503WhenWalletAuthUnconfigured - symmetric 503 on nil walletAuth. - TestAuthRefreshRouteRegistered - exercises SetupRoutes and confirms POST /api/v1/auth/refresh and /api/v1/auth/logout are registered (i.e. not 404). Catches regressions where a future refactor drops the mux.HandleFunc entries for either endpoint. - TestAuthRefreshRequiresBearerToken + TestAuthLogoutRequiresBearerToken - sanity-check that a POST with no Authorization header resolves to 401 or 503 (never 200 or 500). - decodeErrorBody helper extracts ErrorDetail from writeError's {"error":{"code":...,"message":...}} envelope, so asserts on body["code"] match the actual wire format (not the looser {"error":"..."} shape). - newServerNoWalletAuth builds a rest.Server with JWT_SECRET set to a 32-byte string of 'a' so NewServer's fail-fast check from PR #3 is happy; nil db pool is fine because the tests do not exercise any DB path. Verification: cd backend && go vet ./... clean cd backend && go test ./api/rest/ pass (17 tests; 7 new) cd backend && go test ./... pass Out of scope: the live credential rotation in the third follow-up bullet requires infra access (database + SSH + deploy pipeline) and belongs to the operator.
1530 lines
40 KiB
YAML
1530 lines
40 KiB
YAML
openapi: 3.0.3
|
|
info:
|
|
title: SolaceScan API
|
|
description: |
|
|
SolaceScan public explorer API for Chain 138 with tiered access control.
|
|
|
|
## Authentication
|
|
|
|
Track 1 endpoints are public and require no authentication.
|
|
Track 2-4 endpoints require JWT authentication via wallet signature.
|
|
|
|
## Rate Limiting
|
|
|
|
- Track 1: 100 requests/minute per IP
|
|
- Track 2-4: Based on user tier and subscription
|
|
|
|
version: 1.0.0
|
|
contact:
|
|
name: API Support
|
|
email: support@d-bis.org
|
|
license:
|
|
name: MIT
|
|
url: https://opensource.org/licenses/MIT
|
|
|
|
servers:
|
|
- url: https://api.d-bis.org
|
|
description: Production server
|
|
- url: http://localhost:8080
|
|
description: Development server
|
|
|
|
tags:
|
|
- name: Health
|
|
description: Health check endpoints
|
|
- name: Auth
|
|
description: Wallet and user-session authentication endpoints
|
|
- name: Access
|
|
description: RPC product catalog, subscriptions, and API key lifecycle
|
|
- name: Blocks
|
|
description: Block-related endpoints
|
|
- name: Transactions
|
|
description: Transaction-related endpoints
|
|
- name: Addresses
|
|
description: Address-related endpoints
|
|
- name: Search
|
|
description: Unified search endpoints
|
|
- name: Track1
|
|
description: Public RPC gateway endpoints (no auth required)
|
|
- name: MissionControl
|
|
description: Public mission-control health, bridge trace, and cached liquidity helpers
|
|
- name: Track2
|
|
description: Indexed explorer endpoints (auth required)
|
|
- name: Track3
|
|
description: Analytics endpoints (Track 3+ required)
|
|
- name: Track4
|
|
description: Operator endpoints (Track 4 + IP whitelist)
|
|
|
|
paths:
|
|
/health:
|
|
get:
|
|
tags:
|
|
- Health
|
|
summary: Health check
|
|
description: Returns the health status of the API
|
|
operationId: getHealth
|
|
responses:
|
|
'200':
|
|
description: Service is healthy
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
status:
|
|
type: string
|
|
example: ok
|
|
timestamp:
|
|
type: string
|
|
format: date-time
|
|
database:
|
|
type: string
|
|
example: connected
|
|
|
|
/api/v1/auth/nonce:
|
|
post:
|
|
tags:
|
|
- Auth
|
|
summary: Generate wallet auth nonce
|
|
description: Creates a nonce challenge for wallet-signature authentication.
|
|
operationId: createWalletAuthNonce
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/WalletNonceRequest'
|
|
responses:
|
|
'200':
|
|
description: Nonce generated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/WalletNonceResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'503':
|
|
description: Wallet auth storage or database not available
|
|
|
|
/api/v1/auth/wallet:
|
|
post:
|
|
tags:
|
|
- Auth
|
|
summary: Authenticate with wallet signature
|
|
description: Exchanges an address, signature, and nonce for a JWT used by wallet-authenticated track endpoints.
|
|
operationId: authenticateWallet
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/WalletAuthRequest'
|
|
responses:
|
|
'200':
|
|
description: Wallet authenticated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/WalletAuthResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'503':
|
|
description: Wallet auth storage or database not available
|
|
|
|
/api/v1/auth/refresh:
|
|
post:
|
|
tags:
|
|
- Auth
|
|
summary: Refresh a wallet JWT
|
|
description: |
|
|
Accepts a still-valid wallet JWT via `Authorization: Bearer <token>`,
|
|
revokes its `jti` server-side, and returns a freshly issued token with
|
|
a new `jti` and a per-track TTL (Track 4 is capped at 60 minutes).
|
|
Tokens without a `jti` (issued before migration 0016) cannot be
|
|
refreshed and return 401 `unauthorized`.
|
|
operationId: refreshWalletJWT
|
|
security:
|
|
- bearerAuth: []
|
|
responses:
|
|
'200':
|
|
description: New token issued; old token revoked
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/WalletAuthResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'503':
|
|
description: Wallet auth storage or jwt_revocations table missing
|
|
|
|
/api/v1/auth/logout:
|
|
post:
|
|
tags:
|
|
- Auth
|
|
summary: Revoke the current wallet JWT
|
|
description: |
|
|
Inserts the bearer token's `jti` into the `jwt_revocations` table
|
|
(migration 0016). Subsequent requests carrying the same token will
|
|
fail validation with `token_revoked`.
|
|
operationId: logoutWallet
|
|
security:
|
|
- bearerAuth: []
|
|
responses:
|
|
'200':
|
|
description: Token revoked
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
status:
|
|
type: string
|
|
example: ok
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'503':
|
|
description: jwt_revocations table missing; run migration 0016_jwt_revocations
|
|
|
|
/api/v1/auth/register:
|
|
post:
|
|
tags:
|
|
- Auth
|
|
summary: Register an explorer access user
|
|
description: "Creates an email/password account for the `/access` console and returns a user session token."
|
|
operationId: registerAccessUser
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UserRegisterRequest'
|
|
responses:
|
|
'200':
|
|
description: User created and session issued
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UserSessionResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'503':
|
|
description: Database not available
|
|
|
|
/api/v1/auth/login:
|
|
post:
|
|
tags:
|
|
- Auth
|
|
summary: Log in to the explorer access console
|
|
description: "Authenticates an email/password user and returns a user session token for `/api/v1/access/*` endpoints."
|
|
operationId: loginAccessUser
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UserLoginRequest'
|
|
responses:
|
|
'200':
|
|
description: Session issued
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UserSessionResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'503':
|
|
description: Database not available
|
|
|
|
/api/v1/access/me:
|
|
get:
|
|
tags:
|
|
- Access
|
|
summary: Get current access-console user
|
|
description: Returns the signed-in user profile and any known product subscriptions.
|
|
operationId: getAccessMe
|
|
security:
|
|
- userSessionAuth: []
|
|
responses:
|
|
'200':
|
|
description: Current user and subscriptions
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AccessMeResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'503':
|
|
description: Database not available
|
|
|
|
/api/v1/access/products:
|
|
get:
|
|
tags:
|
|
- Access
|
|
summary: List available RPC access products
|
|
description: Returns the commercial and operational RPC products currently modeled by the explorer access layer.
|
|
operationId: listAccessProducts
|
|
responses:
|
|
'200':
|
|
description: Product catalog
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AccessProductsResponse'
|
|
|
|
/api/v1/access/subscriptions:
|
|
get:
|
|
tags:
|
|
- Access
|
|
summary: List subscriptions for the signed-in user
|
|
operationId: listAccessSubscriptions
|
|
security:
|
|
- userSessionAuth: []
|
|
responses:
|
|
'200':
|
|
description: Subscription list
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AccessSubscriptionsResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'503':
|
|
description: Database not available
|
|
|
|
/api/v1/access/admin/subscriptions:
|
|
get:
|
|
tags:
|
|
- Access
|
|
summary: List subscriptions for admin review
|
|
description: Returns pending or filtered subscriptions for users whose email is allowlisted in `ACCESS_ADMIN_EMAILS`.
|
|
operationId: listAccessAdminSubscriptions
|
|
security:
|
|
- userSessionAuth: []
|
|
parameters:
|
|
- name: status
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
enum: [pending, active, suspended, revoked]
|
|
responses:
|
|
'200':
|
|
description: Subscription list
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AccessSubscriptionsResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
description: Admin privileges required
|
|
'503':
|
|
description: Database not available
|
|
post:
|
|
tags:
|
|
- Access
|
|
summary: Approve, suspend, or revoke a subscription
|
|
operationId: updateAccessAdminSubscription
|
|
security:
|
|
- userSessionAuth: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AdminSubscriptionActionRequest'
|
|
responses:
|
|
'200':
|
|
description: Subscription updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AccessSubscriptionResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
description: Admin privileges required
|
|
'503':
|
|
description: Database not available
|
|
post:
|
|
tags:
|
|
- Access
|
|
summary: Request or activate product access
|
|
description: |
|
|
Creates or updates a product subscription. Self-service products become `active` immediately.
|
|
Approval-gated products such as Core RPC are created in `pending` state.
|
|
operationId: createAccessSubscription
|
|
security:
|
|
- userSessionAuth: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CreateSubscriptionRequest'
|
|
responses:
|
|
'200':
|
|
description: Subscription saved
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AccessSubscriptionResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'503':
|
|
description: Database not available
|
|
|
|
/api/v1/access/api-keys:
|
|
get:
|
|
tags:
|
|
- Access
|
|
summary: List API keys for the signed-in user
|
|
operationId: listAccessApiKeys
|
|
security:
|
|
- userSessionAuth: []
|
|
responses:
|
|
'200':
|
|
description: API key records
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AccessAPIKeysResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'503':
|
|
description: Database not available
|
|
post:
|
|
tags:
|
|
- Access
|
|
summary: Create an API key
|
|
description: |
|
|
Issues an API key for the chosen tier and product. If the product is approval-gated and not already active
|
|
for the user, this endpoint returns `subscription_required`.
|
|
operationId: createAccessApiKey
|
|
security:
|
|
- userSessionAuth: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CreateAPIKeyRequest'
|
|
responses:
|
|
'200':
|
|
description: API key created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CreateAPIKeyResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
description: Product access is pending approval or inactive
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
example:
|
|
error:
|
|
code: subscription_required
|
|
message: Product access is pending approval or inactive
|
|
'503':
|
|
description: Database not available
|
|
|
|
/api/v1/access/api-keys/{id}:
|
|
post:
|
|
tags:
|
|
- Access
|
|
summary: Revoke an API key
|
|
description: "Revokes the identified API key. `DELETE` is also accepted by the handler, but the current frontend uses `POST`."
|
|
operationId: revokeAccessApiKey
|
|
security:
|
|
- userSessionAuth: []
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
responses:
|
|
'200':
|
|
description: API key revoked
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RevokeAPIKeyResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'503':
|
|
description: Database not available
|
|
delete:
|
|
tags:
|
|
- Access
|
|
summary: Revoke an API key
|
|
description: Alternate HTTP verb for API key revocation.
|
|
operationId: revokeAccessApiKeyDelete
|
|
security:
|
|
- userSessionAuth: []
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
responses:
|
|
'200':
|
|
description: API key revoked
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RevokeAPIKeyResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'503':
|
|
description: Database not available
|
|
|
|
/api/v1/access/usage:
|
|
get:
|
|
tags:
|
|
- Access
|
|
summary: Get usage summary for the signed-in user
|
|
description: Returns aggregated per-product usage derived from issued API keys.
|
|
operationId: getAccessUsage
|
|
security:
|
|
- userSessionAuth: []
|
|
responses:
|
|
'200':
|
|
description: Usage summary
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AccessUsageResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'503':
|
|
description: Database not available
|
|
|
|
/api/v1/access/audit:
|
|
get:
|
|
tags:
|
|
- Access
|
|
summary: Get recent API activity for the signed-in user
|
|
description: Returns recent validated API-key usage log rows for the current user.
|
|
operationId: getAccessAudit
|
|
security:
|
|
- userSessionAuth: []
|
|
parameters:
|
|
- name: limit
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 200
|
|
default: 20
|
|
responses:
|
|
'200':
|
|
description: Audit entries
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AccessAuditResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'503':
|
|
description: Database not available
|
|
|
|
/api/v1/access/admin/audit:
|
|
get:
|
|
tags:
|
|
- Access
|
|
summary: Get recent API activity across users for admin review
|
|
description: Returns recent validated API-key usage log rows for access admins, optionally filtered by product.
|
|
operationId: getAccessAdminAudit
|
|
security:
|
|
- userSessionAuth: []
|
|
parameters:
|
|
- name: limit
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 500
|
|
default: 50
|
|
- name: product
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
responses:
|
|
'200':
|
|
description: Audit entries
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AccessAuditResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'503':
|
|
description: Database not available
|
|
|
|
/api/v1/access/internal/validate-key:
|
|
get:
|
|
tags:
|
|
- Access
|
|
summary: Validate an API key for nginx auth_request or similar edge subrequests
|
|
description: >-
|
|
Requires `X-Access-Internal-Secret` and accepts the presented API key in
|
|
`X-API-Key` or `Authorization: Bearer ...`. Returns `200` or `401` and
|
|
emits validation metadata in response headers.
|
|
operationId: validateAccessApiKeyInternalGet
|
|
parameters:
|
|
- name: X-Access-Internal-Secret
|
|
in: header
|
|
required: true
|
|
schema:
|
|
type: string
|
|
- name: X-API-Key
|
|
in: header
|
|
required: false
|
|
schema:
|
|
type: string
|
|
- name: Authorization
|
|
in: header
|
|
required: false
|
|
schema:
|
|
type: string
|
|
- name: X-Access-Method
|
|
in: header
|
|
required: false
|
|
schema:
|
|
type: string
|
|
- name: X-Access-Request-Count
|
|
in: header
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
responses:
|
|
'200':
|
|
description: Key validated
|
|
headers:
|
|
X-Validated-Product:
|
|
schema:
|
|
type: string
|
|
X-Validated-Tier:
|
|
schema:
|
|
type: string
|
|
X-Validated-Scopes:
|
|
schema:
|
|
type: string
|
|
X-Quota-Remaining:
|
|
schema:
|
|
type: string
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'503':
|
|
description: Database not available
|
|
post:
|
|
tags:
|
|
- Access
|
|
summary: Validate an API key for internal edge enforcement
|
|
description: Requires `X-Access-Internal-Secret` and returns validated key metadata while incrementing usage counters.
|
|
operationId: validateAccessApiKeyInternal
|
|
parameters:
|
|
- name: X-Access-Internal-Secret
|
|
in: header
|
|
required: true
|
|
schema:
|
|
type: string
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/InternalValidateAPIKeyRequest'
|
|
responses:
|
|
'200':
|
|
description: Key validated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/InternalValidateAPIKeyResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'503':
|
|
description: Database not available
|
|
|
|
/api/v1/blocks:
|
|
get:
|
|
tags:
|
|
- Blocks
|
|
summary: List blocks
|
|
description: Returns a paginated list of blocks
|
|
operationId: listBlocks
|
|
parameters:
|
|
- name: limit
|
|
in: query
|
|
description: Number of blocks to return
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 100
|
|
default: 20
|
|
- name: page
|
|
in: query
|
|
description: Page number
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
default: 1
|
|
- name: chain_id
|
|
in: query
|
|
description: Chain ID filter
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
default: 138
|
|
responses:
|
|
'200':
|
|
description: List of blocks
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/BlockListResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'500':
|
|
$ref: '#/components/responses/InternalServerError'
|
|
|
|
/api/v1/blocks/{chain_id}/{number}:
|
|
get:
|
|
tags:
|
|
- Blocks
|
|
summary: Get block by number
|
|
description: Returns block details by chain ID and block number
|
|
operationId: getBlockByNumber
|
|
parameters:
|
|
- name: chain_id
|
|
in: path
|
|
required: true
|
|
description: Chain ID
|
|
schema:
|
|
type: integer
|
|
example: 138
|
|
- name: number
|
|
in: path
|
|
required: true
|
|
description: Block number
|
|
schema:
|
|
type: integer
|
|
example: 1000
|
|
responses:
|
|
'200':
|
|
description: Block details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Block'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'500':
|
|
$ref: '#/components/responses/InternalServerError'
|
|
|
|
/api/v1/transactions:
|
|
get:
|
|
tags:
|
|
- Transactions
|
|
summary: List transactions
|
|
description: Returns a paginated list of transactions
|
|
operationId: listTransactions
|
|
parameters:
|
|
- name: limit
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 20
|
|
- name: page
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 1
|
|
- name: chain_id
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 138
|
|
responses:
|
|
'200':
|
|
description: List of transactions
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/TransactionListResponse'
|
|
|
|
/api/v1/search:
|
|
get:
|
|
tags:
|
|
- Search
|
|
summary: Unified search
|
|
description: |
|
|
Searches for blocks, transactions, or addresses.
|
|
Automatically detects the type based on the query format.
|
|
operationId: search
|
|
parameters:
|
|
- name: q
|
|
in: query
|
|
required: true
|
|
description: Search query (block number, address, or transaction hash)
|
|
schema:
|
|
type: string
|
|
example: "0x1234567890abcdef"
|
|
responses:
|
|
'200':
|
|
description: Search results
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/SearchResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
|
/api/v1/track1/blocks/latest:
|
|
get:
|
|
tags:
|
|
- Track1
|
|
summary: Get latest blocks (Public)
|
|
description: Returns the latest blocks via RPC gateway. No authentication required.
|
|
operationId: getLatestBlocks
|
|
parameters:
|
|
- name: limit
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 10
|
|
maximum: 50
|
|
responses:
|
|
'200':
|
|
description: Latest blocks
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/BlockListResponse'
|
|
|
|
/api/v1/mission-control/stream:
|
|
get:
|
|
tags:
|
|
- MissionControl
|
|
summary: Mission-control SSE stream
|
|
description: |
|
|
Server-Sent Events stream with the same inner `data` payload as `GET /api/v1/track1/bridge/status`.
|
|
Emits one event immediately, then refreshes every 20 seconds. Configure nginx with `proxy_buffering off`.
|
|
operationId: getMissionControlStream
|
|
responses:
|
|
'200':
|
|
description: SSE stream
|
|
content:
|
|
text/event-stream:
|
|
schema:
|
|
type: string
|
|
|
|
/api/v1/mission-control/liquidity/token/{address}/pools:
|
|
get:
|
|
tags:
|
|
- MissionControl
|
|
summary: Cached liquidity proxy
|
|
description: |
|
|
30-second in-memory cached proxy to the token-aggregation pools endpoint for the configured `CHAIN_ID`.
|
|
operationId: getMissionControlLiquidityPools
|
|
parameters:
|
|
- name: address
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^0x[a-fA-F0-9]{40}$'
|
|
responses:
|
|
'200':
|
|
description: Upstream JSON response
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'503':
|
|
description: "`TOKEN_AGGREGATION_BASE_URL` not configured"
|
|
|
|
/api/v1/mission-control/bridge/trace:
|
|
get:
|
|
tags:
|
|
- MissionControl
|
|
summary: Resolve a transaction through Blockscout and label 138-side contracts
|
|
description: |
|
|
Queries Blockscout using `BLOCKSCOUT_INTERNAL_URL` and labels the `from` and `to` addresses using Chain 138 entries from `SMART_CONTRACTS_MASTER_JSON`.
|
|
operationId: getMissionControlBridgeTrace
|
|
parameters:
|
|
- name: tx
|
|
in: query
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^0x[a-fA-F0-9]{64}$'
|
|
responses:
|
|
'200':
|
|
description: Labeled bridge trace
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'502':
|
|
description: Blockscout lookup failed
|
|
|
|
/api/v1/track4/operator/run-script:
|
|
post:
|
|
tags:
|
|
- Track4
|
|
summary: Run an allowlisted operator script
|
|
description: |
|
|
Track 4 endpoint. Requires authenticated wallet, IP allowlisting, `OPERATOR_SCRIPTS_ROOT`, and `OPERATOR_SCRIPT_ALLOWLIST`.
|
|
operationId: runOperatorScript
|
|
security:
|
|
- bearerAuth: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [script]
|
|
properties:
|
|
script:
|
|
type: string
|
|
description: "Path relative to `OPERATOR_SCRIPTS_ROOT`"
|
|
args:
|
|
type: array
|
|
items:
|
|
type: string
|
|
maxItems: 24
|
|
responses:
|
|
'200':
|
|
description: Script execution result
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'503':
|
|
description: Script root or allowlist not configured
|
|
|
|
/api/v1/track2/search:
|
|
get:
|
|
tags:
|
|
- Track2
|
|
summary: Advanced search (Auth Required)
|
|
description: Advanced search with indexed data. Requires Track 2+ authentication.
|
|
operationId: track2Search
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- name: q
|
|
in: query
|
|
required: true
|
|
schema:
|
|
type: string
|
|
responses:
|
|
'200':
|
|
description: Search results
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
|
components:
|
|
securitySchemes:
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
description: JWT token obtained from /api/v1/auth/wallet
|
|
userSessionAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
description: User session token obtained from /api/v1/auth/register or /api/v1/auth/login
|
|
|
|
schemas:
|
|
WalletNonceRequest:
|
|
type: object
|
|
required: [address]
|
|
properties:
|
|
address:
|
|
type: string
|
|
pattern: '^0x[a-fA-F0-9]{40}$'
|
|
|
|
WalletNonceResponse:
|
|
type: object
|
|
properties:
|
|
address:
|
|
type: string
|
|
nonce:
|
|
type: string
|
|
message:
|
|
type: string
|
|
|
|
WalletAuthRequest:
|
|
type: object
|
|
required: [address, signature, nonce]
|
|
properties:
|
|
address:
|
|
type: string
|
|
pattern: '^0x[a-fA-F0-9]{40}$'
|
|
signature:
|
|
type: string
|
|
nonce:
|
|
type: string
|
|
|
|
WalletAuthResponse:
|
|
type: object
|
|
properties:
|
|
token:
|
|
type: string
|
|
expires_at:
|
|
type: string
|
|
format: date-time
|
|
user:
|
|
type: object
|
|
additionalProperties: true
|
|
|
|
User:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
email:
|
|
type: string
|
|
format: email
|
|
username:
|
|
type: string
|
|
is_admin:
|
|
type: boolean
|
|
|
|
UserRegisterRequest:
|
|
type: object
|
|
required: [email, username, password]
|
|
properties:
|
|
email:
|
|
type: string
|
|
format: email
|
|
username:
|
|
type: string
|
|
password:
|
|
type: string
|
|
minLength: 8
|
|
|
|
UserLoginRequest:
|
|
type: object
|
|
required: [email, password]
|
|
properties:
|
|
email:
|
|
type: string
|
|
format: email
|
|
password:
|
|
type: string
|
|
|
|
UserSessionResponse:
|
|
type: object
|
|
properties:
|
|
user:
|
|
$ref: '#/components/schemas/User'
|
|
token:
|
|
type: string
|
|
expires_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
AccessProduct:
|
|
type: object
|
|
properties:
|
|
slug:
|
|
type: string
|
|
name:
|
|
type: string
|
|
provider:
|
|
type: string
|
|
vmid:
|
|
type: integer
|
|
http_url:
|
|
type: string
|
|
ws_url:
|
|
type: string
|
|
default_tier:
|
|
type: string
|
|
requires_approval:
|
|
type: boolean
|
|
billing_model:
|
|
type: string
|
|
description:
|
|
type: string
|
|
use_cases:
|
|
type: array
|
|
items:
|
|
type: string
|
|
management_features:
|
|
type: array
|
|
items:
|
|
type: string
|
|
|
|
AccessProductsResponse:
|
|
type: object
|
|
properties:
|
|
products:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/AccessProduct'
|
|
note:
|
|
type: string
|
|
|
|
AccessAPIKeyRecord:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
name:
|
|
type: string
|
|
tier:
|
|
type: string
|
|
productSlug:
|
|
type: string
|
|
scopes:
|
|
type: array
|
|
items:
|
|
type: string
|
|
monthlyQuota:
|
|
type: integer
|
|
requestsUsed:
|
|
type: integer
|
|
approved:
|
|
type: boolean
|
|
approvedAt:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
rateLimitPerSecond:
|
|
type: integer
|
|
rateLimitPerMinute:
|
|
type: integer
|
|
lastUsedAt:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
expiresAt:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
revoked:
|
|
type: boolean
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
|
|
AccessSubscription:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
productSlug:
|
|
type: string
|
|
tier:
|
|
type: string
|
|
status:
|
|
type: string
|
|
enum: [active, pending, suspended, revoked]
|
|
monthlyQuota:
|
|
type: integer
|
|
requestsUsed:
|
|
type: integer
|
|
requiresApproval:
|
|
type: boolean
|
|
approvedAt:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
approvedBy:
|
|
type: string
|
|
nullable: true
|
|
notes:
|
|
type: string
|
|
nullable: true
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
|
|
AccessUsageSummary:
|
|
type: object
|
|
properties:
|
|
product_slug:
|
|
type: string
|
|
active_keys:
|
|
type: integer
|
|
requests_used:
|
|
type: integer
|
|
monthly_quota:
|
|
type: integer
|
|
|
|
AccessMeResponse:
|
|
type: object
|
|
properties:
|
|
user:
|
|
$ref: '#/components/schemas/User'
|
|
subscriptions:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/AccessSubscription'
|
|
|
|
AccessSubscriptionsResponse:
|
|
type: object
|
|
properties:
|
|
subscriptions:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/AccessSubscription'
|
|
|
|
AccessSubscriptionResponse:
|
|
type: object
|
|
properties:
|
|
subscription:
|
|
$ref: '#/components/schemas/AccessSubscription'
|
|
|
|
AccessAPIKeysResponse:
|
|
type: object
|
|
properties:
|
|
api_keys:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/AccessAPIKeyRecord'
|
|
|
|
CreateSubscriptionRequest:
|
|
type: object
|
|
required: [product_slug]
|
|
properties:
|
|
product_slug:
|
|
type: string
|
|
tier:
|
|
type: string
|
|
|
|
CreateAPIKeyRequest:
|
|
type: object
|
|
required: [name]
|
|
properties:
|
|
name:
|
|
type: string
|
|
tier:
|
|
type: string
|
|
product_slug:
|
|
type: string
|
|
expires_days:
|
|
type: integer
|
|
monthly_quota:
|
|
type: integer
|
|
scopes:
|
|
type: array
|
|
items:
|
|
type: string
|
|
|
|
AdminSubscriptionActionRequest:
|
|
type: object
|
|
required: [subscription_id, status]
|
|
properties:
|
|
subscription_id:
|
|
type: string
|
|
status:
|
|
type: string
|
|
enum: [active, suspended, revoked]
|
|
notes:
|
|
type: string
|
|
|
|
CreateAPIKeyResponse:
|
|
type: object
|
|
properties:
|
|
api_key:
|
|
type: string
|
|
description: Plaintext key is only returned at creation time.
|
|
record:
|
|
$ref: '#/components/schemas/AccessAPIKeyRecord'
|
|
|
|
RevokeAPIKeyResponse:
|
|
type: object
|
|
properties:
|
|
revoked:
|
|
type: boolean
|
|
api_key_id:
|
|
type: string
|
|
|
|
AccessUsageResponse:
|
|
type: object
|
|
properties:
|
|
usage:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/AccessUsageSummary'
|
|
|
|
AccessAuditEntry:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
apiKeyId:
|
|
type: string
|
|
keyName:
|
|
type: string
|
|
productSlug:
|
|
type: string
|
|
methodName:
|
|
type: string
|
|
requestCount:
|
|
type: integer
|
|
lastIp:
|
|
type: string
|
|
nullable: true
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
|
|
AccessAuditResponse:
|
|
type: object
|
|
properties:
|
|
entries:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/AccessAuditEntry'
|
|
|
|
InternalValidatedAPIKey:
|
|
type: object
|
|
properties:
|
|
apiKeyId:
|
|
type: string
|
|
userId:
|
|
type: string
|
|
name:
|
|
type: string
|
|
tier:
|
|
type: string
|
|
productSlug:
|
|
type: string
|
|
scopes:
|
|
type: array
|
|
items:
|
|
type: string
|
|
monthlyQuota:
|
|
type: integer
|
|
requestsUsed:
|
|
type: integer
|
|
rateLimitPerSecond:
|
|
type: integer
|
|
rateLimitPerMinute:
|
|
type: integer
|
|
lastUsedAt:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
expiresAt:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
|
|
InternalValidateAPIKeyRequest:
|
|
type: object
|
|
required: [api_key]
|
|
properties:
|
|
api_key:
|
|
type: string
|
|
method_name:
|
|
type: string
|
|
request_count:
|
|
type: integer
|
|
last_ip:
|
|
type: string
|
|
|
|
InternalValidateAPIKeyResponse:
|
|
type: object
|
|
properties:
|
|
valid:
|
|
type: boolean
|
|
key:
|
|
$ref: '#/components/schemas/InternalValidatedAPIKey'
|
|
|
|
Block:
|
|
type: object
|
|
properties:
|
|
chain_id:
|
|
type: integer
|
|
example: 138
|
|
number:
|
|
type: integer
|
|
example: 1000
|
|
hash:
|
|
type: string
|
|
example: "0x1234567890abcdef"
|
|
parent_hash:
|
|
type: string
|
|
timestamp:
|
|
type: string
|
|
format: date-time
|
|
miner:
|
|
type: string
|
|
transaction_count:
|
|
type: integer
|
|
gas_used:
|
|
type: integer
|
|
gas_limit:
|
|
type: integer
|
|
|
|
Transaction:
|
|
type: object
|
|
properties:
|
|
chain_id:
|
|
type: integer
|
|
hash:
|
|
type: string
|
|
block_number:
|
|
type: integer
|
|
from_address:
|
|
type: string
|
|
to_address:
|
|
type: string
|
|
value:
|
|
type: string
|
|
gas:
|
|
type: integer
|
|
gas_price:
|
|
type: string
|
|
status:
|
|
type: string
|
|
enum: [success, failed]
|
|
|
|
BlockListResponse:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Block'
|
|
pagination:
|
|
$ref: '#/components/schemas/Pagination'
|
|
|
|
TransactionListResponse:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Transaction'
|
|
pagination:
|
|
$ref: '#/components/schemas/Pagination'
|
|
|
|
Pagination:
|
|
type: object
|
|
properties:
|
|
page:
|
|
type: integer
|
|
limit:
|
|
type: integer
|
|
total:
|
|
type: integer
|
|
total_pages:
|
|
type: integer
|
|
|
|
SearchResponse:
|
|
type: object
|
|
properties:
|
|
query:
|
|
type: string
|
|
results:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
type:
|
|
type: string
|
|
enum: [block, transaction, address]
|
|
data:
|
|
type: object
|
|
|
|
Error:
|
|
type: object
|
|
properties:
|
|
error:
|
|
type: object
|
|
properties:
|
|
code:
|
|
type: string
|
|
message:
|
|
type: string
|
|
|
|
responses:
|
|
BadRequest:
|
|
description: Bad request
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
example:
|
|
error:
|
|
code: "bad_request"
|
|
message: "Invalid request parameters"
|
|
|
|
Unauthorized:
|
|
description: Unauthorized - Authentication required
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
example:
|
|
error:
|
|
code: "unauthorized"
|
|
message: "Authentication required"
|
|
|
|
Forbidden:
|
|
description: Forbidden - Insufficient permissions
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
example:
|
|
error:
|
|
code: "forbidden"
|
|
message: "Insufficient permissions. Track 2+ required."
|
|
|
|
NotFound:
|
|
description: Resource not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
example:
|
|
error:
|
|
code: "not_found"
|
|
message: "Resource not found"
|
|
|
|
InternalServerError:
|
|
description: Internal server error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
example:
|
|
error:
|
|
code: "internal_error"
|
|
message: "An internal error occurred"
|