diff --git a/clients/react-query/src/schema.d.ts b/clients/react-query/src/schema.d.ts index b50c0289e..ff648c5d6 100644 --- a/clients/react-query/src/schema.d.ts +++ b/clients/react-query/src/schema.d.ts @@ -448,10 +448,6 @@ export interface paths { }; content: { "application/json": { - /** @enum {boolean} */ - ok: true; - /** @enum {string} */ - network?: "testnet" | "mainnet"; lastIndexedLedger: number | null; latestLedger: number | null; lagLedgers: number | null; @@ -470,6 +466,19 @@ export interface paths { [key: string]: unknown; }; }; + /** @enum {string} */ + status: "healthy" | "degraded"; + dependencies: { + name: string; + /** @enum {string} */ + status: "ok" | "degraded" | "down" | "unknown"; + /** Format: date-time */ + lastCheckedAt: string | null; + /** Format: date-time */ + lastOkAt: string | null; + latencyMs: number | null; + detail: string | null; + }[]; } & { [key: string]: unknown; }; @@ -2419,7 +2428,7 @@ export interface paths { get: { parameters: { query?: { - /** @description The amount being sent, in whole units of the asset. When given and a percentage fee is configured, each fee is computed for it; without it each fee is the floor. Optional because shipped clients predate it. */ + /** @description The amount being sent, in whole units of the asset. When given and a percentage fee is computed for it; without it each fee is the floor. Optional because shipped clients predate it. */ amount?: string | null; }; header?: never; diff --git a/docs/openapi.json b/docs/openapi.json new file mode 100644 index 000000000..66b7ca57b --- /dev/null +++ b/docs/openapi.json @@ -0,0 +1,6281 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Wraith API", + "version": "1.0.0", + "description": "REST API documentation for the Wraith token transfer indexer.", + "license": { + "name": "MIT", + "url": "https://opensource.org/licenses/MIT" + } + }, + "servers": [ + { + "url": "http://127.0.0.1:3000", + "description": "Local development server" + } + ], + "components": { + "schemas": {}, + "parameters": {} + }, + "paths": { + "/healthz": { + "get": { + "summary": "Liveness probe", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean", + "enum": [ + true + ] + }, + "uptime": { + "type": "number" + } + }, + "required": [ + "ok", + "uptime" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/readyz": { + "get": { + "summary": "Readiness probe", + "parameters": [ + { + "schema": { + "type": "string", + "nullable": true, + "enum": [ + "testnet", + "mainnet", + null + ], + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "example": "mainnet" + }, + "required": false, + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "name": "network", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "default": 100, + "description": "Max acceptable ledger lag" + }, + "required": false, + "description": "Max acceptable ledger lag", + "name": "maxLag", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean" + }, + "network": { + "type": "string", + "enum": [ + "testnet", + "mainnet" + ] + }, + "checks": { + "type": "object", + "properties": { + "db": { + "type": "boolean" + }, + "rpc": { + "type": "boolean" + }, + "indexerCaughtUp": { + "type": "boolean" + } + }, + "required": [ + "db", + "rpc", + "indexerCaughtUp" + ] + }, + "networks": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "checks": { + "type": "object", + "properties": { + "db": { + "type": "boolean" + }, + "rpc": { + "type": "boolean" + }, + "indexerCaughtUp": { + "type": "boolean" + } + }, + "required": [ + "db", + "rpc", + "indexerCaughtUp" + ] + }, + "lastIndexedLedger": { + "type": "integer", + "nullable": true + }, + "latestLedger": { + "type": "integer", + "nullable": true + }, + "lagLedgers": { + "type": "integer", + "nullable": true + } + }, + "required": [ + "checks", + "lastIndexedLedger", + "latestLedger", + "lagLedgers" + ] + } + } + }, + "required": [ + "ok", + "checks" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "503": { + "description": "Service Unavailable", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean" + }, + "network": { + "type": "string", + "enum": [ + "testnet", + "mainnet" + ] + }, + "checks": { + "type": "object", + "properties": { + "db": { + "type": "boolean" + }, + "rpc": { + "type": "boolean" + }, + "indexerCaughtUp": { + "type": "boolean" + } + }, + "required": [ + "db", + "rpc", + "indexerCaughtUp" + ] + }, + "networks": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "checks": { + "type": "object", + "properties": { + "db": { + "type": "boolean" + }, + "rpc": { + "type": "boolean" + }, + "indexerCaughtUp": { + "type": "boolean" + } + }, + "required": [ + "db", + "rpc", + "indexerCaughtUp" + ] + }, + "lastIndexedLedger": { + "type": "integer", + "nullable": true + }, + "latestLedger": { + "type": "integer", + "nullable": true + }, + "lagLedgers": { + "type": "integer", + "nullable": true + } + }, + "required": [ + "checks", + "lastIndexedLedger", + "latestLedger", + "lagLedgers" + ] + } + } + }, + "required": [ + "ok", + "checks" + ] + } + } + } + } + } + } + }, + "/ngn/customers/status": { + "get": { + "summary": "Is this customer reference verified?", + "description": "Read-only check against the naira provider. Creating nothing is the point: the only previous way to ask was to provision a customer and read `verified` off the reply, which needed a full set of personal details to answer a yes/no question about a reference the caller already held. Rate-limited, because a reference is derived from a wallet address and an unthrottled answer would be an oracle.", + "parameters": [ + { + "schema": { + "type": "string", + "description": "Our identifier for the person, stable for the life of their install. Derived from the wallet address, never a provider id.", + "example": "veil_a1b2c3d4e5f6" + }, + "required": true, + "description": "Our identifier for the person, stable for the life of their install. Derived from the wallet address, never a provider id.", + "name": "customerRef", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "customerRef": { + "type": "string" + }, + "verified": { + "type": "boolean", + "description": "Whether the provider verifies this reference right now. Read-only: asking does not create a customer and does not spend a verification attempt, which is the reason this route exists." + }, + "status": { + "type": "string" + } + }, + "required": [ + "customerRef", + "verified" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/metrics": { + "get": { + "summary": "Prometheus metrics", + "description": "Indexer and process metrics in Prometheus text exposition format. Served from in-process counters only — no database or RPC call — so it keeps answering while the subsystems it reports on are down.", + "responses": { + "200": { + "description": "Prometheus text exposition format", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/status": { + "get": { + "summary": "Indexer status", + "parameters": [ + { + "schema": { + "type": "string", + "nullable": true, + "enum": [ + "testnet", + "mainnet", + null + ], + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "example": "mainnet" + }, + "required": false, + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "name": "network", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "lastIndexedLedger": { + "type": "integer", + "nullable": true + }, + "latestLedger": { + "type": "integer", + "nullable": true + }, + "lagLedgers": { + "type": "integer", + "nullable": true + }, + "startedAt": { + "type": "string", + "format": "date-time" + }, + "uptimeSeconds": { + "type": "integer" + }, + "totalIndexed": { + "type": "integer" + }, + "networks": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "lastIndexedLedger": { + "type": "integer", + "nullable": true + }, + "latestLedger": { + "type": "integer", + "nullable": true + }, + "lagLedgers": { + "type": "integer", + "nullable": true + }, + "running": { + "type": "boolean" + }, + "reason": { + "type": "string" + } + }, + "required": [ + "lastIndexedLedger", + "latestLedger", + "lagLedgers", + "running" + ], + "additionalProperties": { + "nullable": true + } + } + }, + "status": { + "type": "string", + "enum": [ + "healthy", + "degraded" + ] + }, + "dependencies": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "ok", + "degraded", + "down", + "unknown" + ] + }, + "lastCheckedAt": { + "type": "string", + "nullable": true, + "format": "date-time" + }, + "lastOkAt": { + "type": "string", + "nullable": true, + "format": "date-time" + }, + "latencyMs": { + "type": "integer", + "nullable": true + }, + "detail": { + "type": "string", + "nullable": true + } + }, + "required": [ + "name", + "status", + "lastCheckedAt", + "lastOkAt", + "latencyMs", + "detail" + ] + } + } + }, + "required": [ + "lastIndexedLedger", + "latestLedger", + "lagLedgers", + "status", + "dependencies" + ], + "additionalProperties": { + "nullable": true + } + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/transfers/incoming/{address}": { + "get": { + "summary": "Incoming transfers", + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "required": true, + "name": "address", + "in": "path" + }, + { + "schema": { + "type": "string", + "nullable": true, + "enum": [ + "testnet", + "mainnet", + null + ], + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "example": "mainnet" + }, + "required": false, + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "name": "network", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "Token contract ID to filter by" + }, + "required": false, + "description": "Token contract ID to filter by", + "name": "contractId", + "in": "query" + }, + { + "schema": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "required": false, + "name": "token", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "fromLedger", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "toLedger", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive lower bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive lower bound on ledgerClosedAt", + "name": "fromDate", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive upper bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive upper bound on ledgerClosedAt", + "name": "toDate", + "in": "query" + }, + { + "schema": { + "type": "array", + "nullable": true, + "items": { + "type": "string" + }, + "description": "Comma-separated list of event types" + }, + "required": false, + "description": "Comma-separated list of event types", + "name": "eventType", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "default": 50, + "description": "Page size" + }, + "required": false, + "description": "Page size", + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "default": 0, + "description": "Pagination offset" + }, + "required": false, + "description": "Pagination offset", + "name": "offset", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "Opaque pagination cursor" + }, + "required": false, + "description": "Opaque pagination cursor", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "OData filter expression" + }, + "required": false, + "description": "OData filter expression", + "name": "$filter", + "in": "query" + }, + { + "schema": { + "type": "array", + "nullable": true, + "items": { + "type": "string" + }, + "description": "Comma-separated field list" + }, + "required": false, + "description": "Comma-separated field list", + "name": "$select", + "in": "query" + }, + { + "schema": { + "type": "boolean", + "nullable": true, + "description": "Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate" + }, + "required": false, + "description": "Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate", + "name": "includeTotal", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Exact match count. Only present when the request set includeTotal=true" + }, + "hasMore": { + "type": "boolean", + "description": "True when another page exists" + }, + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "nextCursor": { + "type": "string", + "nullable": true + }, + "transfers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "contractId": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "eventType": { + "type": "string", + "enum": [ + "transfer", + "mint", + "burn", + "clawback" + ] + }, + "fromAddress": { + "type": "string", + "nullable": true, + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "toAddress": { + "type": "string", + "nullable": true, + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "amount": { + "type": "string" + }, + "displayAmount": { + "type": "string" + }, + "ledger": { + "type": "integer" + }, + "ledgerClosedAt": { + "type": "string", + "format": "date-time" + }, + "txHash": { + "type": "string" + }, + "eventId": { + "type": "string" + }, + "direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing" + ] + } + }, + "required": [ + "id", + "contractId", + "eventType", + "fromAddress", + "toAddress", + "amount", + "displayAmount", + "ledger", + "ledgerClosedAt", + "txHash", + "eventId" + ] + } + } + }, + "required": [ + "hasMore", + "limit", + "offset", + "transfers" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/transfers/outgoing/{address}": { + "get": { + "summary": "Outgoing transfers", + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "required": true, + "name": "address", + "in": "path" + }, + { + "schema": { + "type": "string", + "nullable": true, + "enum": [ + "testnet", + "mainnet", + null + ], + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "example": "mainnet" + }, + "required": false, + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "name": "network", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "Token contract ID to filter by" + }, + "required": false, + "description": "Token contract ID to filter by", + "name": "contractId", + "in": "query" + }, + { + "schema": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "required": false, + "name": "token", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "fromLedger", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "toLedger", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive lower bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive lower bound on ledgerClosedAt", + "name": "fromDate", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive upper bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive upper bound on ledgerClosedAt", + "name": "toDate", + "in": "query" + }, + { + "schema": { + "type": "array", + "nullable": true, + "items": { + "type": "string" + }, + "description": "Comma-separated list of event types" + }, + "required": false, + "description": "Comma-separated list of event types", + "name": "eventType", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "default": 50, + "description": "Page size" + }, + "required": false, + "description": "Page size", + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "default": 0, + "description": "Pagination offset" + }, + "required": false, + "description": "Pagination offset", + "name": "offset", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "Opaque pagination cursor" + }, + "required": false, + "description": "Opaque pagination cursor", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "OData filter expression" + }, + "required": false, + "description": "OData filter expression", + "name": "$filter", + "in": "query" + }, + { + "schema": { + "type": "array", + "nullable": true, + "items": { + "type": "string" + }, + "description": "Comma-separated field list" + }, + "required": false, + "description": "Comma-separated field list", + "name": "$select", + "in": "query" + }, + { + "schema": { + "type": "boolean", + "nullable": true, + "description": "Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate" + }, + "required": false, + "description": "Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate", + "name": "includeTotal", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Exact match count. Only present when the request set includeTotal=true" + }, + "hasMore": { + "type": "boolean", + "description": "True when another page exists" + }, + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "nextCursor": { + "type": "string", + "nullable": true + }, + "transfers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "contractId": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "eventType": { + "type": "string", + "enum": [ + "transfer", + "mint", + "burn", + "clawback" + ] + }, + "fromAddress": { + "type": "string", + "nullable": true, + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "toAddress": { + "type": "string", + "nullable": true, + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "amount": { + "type": "string" + }, + "displayAmount": { + "type": "string" + }, + "ledger": { + "type": "integer" + }, + "ledgerClosedAt": { + "type": "string", + "format": "date-time" + }, + "txHash": { + "type": "string" + }, + "eventId": { + "type": "string" + }, + "direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing" + ] + } + }, + "required": [ + "id", + "contractId", + "eventType", + "fromAddress", + "toAddress", + "amount", + "displayAmount", + "ledger", + "ledgerClosedAt", + "txHash", + "eventId" + ] + } + } + }, + "required": [ + "hasMore", + "limit", + "offset", + "transfers" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/transfers/address/{address}": { + "get": { + "summary": "All transfers for address", + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "required": true, + "name": "address", + "in": "path" + }, + { + "schema": { + "type": "string", + "nullable": true, + "enum": [ + "testnet", + "mainnet", + null + ], + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "example": "mainnet" + }, + "required": false, + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "name": "network", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "Token contract ID to filter by" + }, + "required": false, + "description": "Token contract ID to filter by", + "name": "contractId", + "in": "query" + }, + { + "schema": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "required": false, + "name": "token", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "fromLedger", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "toLedger", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive lower bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive lower bound on ledgerClosedAt", + "name": "fromDate", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive upper bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive upper bound on ledgerClosedAt", + "name": "toDate", + "in": "query" + }, + { + "schema": { + "type": "array", + "nullable": true, + "items": { + "type": "string" + }, + "description": "Comma-separated list of event types" + }, + "required": false, + "description": "Comma-separated list of event types", + "name": "eventType", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "default": 50, + "description": "Page size" + }, + "required": false, + "description": "Page size", + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "default": 0, + "description": "Pagination offset" + }, + "required": false, + "description": "Pagination offset", + "name": "offset", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "Opaque pagination cursor" + }, + "required": false, + "description": "Opaque pagination cursor", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "OData filter expression" + }, + "required": false, + "description": "OData filter expression", + "name": "$filter", + "in": "query" + }, + { + "schema": { + "type": "array", + "nullable": true, + "items": { + "type": "string" + }, + "description": "Comma-separated field list" + }, + "required": false, + "description": "Comma-separated field list", + "name": "$select", + "in": "query" + }, + { + "schema": { + "type": "boolean", + "nullable": true, + "description": "Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate" + }, + "required": false, + "description": "Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate", + "name": "includeTotal", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Exact match count. Only present when the request set includeTotal=true" + }, + "hasMore": { + "type": "boolean", + "description": "True when another page exists" + }, + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "nextCursor": { + "type": "string", + "nullable": true + }, + "transfers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "contractId": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "eventType": { + "type": "string", + "enum": [ + "transfer", + "mint", + "burn", + "clawback" + ] + }, + "fromAddress": { + "type": "string", + "nullable": true, + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "toAddress": { + "type": "string", + "nullable": true, + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "amount": { + "type": "string" + }, + "displayAmount": { + "type": "string" + }, + "ledger": { + "type": "integer" + }, + "ledgerClosedAt": { + "type": "string", + "format": "date-time" + }, + "txHash": { + "type": "string" + }, + "eventId": { + "type": "string" + }, + "direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing" + ] + } + }, + "required": [ + "id", + "contractId", + "eventType", + "fromAddress", + "toAddress", + "amount", + "displayAmount", + "ledger", + "ledgerClosedAt", + "txHash", + "eventId" + ] + } + } + }, + "required": [ + "hasMore", + "limit", + "offset", + "transfers" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/transfers/address/{address}/export.csv": { + "get": { + "summary": "Export transfers to CSV", + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "required": true, + "name": "address", + "in": "path" + }, + { + "schema": { + "type": "string", + "nullable": true, + "enum": [ + "testnet", + "mainnet", + null + ], + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "example": "mainnet" + }, + "required": false, + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "name": "network", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "Token contract ID to filter by" + }, + "required": false, + "description": "Token contract ID to filter by", + "name": "contractId", + "in": "query" + }, + { + "schema": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "required": false, + "name": "token", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "fromLedger", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "toLedger", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive lower bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive lower bound on ledgerClosedAt", + "name": "fromDate", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive upper bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive upper bound on ledgerClosedAt", + "name": "toDate", + "in": "query" + }, + { + "schema": { + "type": "array", + "nullable": true, + "items": { + "type": "string" + }, + "description": "Comma-separated list of event types" + }, + "required": false, + "description": "Comma-separated list of event types", + "name": "eventType", + "in": "query" + }, + { + "schema": { + "type": "boolean", + "nullable": true, + "description": "Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate" + }, + "required": false, + "description": "Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate", + "name": "includeTotal", + "in": "query" + } + ], + "responses": { + "200": { + "description": "CSV export", + "content": { + "text/csv": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/transfers/tx/{txHash}": { + "get": { + "summary": "Transfers by transaction", + "parameters": [ + { + "schema": { + "type": "string", + "description": "Transaction hash" + }, + "required": true, + "description": "Transaction hash", + "name": "txHash", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "transfers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "contractId": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "eventType": { + "type": "string", + "enum": [ + "transfer", + "mint", + "burn", + "clawback" + ] + }, + "fromAddress": { + "type": "string", + "nullable": true, + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "toAddress": { + "type": "string", + "nullable": true, + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "amount": { + "type": "string" + }, + "displayAmount": { + "type": "string" + }, + "ledger": { + "type": "integer" + }, + "ledgerClosedAt": { + "type": "string", + "format": "date-time" + }, + "txHash": { + "type": "string" + }, + "eventId": { + "type": "string" + }, + "direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing" + ] + } + }, + "required": [ + "id", + "contractId", + "eventType", + "fromAddress", + "toAddress", + "amount", + "displayAmount", + "ledger", + "ledgerClosedAt", + "txHash", + "eventId" + ] + } + } + }, + "required": [ + "transfers" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/summary/{address}": { + "get": { + "summary": "Token summary", + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "required": true, + "name": "address", + "in": "path" + }, + { + "schema": { + "type": "string", + "nullable": true, + "enum": [ + "testnet", + "mainnet", + null + ], + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "example": "mainnet" + }, + "required": false, + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "name": "network", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "Token contract ID to filter by" + }, + "required": false, + "description": "Token contract ID to filter by", + "name": "contractId", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive lower bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive lower bound on ledgerClosedAt", + "name": "fromDate", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive upper bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive upper bound on ledgerClosedAt", + "name": "toDate", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "address": { + "type": "string", + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "window": { + "type": "object", + "properties": { + "fromDate": { + "type": "string", + "nullable": true, + "format": "date-time", + "example": "2025-01-01T00:00:00Z" + }, + "toDate": { + "type": "string", + "nullable": true, + "format": "date-time", + "example": "2025-01-01T00:00:00Z" + } + }, + "required": [ + "fromDate", + "toDate" + ] + }, + "tokens": { + "type": "array", + "items": { + "type": "object", + "properties": { + "contractId": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "totalReceived": { + "type": "string" + }, + "totalSent": { + "type": "string" + }, + "netFlow": { + "type": "string" + }, + "displayTotalReceived": { + "type": "string" + }, + "displayTotalSent": { + "type": "string" + }, + "displayNetFlow": { + "type": "string" + }, + "txCount": { + "type": "integer" + } + }, + "required": [ + "contractId", + "totalReceived", + "totalSent", + "netFlow", + "displayTotalReceived", + "displayTotalSent", + "displayNetFlow", + "txCount" + ] + } + } + }, + "required": [ + "address", + "window", + "tokens" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/accounts/{address}/summary": { + "get": { + "summary": "Token summary", + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "required": true, + "name": "address", + "in": "path" + }, + { + "schema": { + "type": "string", + "nullable": true, + "enum": [ + "testnet", + "mainnet", + null + ], + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "example": "mainnet" + }, + "required": false, + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "name": "network", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "Token contract ID to filter by" + }, + "required": false, + "description": "Token contract ID to filter by", + "name": "contractId", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive lower bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive lower bound on ledgerClosedAt", + "name": "fromDate", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive upper bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive upper bound on ledgerClosedAt", + "name": "toDate", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "address": { + "type": "string", + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "window": { + "type": "object", + "properties": { + "fromDate": { + "type": "string", + "nullable": true, + "format": "date-time", + "example": "2025-01-01T00:00:00Z" + }, + "toDate": { + "type": "string", + "nullable": true, + "format": "date-time", + "example": "2025-01-01T00:00:00Z" + } + }, + "required": [ + "fromDate", + "toDate" + ] + }, + "tokens": { + "type": "array", + "items": { + "type": "object", + "properties": { + "contractId": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "totalReceived": { + "type": "string" + }, + "totalSent": { + "type": "string" + }, + "netFlow": { + "type": "string" + }, + "displayTotalReceived": { + "type": "string" + }, + "displayTotalSent": { + "type": "string" + }, + "displayNetFlow": { + "type": "string" + }, + "txCount": { + "type": "integer" + } + }, + "required": [ + "contractId", + "totalReceived", + "totalSent", + "netFlow", + "displayTotalReceived", + "displayTotalSent", + "displayNetFlow", + "txCount" + ] + } + } + }, + "required": [ + "address", + "window", + "tokens" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/accounts/{address}/balance": { + "get": { + "summary": "Derived token balances", + "description": "Per-token balance for an address, derived by summing indexed transfers — not a read from chain. Excludes any history before the indexer's start ledger, so an address that held a token before then reads low.", + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "required": true, + "name": "address", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "address": { + "type": "string", + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "network": { + "type": "string", + "enum": [ + "testnet", + "mainnet" + ] + }, + "balances": { + "type": "array", + "items": { + "type": "object", + "properties": { + "contractId": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "balance": { + "type": "string" + }, + "displayBalance": { + "type": "string" + } + }, + "required": [ + "contractId", + "balance", + "displayBalance" + ] + } + }, + "derivedFromLedger": { + "type": "boolean", + "enum": [ + true + ] + }, + "note": { + "type": "string" + } + }, + "required": [ + "address", + "network", + "balances", + "derivedFromLedger", + "note" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/accounts/{address}/transfers": { + "get": { + "summary": "Account transfers", + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "required": true, + "name": "address", + "in": "path" + }, + { + "schema": { + "type": "string", + "nullable": true, + "enum": [ + "testnet", + "mainnet", + null + ], + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "example": "mainnet" + }, + "required": false, + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "name": "network", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "Token contract ID to filter by" + }, + "required": false, + "description": "Token contract ID to filter by", + "name": "contractId", + "in": "query" + }, + { + "schema": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "required": false, + "name": "token", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "fromLedger", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "toLedger", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive lower bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive lower bound on ledgerClosedAt", + "name": "fromDate", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive upper bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive upper bound on ledgerClosedAt", + "name": "toDate", + "in": "query" + }, + { + "schema": { + "type": "array", + "nullable": true, + "items": { + "type": "string" + }, + "description": "Comma-separated list of event types" + }, + "required": false, + "description": "Comma-separated list of event types", + "name": "eventType", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "default": 50, + "description": "Page size" + }, + "required": false, + "description": "Page size", + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "default": 0, + "description": "Pagination offset" + }, + "required": false, + "description": "Pagination offset", + "name": "offset", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "Opaque pagination cursor" + }, + "required": false, + "description": "Opaque pagination cursor", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "OData filter expression" + }, + "required": false, + "description": "OData filter expression", + "name": "$filter", + "in": "query" + }, + { + "schema": { + "type": "array", + "nullable": true, + "items": { + "type": "string" + }, + "description": "Comma-separated field list" + }, + "required": false, + "description": "Comma-separated field list", + "name": "$select", + "in": "query" + }, + { + "schema": { + "type": "boolean", + "nullable": true, + "description": "Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate" + }, + "required": false, + "description": "Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate", + "name": "includeTotal", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Exact match count. Only present when the request set includeTotal=true" + }, + "hasMore": { + "type": "boolean", + "description": "True when another page exists" + }, + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "nextCursor": { + "type": "string", + "nullable": true + }, + "transfers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "contractId": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "eventType": { + "type": "string", + "enum": [ + "transfer", + "mint", + "burn", + "clawback" + ] + }, + "fromAddress": { + "type": "string", + "nullable": true, + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "toAddress": { + "type": "string", + "nullable": true, + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "amount": { + "type": "string" + }, + "displayAmount": { + "type": "string" + }, + "ledger": { + "type": "integer" + }, + "ledgerClosedAt": { + "type": "string", + "format": "date-time" + }, + "txHash": { + "type": "string" + }, + "eventId": { + "type": "string" + }, + "direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing" + ] + } + }, + "required": [ + "id", + "contractId", + "eventType", + "fromAddress", + "toAddress", + "amount", + "displayAmount", + "ledger", + "ledgerClosedAt", + "txHash", + "eventId" + ] + } + } + }, + "required": [ + "hasMore", + "limit", + "offset", + "transfers" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/webhooks": { + "post": { + "summary": "Create a webhook subscription", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri" + }, + "secret": { + "type": "string", + "minLength": 1 + }, + "filter": { + "type": "object", + "nullable": true, + "properties": { + "contract": { + "type": "string" + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "min_amount": { + "type": "string" + } + }, + "additionalProperties": false + }, + "active": { + "type": "boolean" + } + }, + "required": [ + "url", + "secret" + ] + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "filter": { + "type": "object", + "nullable": true, + "properties": { + "contract": { + "type": "string" + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "min_amount": { + "type": "string" + } + }, + "additionalProperties": false + }, + "active": { + "type": "boolean" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "signatureVerification": { + "type": "object", + "properties": { + "header": { + "type": "string", + "enum": [ + "X-Wraith-Signature" + ] + }, + "algorithm": { + "type": "string", + "enum": [ + "sha256" + ] + }, + "format": { + "type": "string" + }, + "body": { + "type": "string" + }, + "example": { + "type": "string" + }, + "safeCompare": { + "type": "string" + } + }, + "required": [ + "header", + "algorithm", + "format", + "body", + "example", + "safeCompare" + ] + } + }, + "required": [ + "id", + "url", + "filter", + "active", + "createdAt", + "signatureVerification" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + }, + "get": { + "summary": "List webhook subscriptions", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "subscriptions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "filter": { + "type": "object", + "nullable": true, + "properties": { + "contract": { + "type": "string" + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "min_amount": { + "type": "string" + } + }, + "additionalProperties": false + }, + "active": { + "type": "boolean" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "url", + "filter", + "active", + "createdAt", + "updatedAt" + ] + } + } + }, + "required": [ + "subscriptions" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/webhooks/{id}": { + "delete": { + "summary": "Delete a webhook subscription", + "parameters": [ + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean", + "enum": [ + true + ] + } + }, + "required": [ + "ok" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/webhooks/{id}/deliveries": { + "get": { + "summary": "Webhook delivery log", + "parameters": [ + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "id", + "in": "path" + }, + { + "schema": { + "type": "string", + "enum": [ + "pending", + "success", + "failed" + ] + }, + "required": false, + "name": "status", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "default": 50, + "description": "Page size" + }, + "required": false, + "description": "Page size", + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "default": 0, + "description": "Pagination offset" + }, + "required": false, + "description": "Pagination offset", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "deliveries": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "eventId": { + "type": "string" + }, + "status": { + "type": "string" + }, + "attempts": { + "type": "integer" + }, + "lastStatusCode": { + "type": "integer", + "nullable": true + }, + "lastError": { + "type": "string", + "nullable": true + }, + "nextRetryAt": { + "type": "string", + "nullable": true, + "format": "date-time" + }, + "deliveredAt": { + "type": "string", + "nullable": true, + "format": "date-time" + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "eventId", + "status", + "attempts", + "lastStatusCode", + "lastError", + "nextRetryAt", + "deliveredAt", + "createdAt" + ] + } + } + }, + "required": [ + "total", + "limit", + "offset", + "deliveries" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/host-fn/{contractId}": { + "get": { + "summary": "Host function logs", + "parameters": [ + { + "schema": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "required": true, + "name": "contractId", + "in": "path" + }, + { + "schema": { + "type": "string", + "nullable": true, + "enum": [ + "testnet", + "mainnet", + null + ], + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "example": "mainnet" + }, + "required": false, + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "name": "network", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "Host function name to filter by" + }, + "required": false, + "description": "Host function name to filter by", + "name": "functionName", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "default": 50, + "description": "Page size" + }, + "required": false, + "description": "Page size", + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "default": 0, + "description": "Pagination offset" + }, + "required": false, + "description": "Pagination offset", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "contractId": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "total": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "logs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "contractId": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "functionName": { + "type": "string" + }, + "args": { + "nullable": true + }, + "result": { + "nullable": true + }, + "gasUsed": { + "type": "string", + "nullable": true + }, + "ledger": { + "type": "integer" + }, + "ledgerClosedAt": { + "type": "string", + "format": "date-time" + }, + "txHash": { + "type": "string" + }, + "eventId": { + "type": "string" + } + }, + "required": [ + "contractId", + "functionName", + "gasUsed", + "ledger", + "ledgerClosedAt", + "txHash", + "eventId" + ] + } + } + }, + "required": [ + "contractId", + "total", + "limit", + "offset", + "logs" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/nfts/transfers": { + "get": { + "summary": "NFT transfers", + "parameters": [ + { + "schema": { + "type": "string", + "nullable": true, + "enum": [ + "testnet", + "mainnet", + null + ], + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "example": "mainnet" + }, + "required": false, + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "name": "network", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "NFT contract ID to filter by" + }, + "required": false, + "description": "NFT contract ID to filter by", + "name": "contract", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "NFT token identifier" + }, + "required": false, + "description": "NFT token identifier", + "name": "token_id", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "Filter by sender or recipient address" + }, + "required": false, + "description": "Filter by sender or recipient address", + "name": "address", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "fromLedger", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "toLedger", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "default": 50, + "description": "Page size" + }, + "required": false, + "description": "Page size", + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "default": 0, + "description": "Pagination offset" + }, + "required": false, + "description": "Pagination offset", + "name": "offset", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "Opaque pagination cursor" + }, + "required": false, + "description": "Opaque pagination cursor", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "OData filter expression" + }, + "required": false, + "description": "OData filter expression", + "name": "$filter", + "in": "query" + }, + { + "schema": { + "type": "array", + "nullable": true, + "items": { + "type": "string" + }, + "description": "Comma-separated field list" + }, + "required": false, + "description": "Comma-separated field list", + "name": "$select", + "in": "query" + }, + { + "schema": { + "type": "boolean", + "nullable": true, + "description": "Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate" + }, + "required": false, + "description": "Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate", + "name": "includeTotal", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Exact match count. Only present when the request set includeTotal=true" + }, + "hasMore": { + "type": "boolean", + "description": "True when another page exists" + }, + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "nextCursor": { + "type": "string", + "nullable": true + }, + "transfers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "contractId": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "tokenId": { + "type": "string" + }, + "fromAddress": { + "type": "string", + "nullable": true, + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "toAddress": { + "type": "string", + "nullable": true, + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "ledger": { + "type": "integer" + }, + "ledgerClosedAt": { + "type": "string", + "format": "date-time" + }, + "txHash": { + "type": "string" + }, + "eventId": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "contractId", + "tokenId", + "fromAddress", + "toAddress", + "ledger", + "ledgerClosedAt", + "txHash", + "eventId", + "createdAt" + ] + } + } + }, + "required": [ + "hasMore", + "limit", + "offset", + "transfers" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/nfts/owners/{contract}/{token_id}": { + "get": { + "summary": "NFT owner lookup", + "parameters": [ + { + "schema": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "required": true, + "name": "contract", + "in": "path" + }, + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "token_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "contract": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "token_id": { + "type": "string" + }, + "owner": { + "type": "string", + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "metadata": { + "type": "object", + "nullable": true, + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "tokenUri": { + "type": "string", + "nullable": true + } + }, + "required": [ + "name", + "tokenUri" + ] + } + }, + "required": [ + "contract", + "token_id", + "owner", + "metadata" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/assets/popular": { + "get": { + "summary": "Popular assets", + "parameters": [ + { + "schema": { + "type": "string", + "nullable": true, + "enum": [ + "testnet", + "mainnet", + null + ], + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "example": "mainnet" + }, + "required": false, + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "name": "network", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "1h", + "24h", + "7d" + ], + "default": "24h" + }, + "required": false, + "name": "window", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "transfers", + "volume" + ], + "default": "transfers" + }, + "required": false, + "name": "by", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "default": 20, + "description": "Page size" + }, + "required": false, + "description": "Page size", + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "default": 0, + "description": "Pagination offset" + }, + "required": false, + "description": "Pagination offset", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "window": { + "type": "string", + "enum": [ + "1h", + "24h", + "7d" + ] + }, + "by": { + "type": "string", + "enum": [ + "transfers", + "volume" + ] + }, + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "total": { + "type": "integer" + }, + "assets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "contractId": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "transferCount": { + "type": "integer" + }, + "volume": { + "type": "string" + }, + "displayVolume": { + "type": "string" + } + }, + "required": [ + "contractId", + "transferCount", + "volume", + "displayVolume" + ] + } + } + }, + "required": [ + "window", + "by", + "limit", + "offset", + "total", + "assets" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/search": { + "get": { + "summary": "Fuzzy search across accounts, assets, and contracts", + "parameters": [ + { + "schema": { + "type": "string", + "nullable": true, + "enum": [ + "testnet", + "mainnet", + null + ], + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "example": "mainnet" + }, + "required": false, + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "name": "network", + "in": "query" + }, + { + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 80 + }, + "required": true, + "name": "q", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "query": { + "type": "string" + }, + "count": { + "type": "integer" + }, + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "account", + "asset", + "contract" + ] + }, + "value": { + "type": "string" + }, + "isSac": { + "type": "boolean" + }, + "lastActivityAt": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ] + } + } + }, + "required": [ + "query", + "count", + "results" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/tokens": { + "get": { + "summary": "Cached token metadata", + "description": "All tokens the indexer has encountered and cached, on the selected network. Served from the in-memory token cache — a token appears here once the indexer has seen a transfer of it and resolved its symbol, name and decimals.", + "parameters": [ + { + "schema": { + "type": "string", + "nullable": true, + "enum": [ + "testnet", + "mainnet", + null + ], + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "example": "mainnet" + }, + "required": false, + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "name": "network", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean", + "enum": [ + true + ] + }, + "network": { + "type": "string", + "enum": [ + "testnet", + "mainnet" + ] + }, + "tokens": { + "type": "array", + "items": { + "type": "object", + "properties": { + "network": { + "type": "string", + "enum": [ + "testnet", + "mainnet" + ] + }, + "contractId": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "symbol": { + "type": "string" + }, + "name": { + "type": "string" + }, + "decimals": { + "type": "integer" + } + }, + "required": [ + "network", + "contractId", + "symbol", + "name", + "decimals" + ] + } + } + }, + "required": [ + "ok", + "network", + "tokens" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/fees": { + "get": { + "summary": "Veil platform fee", + "description": "What the wallet should charge on a send, and where to pay it, per asset. The fee is collected on-chain in the same transaction as the send; this only tells the client the amount, so it can change without shipping an app. Unauthenticated and served with `Cache-Control: no-store`, because a stale fee is one the user was shown but did not agree to. `enabled: false` means no fee is configured today.", + "parameters": [ + { + "schema": { + "type": "string", + "nullable": true, + "description": "The amount being sent, in whole units of the asset. When given and a percentage fee is computed for it; without it each fee is the floor. Optional because shipped clients predate it.", + "example": "25" + }, + "required": false, + "description": "The amount being sent, in whole units of the asset. When given and a percentage fee is computed for it; without it each fee is the floor. Optional because shipped clients predate it.", + "name": "amount", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "False when no fee is configured, so a client can tell a deliberate waiver from a backend too old to know about fees." + }, + "fees": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "amount": { + "type": "string", + "description": "What to charge, in whole units of the fee asset, as a decimal string.", + "example": "0.25" + }, + "assetCode": { + "type": "string", + "example": "USDC" + }, + "assetIssuer": { + "type": "string", + "nullable": true, + "description": "Null for native XLM." + }, + "destination": { + "type": "string", + "description": "The classic account the fee is paid to." + }, + "percent": { + "type": "string", + "description": "Percent of the sent amount, when the fee scales. Absent when it is flat." + }, + "min": { + "type": "string", + "description": "Floor; present only alongside percent." + }, + "max": { + "type": "string", + "description": "Cap; present only alongside percent." + } + }, + "required": [ + "amount", + "assetCode", + "assetIssuer", + "destination" + ] + }, + "description": "Keyed by asset code. Always charged in the asset being sent." + } + }, + "required": [ + "enabled", + "fees" + ] + } + } + } + } + } + } + }, + "/transfers.csv": { + "get": { + "summary": "Export all transfers as CSV", + "description": "Streams every transfer matching the filters as CSV. No pagination: the response is the entire matching set, so narrow it with the ledger or date bounds on large deployments.", + "parameters": [ + { + "schema": { + "type": "string", + "nullable": true, + "enum": [ + "testnet", + "mainnet", + null + ], + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "example": "mainnet" + }, + "required": false, + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "name": "network", + "in": "query" + }, + { + "schema": { + "type": "string", + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "required": false, + "name": "address", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "Token contract ID to filter by" + }, + "required": false, + "description": "Token contract ID to filter by", + "name": "contractId", + "in": "query" + }, + { + "schema": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "required": false, + "name": "token", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "fromLedger", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "toLedger", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive lower bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive lower bound on ledgerClosedAt", + "name": "fromDate", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive upper bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive upper bound on ledgerClosedAt", + "name": "toDate", + "in": "query" + }, + { + "schema": { + "type": "array", + "nullable": true, + "items": { + "type": "string" + }, + "description": "Comma-separated list of event types" + }, + "required": false, + "description": "Comma-separated list of event types", + "name": "eventType", + "in": "query" + } + ], + "responses": { + "200": { + "description": "File download of the full matching transfer set (no pagination).", + "content": { + "text/csv": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/transfers.parquet": { + "get": { + "summary": "Export all transfers as Parquet", + "description": "Streams every transfer matching the filters as an Apache Parquet file. Same filters and streaming behaviour as /transfers.csv.", + "parameters": [ + { + "schema": { + "type": "string", + "nullable": true, + "enum": [ + "testnet", + "mainnet", + null + ], + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "example": "mainnet" + }, + "required": false, + "description": "Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins.", + "name": "network", + "in": "query" + }, + { + "schema": { + "type": "string", + "minLength": 1, + "example": "GABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + "required": false, + "name": "address", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "description": "Token contract ID to filter by" + }, + "required": false, + "description": "Token contract ID to filter by", + "name": "contractId", + "in": "query" + }, + { + "schema": { + "type": "string", + "pattern": "^C[A-Z2-7]{55}$", + "example": "CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B" + }, + "required": false, + "name": "token", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "fromLedger", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true + }, + "required": false, + "name": "toLedger", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive lower bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive lower bound on ledgerClosedAt", + "name": "fromDate", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Inclusive upper bound on ledgerClosedAt" + }, + "required": false, + "description": "Inclusive upper bound on ledgerClosedAt", + "name": "toDate", + "in": "query" + }, + { + "schema": { + "type": "array", + "nullable": true, + "items": { + "type": "string" + }, + "description": "Comma-separated list of event types" + }, + "required": false, + "description": "Comma-separated list of event types", + "name": "eventType", + "in": "query" + } + ], + "responses": { + "200": { + "description": "File download of the full matching transfer set (no pagination).", + "content": { + "text/csv": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/webhooks/linq": { + "post": { + "summary": "Payout webhook (provider callback)", + "description": "Internal endpoint. Not part of the public data API: (provider callback / wallet-only cash-out surface served on behalf of the Veil wallet client).", + "x-internal": true, + "responses": { + "200": { + "description": "Acknowledged", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean", + "enum": [ + true + ] + } + }, + "required": [ + "ok" + ] + } + } + } + }, + "400": { + "description": "Unrecognised event", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "401": { + "description": "Invalid signature", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/offramp/availability": { + "get": { + "summary": "Is cash-out enabled on this deployment", + "description": "Internal endpoint. Not part of the public data API: (provider callback / wallet-only cash-out surface served on behalf of the Veil wallet client). Answers the availability question without pricing anything: it makes no provider call, so it costs none of the shared rate allowance.", + "x-internal": true, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "available": { + "type": "boolean", + "enum": [ + true + ] + } + }, + "required": [ + "available" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "503": { + "description": "Offramp not configured on this deployment", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/offramp/rate": { + "get": { + "summary": "Indicative naira-per-coin rate", + "description": "Internal endpoint. Not part of the public data API: (provider callback / wallet-only cash-out surface served on behalf of the Veil wallet client).", + "x-internal": true, + "parameters": [ + { + "schema": { + "type": "string", + "enum": [ + "xlm", + "usdc" + ], + "description": "Asset to price. Send it: without one the answer is priced for `usdc`, which is naira per dollar and wrong for every other asset by that asset's price. Omitting it is deprecated and will be refused once the installed wallet builds ask for a coin; a response always names the coin it was priced for.", + "example": "usdc" + }, + "required": false, + "description": "Asset to price. Send it: without one the answer is priced for `usdc`, which is naira per dollar and wrong for every other asset by that asset's price. Omitting it is deprecated and will be refused once the installed wallet builds ask for a coin; a response always names the coin it was priced for.", + "name": "coin", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "coin": { + "type": "string", + "enum": [ + "xlm", + "usdc" + ], + "description": "The asset `rate` is quoted per unit of. Always present, including for a request that named none — which is priced as `usdc`." + }, + "rate": { + "type": "number", + "description": "Naira per one unit of the requested coin, not naira per dollar." + }, + "indicative": { + "type": "boolean", + "enum": [ + true + ] + } + }, + "required": [ + "coin", + "rate", + "indicative" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "502": { + "description": "Payout provider error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "503": { + "description": "Offramp not configured on this deployment", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/offramp/verify-bank": { + "post": { + "summary": "Verify a bank account before creating an order", + "description": "Internal endpoint. Not part of the public data API: (provider callback / wallet-only cash-out surface served on behalf of the Veil wallet client).", + "x-internal": true, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "bankCode": { + "type": "string", + "minLength": 1 + }, + "accountNumber": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "bankCode", + "accountNumber" + ], + "additionalProperties": { + "nullable": true + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "accountName": { + "type": "string" + }, + "bankName": { + "type": "string" + }, + "accountNumber": { + "type": "string" + }, + "bankCode": { + "type": "string" + } + }, + "required": [ + "accountName", + "bankName", + "accountNumber", + "bankCode" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "502": { + "description": "Payout provider error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "503": { + "description": "Offramp not configured on this deployment", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/offramp/trustline": { + "get": { + "summary": "Check a refund address holds a USDC trustline", + "description": "Internal endpoint. Not part of the public data API: (provider callback / wallet-only cash-out surface served on behalf of the Veil wallet client).", + "x-internal": true, + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "address", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "valid": { + "type": "boolean" + }, + "trustsUSDC": { + "type": "boolean" + } + }, + "required": [ + "address", + "valid", + "trustsUSDC" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "502": { + "description": "Payout provider error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "503": { + "description": "Offramp not configured on this deployment", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/offramp/orders": { + "post": { + "summary": "Create a cash-out order", + "description": "Internal endpoint. Not part of the public data API: (provider callback / wallet-only cash-out surface served on behalf of the Veil wallet client).", + "x-internal": true, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "amountNGN": { + "type": "number" + }, + "amountStableCoin": { + "type": "number" + }, + "bankAccount": { + "type": "string", + "minLength": 1 + }, + "bankCode": { + "type": "string", + "minLength": 1 + }, + "bankName": { + "type": "string", + "minLength": 1 + }, + "accountName": { + "type": "string", + "minLength": 1 + }, + "refundAddress": { + "type": "string" + }, + "walletAddress": { + "type": "string", + "minLength": 1 + }, + "idempotencyKey": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "bankAccount", + "bankCode", + "bankName", + "accountName", + "walletAddress", + "idempotencyKey" + ], + "additionalProperties": { + "nullable": true + } + } + } + } + }, + "responses": { + "200": { + "description": "Replayed existing order (same idempotencyKey)", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "walletAddress": { + "type": "string", + "description": "Deposit address the stablecoin is sent to." + }, + "chain": { + "type": "string" + }, + "coin": { + "type": "string" + }, + "amountStableCoin": { + "type": "number" + }, + "amountNGN": { + "type": "number" + }, + "rate": { + "type": "number" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "waiting_for_deposit", + "processing", + "completed", + "failed", + "expired" + ], + "description": "Client-facing status mapped from the provider's raw one." + }, + "providerStatus": { + "type": "string", + "description": "Raw provider status, when the order is not a replay." + }, + "replayed": { + "type": "boolean" + } + }, + "required": [ + "id", + "walletAddress", + "chain", + "coin", + "amountStableCoin", + "amountNGN", + "rate", + "status" + ] + } + } + } + }, + "201": { + "description": "Order created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "walletAddress": { + "type": "string", + "description": "Deposit address the stablecoin is sent to." + }, + "chain": { + "type": "string" + }, + "coin": { + "type": "string" + }, + "amountStableCoin": { + "type": "number" + }, + "amountNGN": { + "type": "number" + }, + "rate": { + "type": "number" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "waiting_for_deposit", + "processing", + "completed", + "failed", + "expired" + ], + "description": "Client-facing status mapped from the provider's raw one." + }, + "providerStatus": { + "type": "string", + "description": "Raw provider status, when the order is not a replay." + }, + "replayed": { + "type": "boolean" + } + }, + "required": [ + "id", + "walletAddress", + "chain", + "coin", + "amountStableCoin", + "amountNGN", + "rate", + "status" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "502": { + "description": "Payout provider error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "503": { + "description": "Offramp not configured on this deployment", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + }, + "/offramp/orders/{orderId}": { + "get": { + "summary": "Get the status of a cash-out order", + "description": "Internal endpoint. Not part of the public data API: (provider callback / wallet-only cash-out surface served on behalf of the Veil wallet client).", + "x-internal": true, + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "orderId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "waiting_for_deposit", + "processing", + "completed", + "failed", + "expired" + ], + "description": "Client-facing status mapped from the provider's raw one." + }, + "providerStatus": { + "type": "string" + }, + "amountStableCoin": { + "type": "number" + }, + "amountNGN": { + "type": "number" + }, + "depositAddress": { + "type": "string" + }, + "rate": { + "type": "number" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "source": { + "type": "string", + "enum": [ + "live", + "cache" + ] + } + }, + "required": [ + "id", + "status", + "providerStatus", + "amountStableCoin", + "amountNGN", + "depositAddress", + "rate", + "createdAt", + "source" + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "502": { + "description": "Payout provider error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + }, + "503": { + "description": "Offramp not configured on this deployment", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + } + } + } + } + } + } + } +} diff --git a/openapi.json b/openapi.json index 798613d38..da66bbedb 100644 --- a/openapi.json +++ b/openapi.json @@ -676,19 +676,6 @@ "schema": { "type": "object", "properties": { - "ok": { - "type": "boolean", - "enum": [ - true - ] - }, - "network": { - "type": "string", - "enum": [ - "testnet", - "mainnet" - ] - }, "lastIndexedLedger": { "type": "integer", "nullable": true @@ -745,13 +732,67 @@ "nullable": true } } + }, + "status": { + "type": "string", + "enum": [ + "healthy", + "degraded" + ] + }, + "dependencies": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "ok", + "degraded", + "down", + "unknown" + ] + }, + "lastCheckedAt": { + "type": "string", + "nullable": true, + "format": "date-time" + }, + "lastOkAt": { + "type": "string", + "nullable": true, + "format": "date-time" + }, + "latencyMs": { + "type": "integer", + "nullable": true + }, + "detail": { + "type": "string", + "nullable": true + } + }, + "required": [ + "name", + "status", + "lastCheckedAt", + "lastOkAt", + "latencyMs", + "detail" + ] + } } }, "required": [ - "ok", "lastIndexedLedger", "latestLedger", - "lagLedgers" + "lagLedgers", + "status", + "dependencies" ], "additionalProperties": { "nullable": true @@ -4871,11 +4912,11 @@ "schema": { "type": "string", "nullable": true, - "description": "The amount being sent, in whole units of the asset. When given and a percentage fee is configured, each fee is computed for it; without it each fee is the floor. Optional because shipped clients predate it.", + "description": "The amount being sent, in whole units of the asset. When given and a percentage fee is computed for it; without it each fee is the floor. Optional because shipped clients predate it.", "example": "25" }, "required": false, - "description": "The amount being sent, in whole units of the asset. When given and a percentage fee is configured, each fee is computed for it; without it each fee is the floor. Optional because shipped clients predate it.", + "description": "The amount being sent, in whole units of the asset. When given and a percentage fee is computed for it; without it each fee is the floor. Optional because shipped clients predate it.", "name": "amount", "in": "query" } diff --git a/package-lock.json b/package-lock.json index 27cf923c1..22e589958 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,9 @@ "@apollo/server": "^5.5.1", "@as-integrations/express4": "^1.1.2", "@asteasolutions/zod-to-openapi": "^9.1.0", + "@emnapi/core": "^1.11.3", + "@emnapi/runtime": "^1.11.3", + "@emnapi/wasi-threads": "^1.2.3", "@fast-csv/format": "^5.0.7", "@prisma/client": "^5.10.0", "@stellar/stellar-sdk": "^15.0.1", @@ -558,6 +561,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1098,38 +1102,31 @@ } }, "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "dev": true, + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz", + "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", "license": "MIT", - "optional": true, "peer": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.2", + "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", - "dev": true, + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", "license": "MIT", - "optional": true, "peer": true, "dependencies": { "tslib": "^2.4.0" } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", - "dev": true, + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -2290,22 +2287,25 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.5.tgz", + "integrity": "sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "node_modules/@noble/curves": { @@ -3014,9 +3014,9 @@ "license": "MIT" }, "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", "dev": true, "license": "MIT", "optional": true, @@ -3249,6 +3249,7 @@ "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -4280,6 +4281,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -5224,6 +5226,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -5688,6 +5691,7 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", "license": "MIT", + "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -6257,6 +6261,7 @@ "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.4.2", "@jest/types": "30.4.1", @@ -7890,6 +7895,7 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/engines": "5.22.0" }, @@ -8845,6 +8851,7 @@ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9026,6 +9033,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -9237,6 +9245,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9420,6 +9429,7 @@ "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", @@ -9561,6 +9571,7 @@ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9957,6 +9968,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 706dc5dba..5d4416892 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "wraith", "version": "1.0.0", - "description": "Soroban incoming token transfer indexer \u2014 fills the Horizon indexing gap for SEP-41/CAP-67 events", + "description": "Soroban incoming token transfer indexer — fills the Horizon indexing gap for SEP-41/CAP-67 events", "main": "dist/index.js", "scripts": { "dev": "ts-node-dev --respawn --transpile-only src/index.ts", @@ -67,6 +67,9 @@ "@apollo/server": "^5.5.1", "@as-integrations/express4": "^1.1.2", "@asteasolutions/zod-to-openapi": "^9.1.0", + "@emnapi/core": "^1.11.3", + "@emnapi/runtime": "^1.11.3", + "@emnapi/wasi-threads": "^1.2.3", "@fast-csv/format": "^5.0.7", "@prisma/client": "^5.10.0", "@stellar/stellar-sdk": "^15.0.1", diff --git a/src/api.ts b/src/api.ts index 23fc71f38..68f89671b 100644 --- a/src/api.ts +++ b/src/api.ts @@ -34,6 +34,7 @@ import { networkMiddleware, requestNetwork } from "./middleware/network"; import { renderMetrics, metricsContentType, httpRequestsTotal, httpRequestDurationSeconds } from "./metrics"; import { getAllCachedTokens, getCachedTokenDecimals } from "./tokenCache"; import { toDisplayAmount } from "./amount"; +import { getDependencyStatus, dependencyStatusBoundMs } from "./dependencies"; import { HttpError, newCorrelationId } from "./utils/errors"; // ─── RPC Health Check Cache ─────────────────────────────────────────────── @@ -472,6 +473,7 @@ export function createApp(): express.Application { const rpcSuccess = latestLedgerResult.status === "fulfilled" && typeof latestLedgerResult.value === "number"; const latestLedger = rpcSuccess ? latestLedgerResult.value : null; const stats = getIndexerStats(selected); + const dependencies = getDependencyStatus(); // Per-network progress (#161). The top-level fields above stay as they // were so existing consumers are unaffected; this reports each loop @@ -512,7 +514,7 @@ export function createApp(): express.Application { ); const byNetwork = Object.fromEntries(perNetwork); - if (rpcSuccess && latestLedger !== null) { + if (rpcSuccess && latestLedger !== null && dependencies.railsUsable) { res.json({ ok: true, status: "healthy", @@ -527,9 +529,12 @@ export function createApp(): express.Application { lagLedgers: latestLedger - (lastIndexedLedger ?? latestLedger), ...stats, networks: byNetwork, - cache: cacheStatus(), - }); - } else { + dependencies, + dependency_status_bound_ms: dependencyStatusBoundMs, + cache: cacheStatus(), + }); +} +else { res.setHeader("X-Data-Stale", "true"); if (lastIndexedLedger !== null) { res.setHeader("X-As-Of-Ledger", String(lastIndexedLedger)); @@ -542,13 +547,15 @@ export function createApp(): express.Application { as_of_ledger: lastIndexedLedger ?? undefined, lastIndexedLedger, last_indexed_ledger: lastIndexedLedger, - latestLedger: null, - lagLedgers: null, + latestLedger: rpcSuccess ? latestLedger : null, + lagLedgers: rpcSuccess && latestLedger !== null ? latestLedger - (lastIndexedLedger ?? latestLedger) : null, ...stats, // Also reported when degraded — with two loops, "RPC is down" is // usually true of one chain only, and the top-level nulls above // cannot say which. networks: byNetwork, + dependencies, + dependency_status_bound_ms: dependencyStatusBoundMs, cache: cacheStatus(), }); } diff --git a/src/dependencies.ts b/src/dependencies.ts new file mode 100644 index 000000000..e9fb9956a --- /dev/null +++ b/src/dependencies.ts @@ -0,0 +1,45 @@ +export const dependencyStatusBoundMs: number = 15 * 60 * 1000; + +const dependencyOutcomes = new Map(); + +export function recordDependencyOutcome( + name: string, + success: boolean, + latencyMs?: number, + error?: string, +): void { + const now = new Date().toISOString(); + const status = success ? "healthy" : "unavailable"; + dependencyOutcomes.set(name, { status, lastChecked: now, latencyMs, error }); +} + +export function getDependencyStatus(): { + railsUsable: boolean; + dependencies: Record; + boundMs: number; +} { + const allHealthy = Array.from(dependencyOutcomes.values()).every( + (dep) => dep.status === "healthy", + ); + // With no recorded outcomes, rails are assumed usable; an observed failure + // flips this to degraded. + const railsUsable = dependencyOutcomes.size === 0 || allHealthy; + + const dependencies: Record = {}; + + for (const [name, dep] of dependencyOutcomes) { + dependencies[name] = { + status: dep.status, + lastChecked: dep.lastChecked ?? null, + latencyMs: dep.latencyMs, + error: dep.error, + }; + } + + return { railsUsable, dependencies, boundMs: dependencyStatusBoundMs }; +} \ No newline at end of file diff --git a/src/dependencyHealth.ts b/src/dependencyHealth.ts new file mode 100644 index 000000000..d3ae0ab57 --- /dev/null +++ b/src/dependencyHealth.ts @@ -0,0 +1,3 @@ +import { recordDependencyOutcome } from "./dependencies"; + +export { recordDependencyOutcome }; \ No newline at end of file diff --git a/src/linq/__tests__/budget.test.ts b/src/linq/__tests__/budget.test.ts index ef1087dec..42e3cbba1 100644 --- a/src/linq/__tests__/budget.test.ts +++ b/src/linq/__tests__/budget.test.ts @@ -9,7 +9,7 @@ * hand, so the arithmetic is asserted instead. */ -import { LinqError, retryOnProviderFailure } from "../client"; +import { LinqError, retryOnProviderFailure } from "../http"; describe("retryOnProviderFailure", () => { const serverError = () => new LinqError("Wallet generation failed", 500); diff --git a/src/linq/coinPrice.ts b/src/linq/coinPrice.ts index 684f8124a..105d3248a 100644 --- a/src/linq/coinPrice.ts +++ b/src/linq/coinPrice.ts @@ -32,7 +32,7 @@ * that is five times wrong is a loss. */ -import { LinqError } from "./client"; +import { LinqError } from "./http"; /** Circle's USDC on mainnet, identified by home domain rather than code. */ const USDC_ISSUER = "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"; diff --git a/src/linq/dependencyHealth.ts b/src/linq/dependencyHealth.ts new file mode 100644 index 000000000..605ddf7bd --- /dev/null +++ b/src/linq/dependencyHealth.ts @@ -0,0 +1,3 @@ +import { recordDependencyOutcome } from "../dependencies"; + +export { recordDependencyOutcome }; \ No newline at end of file diff --git a/src/linq/statusForClients.ts b/src/linq/statusForClients.ts index 6cbc7588c..63f299452 100644 --- a/src/linq/statusForClients.ts +++ b/src/linq/statusForClients.ts @@ -4,7 +4,7 @@ * Installed Veil APKs decide an order is finished only when its status * contains "settled", "disbursed", "failed" or "timeout". The provider also * ends orders as "refunded" (and can expire, cancel or reverse them), which - * those apps do not recognise — so a refunded order was shown as still waiting + * those apps do not recognise -- so a refunded order was shown as still waiting * for payment, with a Pay button pointing at a deposit address that no longer * exists. A user got stuck on exactly that screen. * diff --git a/src/openapi/schemas.ts b/src/openapi/schemas.ts index c7e04420c..e218505ac 100644 --- a/src/openapi/schemas.ts +++ b/src/openapi/schemas.ts @@ -210,30 +210,37 @@ export const readyzResponseSchema = z.object({ .optional(), }); +const dependencyStatusSchema = z.object({ + name: z.string(), + status: z.enum(["ok", "degraded", "down", "unknown"]), + lastCheckedAt: z.string().datetime({ offset: true }).nullable(), + lastOkAt: z.string().datetime({ offset: true }).nullable(), + latencyMs: z.number().int().nullable(), + detail: z.string().nullable(), +}); + export const statusResponseSchema = z.object({ - ok: z.literal(true), - /** The network the top-level fields describe; `networks` reports every loop. */ - network: z.enum(["testnet", "mainnet"]).optional(), lastIndexedLedger: z.number().int().nullable(), latestLedger: z.number().int().nullable(), lagLedgers: z.number().int().nullable(), startedAt: z.string().datetime({ offset: true }).optional(), uptimeSeconds: z.number().int().optional(), totalIndexed: z.number().int().optional(), - networks: z - .record( - z.string(), - z.object({ - lastIndexedLedger: z.number().int().nullable(), - latestLedger: z.number().int().nullable(), - lagLedgers: z.number().int().nullable(), - running: z.boolean(), - reason: z.string().optional(), - }).passthrough() - ) - .optional(), + networks: z.record( + z.string(), + z.object({ + lastIndexedLedger: z.number().int().nullable(), + latestLedger: z.number().int().nullable(), + lagLedgers: z.number().int().nullable(), + running: z.boolean(), + reason: z.string().optional(), + }).passthrough() + ).optional(), + status: z.enum(["healthy", "degraded"]), + dependencies: z.array(dependencyStatusSchema), }).passthrough(); + export const transferSchema = z.object({ id: z.number().int(), contractId: contractAddressSchema, @@ -796,34 +803,11 @@ export const railErrorResponseSchema = z.object({ }), }); -export const ngnCustomerStatusQuerySchema = z.object({ - customerRef: z.string().openapi({ - description: - "Our identifier for the person, stable for the life of their install. " + - "Derived from the wallet address, never a provider id.", - example: "veil_a1b2c3d4e5f6", - }), -}); - -export const ngnCustomerStatusResponseSchema = z.object({ - customerRef: z.string(), - verified: z.boolean().openapi({ - description: - "Whether the provider verifies this reference right now. Read-only: " + - "asking does not create a customer and does not spend a verification " + - "attempt, which is the reason this route exists.", - }), - status: z.string().optional(), -}); - - // ─── GET /fees ────────────────────────────────────────────────────────────── - export const feesQuerySchema = z.object({ amount: optionalQueryString( "The amount being sent, in whole units of the asset. When given and a percentage " + - "fee is configured, each fee is computed for it; without it each fee is the " + - "floor. Optional because shipped clients predate it.", + "fee is computed for it; without it each fee is the floor. Optional because shipped clients predate it.", ).openapi({ example: "25" }), }); @@ -852,3 +836,27 @@ export const feesResponseSchema = z.object({ description: "Keyed by asset code. Always charged in the asset being sent.", }), }); + +export const ngnCustomerStatusQuerySchema = z.object({ + customerRef: z.string().openapi({ + description: + "Our identifier for the person, stable for the life of their install. " + + "Derived from the wallet address, never a provider id.", + example: "veil_a1b2c3d4e5f6", + }), +}); + +export const ngnCustomerStatusResponseSchema = z.object({ + customerRef: z.string(), + verified: z.boolean().openapi({ + description: + "Whether the provider verifies this reference right now. Read-only: " + + "asking does not create a customer and does not spend a verification " + + "attempt, which is the reason this route exists.", + }), + status: z.string().optional(), +}); + + + + diff --git a/src/schema.d.ts b/src/schema.d.ts new file mode 100644 index 000000000..413c38d01 --- /dev/null +++ b/src/schema.d.ts @@ -0,0 +1,3301 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/healthz": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Liveness probe */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @enum {boolean} */ + ok: true; + uptime: number; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/readyz": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Readiness probe */ + get: { + parameters: { + query?: { + /** @description Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins. */ + network?: "testnet" | "mainnet" | null; + /** @description Max acceptable ledger lag */ + maxLag?: number | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ok: boolean; + /** @enum {string} */ + network?: "testnet" | "mainnet"; + checks: { + db: boolean; + rpc: boolean; + indexerCaughtUp: boolean; + }; + networks?: { + [key: string]: { + checks: { + db: boolean; + rpc: boolean; + indexerCaughtUp: boolean; + }; + lastIndexedLedger: number | null; + latestLedger: number | null; + lagLedgers: number | null; + }; + }; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ok: boolean; + /** @enum {string} */ + network?: "testnet" | "mainnet"; + checks: { + db: boolean; + rpc: boolean; + indexerCaughtUp: boolean; + }; + networks?: { + [key: string]: { + checks: { + db: boolean; + rpc: boolean; + indexerCaughtUp: boolean; + }; + lastIndexedLedger: number | null; + latestLedger: number | null; + lagLedgers: number | null; + }; + }; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ngn/customers/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Is this customer reference verified? + * @description Read-only check against the naira provider. Creating nothing is the point: the only previous way to ask was to provision a customer and read `verified` off the reply, which needed a full set of personal details to answer a yes/no question about a reference the caller already held. Rate-limited, because a reference is derived from a wallet address and an unthrottled answer would be an oracle. + */ + get: { + parameters: { + query: { + /** @description Our identifier for the person, stable for the life of their install. Derived from the wallet address, never a provider id. */ + customerRef: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + customerRef: string; + /** @description Whether the provider verifies this reference right now. Read-only: asking does not create a customer and does not spend a verification attempt, which is the reason this route exists. */ + verified: boolean; + status?: string; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/metrics": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Prometheus metrics + * @description Indexer and process metrics in Prometheus text exposition format. Served from in-process counters only — no database or RPC call — so it keeps answering while the subsystems it reports on are down. + */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Prometheus text exposition format */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/plain": string; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Indexer status */ + get: { + parameters: { + query?: { + /** @description Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins. */ + network?: "testnet" | "mainnet" | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + lastIndexedLedger: number | null; + latestLedger: number | null; + lagLedgers: number | null; + /** Format: date-time */ + startedAt?: string; + uptimeSeconds?: number; + totalIndexed?: number; + networks?: { + [key: string]: { + lastIndexedLedger: number | null; + latestLedger: number | null; + lagLedgers: number | null; + running: boolean; + reason?: string; + } & { + [key: string]: unknown; + }; + }; + /** @enum {string} */ + status: "healthy" | "degraded"; + dependencies: { + name: string; + /** @enum {string} */ + status: "ok" | "degraded" | "down" | "unknown"; + /** Format: date-time */ + lastCheckedAt: string | null; + /** Format: date-time */ + lastOkAt: string | null; + latencyMs: number | null; + detail: string | null; + }[]; + } & { + [key: string]: unknown; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/transfers/incoming/{address}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Incoming transfers */ + get: { + parameters: { + query?: { + /** @description Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins. */ + network?: "testnet" | "mainnet" | null; + /** @description Token contract ID to filter by */ + contractId?: string | null; + token?: string; + fromLedger?: number | null; + toLedger?: number | null; + /** @description Inclusive lower bound on ledgerClosedAt */ + fromDate?: string | null; + /** @description Inclusive upper bound on ledgerClosedAt */ + toDate?: string | null; + /** @description Comma-separated list of event types */ + eventType?: string[] | null; + /** @description Page size */ + limit?: number | null; + /** @description Pagination offset */ + offset?: number | null; + /** @description Opaque pagination cursor */ + cursor?: string | null; + /** @description OData filter expression */ + $filter?: string | null; + /** @description Comma-separated field list */ + $select?: string[] | null; + /** @description Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate */ + includeTotal?: boolean | null; + }; + header?: never; + path: { + address: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Exact match count. Only present when the request set includeTotal=true */ + total?: number; + /** @description True when another page exists */ + hasMore: boolean; + limit: number; + offset: number; + nextCursor?: string | null; + transfers: { + id: number; + /** @example CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B */ + contractId: string; + /** @enum {string} */ + eventType: "transfer" | "mint" | "burn" | "clawback"; + /** @example GABCDEFGHIJKLMNOPQRSTUVWXYZ */ + fromAddress: string | null; + /** @example GABCDEFGHIJKLMNOPQRSTUVWXYZ */ + toAddress: string | null; + amount: string; + displayAmount: string; + ledger: number; + /** Format: date-time */ + ledgerClosedAt: string; + txHash: string; + eventId: string; + /** @enum {string} */ + direction?: "incoming" | "outgoing"; + }[]; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/transfers/outgoing/{address}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Outgoing transfers */ + get: { + parameters: { + query?: { + /** @description Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins. */ + network?: "testnet" | "mainnet" | null; + /** @description Token contract ID to filter by */ + contractId?: string | null; + token?: string; + fromLedger?: number | null; + toLedger?: number | null; + /** @description Inclusive lower bound on ledgerClosedAt */ + fromDate?: string | null; + /** @description Inclusive upper bound on ledgerClosedAt */ + toDate?: string | null; + /** @description Comma-separated list of event types */ + eventType?: string[] | null; + /** @description Page size */ + limit?: number | null; + /** @description Pagination offset */ + offset?: number | null; + /** @description Opaque pagination cursor */ + cursor?: string | null; + /** @description OData filter expression */ + $filter?: string | null; + /** @description Comma-separated field list */ + $select?: string[] | null; + /** @description Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate */ + includeTotal?: boolean | null; + }; + header?: never; + path: { + address: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Exact match count. Only present when the request set includeTotal=true */ + total?: number; + /** @description True when another page exists */ + hasMore: boolean; + limit: number; + offset: number; + nextCursor?: string | null; + transfers: { + id: number; + /** @example CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B */ + contractId: string; + /** @enum {string} */ + eventType: "transfer" | "mint" | "burn" | "clawback"; + /** @example GABCDEFGHIJKLMNOPQRSTUVWXYZ */ + fromAddress: string | null; + /** @example GABCDEFGHIJKLMNOPQRSTUVWXYZ */ + toAddress: string | null; + amount: string; + displayAmount: string; + ledger: number; + /** Format: date-time */ + ledgerClosedAt: string; + txHash: string; + eventId: string; + /** @enum {string} */ + direction?: "incoming" | "outgoing"; + }[]; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/transfers/address/{address}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** All transfers for address */ + get: { + parameters: { + query?: { + /** @description Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins. */ + network?: "testnet" | "mainnet" | null; + /** @description Token contract ID to filter by */ + contractId?: string | null; + token?: string; + fromLedger?: number | null; + toLedger?: number | null; + /** @description Inclusive lower bound on ledgerClosedAt */ + fromDate?: string | null; + /** @description Inclusive upper bound on ledgerClosedAt */ + toDate?: string | null; + /** @description Comma-separated list of event types */ + eventType?: string[] | null; + /** @description Page size */ + limit?: number | null; + /** @description Pagination offset */ + offset?: number | null; + /** @description Opaque pagination cursor */ + cursor?: string | null; + /** @description OData filter expression */ + $filter?: string | null; + /** @description Comma-separated field list */ + $select?: string[] | null; + /** @description Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate */ + includeTotal?: boolean | null; + }; + header?: never; + path: { + address: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Exact match count. Only present when the request set includeTotal=true */ + total?: number; + /** @description True when another page exists */ + hasMore: boolean; + limit: number; + offset: number; + nextCursor?: string | null; + transfers: { + id: number; + /** @example CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B */ + contractId: string; + /** @enum {string} */ + eventType: "transfer" | "mint" | "burn" | "clawback"; + /** @example GABCDEFGHIJKLMNOPQRSTUVWXYZ */ + fromAddress: string | null; + /** @example GABCDEFGHIJKLMNOPQRSTUVWXYZ */ + toAddress: string | null; + amount: string; + displayAmount: string; + ledger: number; + /** Format: date-time */ + ledgerClosedAt: string; + txHash: string; + eventId: string; + /** @enum {string} */ + direction?: "incoming" | "outgoing"; + }[]; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/transfers/address/{address}/export.csv": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Export transfers to CSV */ + get: { + parameters: { + query?: { + /** @description Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins. */ + network?: "testnet" | "mainnet" | null; + /** @description Token contract ID to filter by */ + contractId?: string | null; + token?: string; + fromLedger?: number | null; + toLedger?: number | null; + /** @description Inclusive lower bound on ledgerClosedAt */ + fromDate?: string | null; + /** @description Inclusive upper bound on ledgerClosedAt */ + toDate?: string | null; + /** @description Comma-separated list of event types */ + eventType?: string[] | null; + /** @description Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate */ + includeTotal?: boolean | null; + }; + header?: never; + path: { + address: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description CSV export */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/csv": string; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/transfers/tx/{txHash}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Transfers by transaction */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Transaction hash */ + txHash: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + transfers: { + id: number; + /** @example CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B */ + contractId: string; + /** @enum {string} */ + eventType: "transfer" | "mint" | "burn" | "clawback"; + /** @example GABCDEFGHIJKLMNOPQRSTUVWXYZ */ + fromAddress: string | null; + /** @example GABCDEFGHIJKLMNOPQRSTUVWXYZ */ + toAddress: string | null; + amount: string; + displayAmount: string; + ledger: number; + /** Format: date-time */ + ledgerClosedAt: string; + txHash: string; + eventId: string; + /** @enum {string} */ + direction?: "incoming" | "outgoing"; + }[]; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/summary/{address}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Token summary */ + get: { + parameters: { + query?: { + /** @description Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins. */ + network?: "testnet" | "mainnet" | null; + /** @description Token contract ID to filter by */ + contractId?: string | null; + /** @description Inclusive lower bound on ledgerClosedAt */ + fromDate?: string | null; + /** @description Inclusive upper bound on ledgerClosedAt */ + toDate?: string | null; + }; + header?: never; + path: { + address: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example GABCDEFGHIJKLMNOPQRSTUVWXYZ */ + address: string; + window: { + /** + * Format: date-time + * @example 2025-01-01T00:00:00Z + */ + fromDate: string | null; + /** + * Format: date-time + * @example 2025-01-01T00:00:00Z + */ + toDate: string | null; + }; + tokens: { + /** @example CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B */ + contractId: string; + totalReceived: string; + totalSent: string; + netFlow: string; + displayTotalReceived: string; + displayTotalSent: string; + displayNetFlow: string; + txCount: number; + }[]; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/accounts/{address}/summary": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Token summary */ + get: { + parameters: { + query?: { + /** @description Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins. */ + network?: "testnet" | "mainnet" | null; + /** @description Token contract ID to filter by */ + contractId?: string | null; + /** @description Inclusive lower bound on ledgerClosedAt */ + fromDate?: string | null; + /** @description Inclusive upper bound on ledgerClosedAt */ + toDate?: string | null; + }; + header?: never; + path: { + address: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example GABCDEFGHIJKLMNOPQRSTUVWXYZ */ + address: string; + window: { + /** + * Format: date-time + * @example 2025-01-01T00:00:00Z + */ + fromDate: string | null; + /** + * Format: date-time + * @example 2025-01-01T00:00:00Z + */ + toDate: string | null; + }; + tokens: { + /** @example CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B */ + contractId: string; + totalReceived: string; + totalSent: string; + netFlow: string; + displayTotalReceived: string; + displayTotalSent: string; + displayNetFlow: string; + txCount: number; + }[]; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/accounts/{address}/balance": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Derived token balances + * @description Per-token balance for an address, derived by summing indexed transfers — not a read from chain. Excludes any history before the indexer's start ledger, so an address that held a token before then reads low. + */ + get: { + parameters: { + query?: never; + header?: never; + path: { + address: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example GABCDEFGHIJKLMNOPQRSTUVWXYZ */ + address: string; + /** @enum {string} */ + network: "testnet" | "mainnet"; + balances: { + /** @example CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B */ + contractId: string; + balance: string; + displayBalance: string; + }[]; + /** @enum {boolean} */ + derivedFromLedger: true; + note: string; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/accounts/{address}/transfers": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Account transfers */ + get: { + parameters: { + query?: { + /** @description Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins. */ + network?: "testnet" | "mainnet" | null; + /** @description Token contract ID to filter by */ + contractId?: string | null; + token?: string; + fromLedger?: number | null; + toLedger?: number | null; + /** @description Inclusive lower bound on ledgerClosedAt */ + fromDate?: string | null; + /** @description Inclusive upper bound on ledgerClosedAt */ + toDate?: string | null; + /** @description Comma-separated list of event types */ + eventType?: string[] | null; + /** @description Page size */ + limit?: number | null; + /** @description Pagination offset */ + offset?: number | null; + /** @description Opaque pagination cursor */ + cursor?: string | null; + /** @description OData filter expression */ + $filter?: string | null; + /** @description Comma-separated field list */ + $select?: string[] | null; + /** @description Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate */ + includeTotal?: boolean | null; + }; + header?: never; + path: { + address: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Exact match count. Only present when the request set includeTotal=true */ + total?: number; + /** @description True when another page exists */ + hasMore: boolean; + limit: number; + offset: number; + nextCursor?: string | null; + transfers: { + id: number; + /** @example CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B */ + contractId: string; + /** @enum {string} */ + eventType: "transfer" | "mint" | "burn" | "clawback"; + /** @example GABCDEFGHIJKLMNOPQRSTUVWXYZ */ + fromAddress: string | null; + /** @example GABCDEFGHIJKLMNOPQRSTUVWXYZ */ + toAddress: string | null; + amount: string; + displayAmount: string; + ledger: number; + /** Format: date-time */ + ledgerClosedAt: string; + txHash: string; + eventId: string; + /** @enum {string} */ + direction?: "incoming" | "outgoing"; + }[]; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/webhooks": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List webhook subscriptions */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + subscriptions: { + id: number; + url: string; + filter: { + contract?: string; + from?: string; + to?: string; + min_amount?: string; + } | null; + active: boolean; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + updatedAt: string; + }[]; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + /** Create a webhook subscription */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** Format: uri */ + url: string; + secret: string; + filter?: { + contract?: string; + from?: string; + to?: string; + min_amount?: string; + } | null; + active?: boolean; + }; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + id: number; + url: string; + filter: { + contract?: string; + from?: string; + to?: string; + min_amount?: string; + } | null; + active: boolean; + /** Format: date-time */ + createdAt: string; + signatureVerification: { + /** @enum {string} */ + header: "X-Wraith-Signature"; + /** @enum {string} */ + algorithm: "sha256"; + format: string; + body: string; + example: string; + safeCompare: string; + }; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/webhooks/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Delete a webhook subscription */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: number | null; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @enum {boolean} */ + ok: true; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/webhooks/{id}/deliveries": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Webhook delivery log */ + get: { + parameters: { + query?: { + status?: "pending" | "success" | "failed"; + /** @description Page size */ + limit?: number | null; + /** @description Pagination offset */ + offset?: number | null; + }; + header?: never; + path: { + id: number | null; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + total: number; + limit: number; + offset: number; + deliveries: { + id: number; + eventId: string; + status: string; + attempts: number; + lastStatusCode: number | null; + lastError: string | null; + /** Format: date-time */ + nextRetryAt: string | null; + /** Format: date-time */ + deliveredAt: string | null; + /** Format: date-time */ + createdAt: string; + }[]; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/host-fn/{contractId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Host function logs */ + get: { + parameters: { + query?: { + /** @description Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins. */ + network?: "testnet" | "mainnet" | null; + /** @description Host function name to filter by */ + functionName?: string | null; + /** @description Page size */ + limit?: number | null; + /** @description Pagination offset */ + offset?: number | null; + }; + header?: never; + path: { + contractId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B */ + contractId: string; + total: number; + limit: number; + offset: number; + logs: { + /** @example CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B */ + contractId: string; + functionName: string; + args?: unknown; + result?: unknown; + gasUsed: string | null; + ledger: number; + /** Format: date-time */ + ledgerClosedAt: string; + txHash: string; + eventId: string; + }[]; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/nfts/transfers": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** NFT transfers */ + get: { + parameters: { + query?: { + /** @description Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins. */ + network?: "testnet" | "mainnet" | null; + /** @description NFT contract ID to filter by */ + contract?: string | null; + /** @description NFT token identifier */ + token_id?: string | null; + /** @description Filter by sender or recipient address */ + address?: string | null; + fromLedger?: number | null; + toLedger?: number | null; + /** @description Page size */ + limit?: number | null; + /** @description Pagination offset */ + offset?: number | null; + /** @description Opaque pagination cursor */ + cursor?: string | null; + /** @description OData filter expression */ + $filter?: string | null; + /** @description Comma-separated field list */ + $select?: string[] | null; + /** @description Also return an exact `total`. Runs a COUNT over every matching row, so it is markedly slower on large result sets; omit it and use `hasMore` / `nextCursor` to paginate */ + includeTotal?: boolean | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description Exact match count. Only present when the request set includeTotal=true */ + total?: number; + /** @description True when another page exists */ + hasMore: boolean; + limit: number; + offset: number; + nextCursor?: string | null; + transfers: { + id: number; + /** @example CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B */ + contractId: string; + tokenId: string; + /** @example GABCDEFGHIJKLMNOPQRSTUVWXYZ */ + fromAddress: string | null; + /** @example GABCDEFGHIJKLMNOPQRSTUVWXYZ */ + toAddress: string | null; + ledger: number; + /** Format: date-time */ + ledgerClosedAt: string; + txHash: string; + eventId: string; + /** Format: date-time */ + createdAt: string; + }[]; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/nfts/owners/{contract}/{token_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** NFT owner lookup */ + get: { + parameters: { + query?: never; + header?: never; + path: { + contract: string; + token_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B */ + contract: string; + token_id: string; + /** @example GABCDEFGHIJKLMNOPQRSTUVWXYZ */ + owner: string; + metadata: { + name: string | null; + tokenUri: string | null; + } | null; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/assets/popular": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Popular assets */ + get: { + parameters: { + query?: { + /** @description Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins. */ + network?: "testnet" | "mainnet" | null; + window?: "1h" | "24h" | "7d"; + by?: "transfers" | "volume"; + /** @description Page size */ + limit?: number | null; + /** @description Pagination offset */ + offset?: number | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @enum {string} */ + window: "1h" | "24h" | "7d"; + /** @enum {string} */ + by: "transfers" | "volume"; + limit: number; + offset: number; + total: number; + assets: { + /** @example CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B */ + contractId: string; + transferCount: number; + volume: string; + displayVolume: string; + }[]; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Fuzzy search across accounts, assets, and contracts */ + get: { + parameters: { + query: { + /** @description Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins. */ + network?: "testnet" | "mainnet" | null; + q: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + query: string; + count: number; + results: { + /** @enum {string} */ + type: "account" | "asset" | "contract"; + value: string; + isSac?: boolean; + lastActivityAt?: string; + }[]; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/tokens": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Cached token metadata + * @description All tokens the indexer has encountered and cached, on the selected network. Served from the in-memory token cache — a token appears here once the indexer has seen a transfer of it and resolved its symbol, name and decimals. + */ + get: { + parameters: { + query?: { + /** @description Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins. */ + network?: "testnet" | "mainnet" | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @enum {boolean} */ + ok: true; + /** @enum {string} */ + network: "testnet" | "mainnet"; + tokens: { + /** @enum {string} */ + network: "testnet" | "mainnet"; + /** @example CB64D3G7SM2RTH6ISYIG4P2IYYD6J2OFR6B */ + contractId: string; + symbol: string; + name: string; + decimals: number; + }[]; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/fees": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Veil platform fee + * @description What the wallet should charge on a send, and where to pay it, per asset. The fee is collected on-chain in the same transaction as the send; this only tells the client the amount, so it can change without shipping an app. Unauthenticated and served with `Cache-Control: no-store`, because a stale fee is one the user was shown but did not agree to. `enabled: false` means no fee is configured today. + */ + get: { + parameters: { + query?: { + /** @description The amount being sent, in whole units of the asset. When given and a percentage fee is computed for it; without it each fee is the floor. Optional because shipped clients predate it. */ + amount?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description False when no fee is configured, so a client can tell a deliberate waiver from a backend too old to know about fees. */ + enabled: boolean; + /** @description Keyed by asset code. Always charged in the asset being sent. */ + fees: { + [key: string]: { + /** + * @description What to charge, in whole units of the fee asset, as a decimal string. + * @example 0.25 + */ + amount: string; + /** @example USDC */ + assetCode: string; + /** @description Null for native XLM. */ + assetIssuer: string | null; + /** @description The classic account the fee is paid to. */ + destination: string; + /** @description Percent of the sent amount, when the fee scales. Absent when it is flat. */ + percent?: string; + /** @description Floor; present only alongside percent. */ + min?: string; + /** @description Cap; present only alongside percent. */ + max?: string; + }; + }; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/transfers.csv": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Export all transfers as CSV + * @description Streams every transfer matching the filters as CSV. No pagination: the response is the entire matching set, so narrow it with the ledger or date bounds on large deployments. + */ + get: { + parameters: { + query?: { + /** @description Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins. */ + network?: "testnet" | "mainnet" | null; + address?: string; + /** @description Token contract ID to filter by */ + contractId?: string | null; + token?: string; + fromLedger?: number | null; + toLedger?: number | null; + /** @description Inclusive lower bound on ledgerClosedAt */ + fromDate?: string | null; + /** @description Inclusive upper bound on ledgerClosedAt */ + toDate?: string | null; + /** @description Comma-separated list of event types */ + eventType?: string[] | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description File download of the full matching transfer set (no pagination). */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/csv": string; + "application/octet-stream": string; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/transfers.parquet": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Export all transfers as Parquet + * @description Streams every transfer matching the filters as an Apache Parquet file. Same filters and streaming behaviour as /transfers.csv. + */ + get: { + parameters: { + query?: { + /** @description Network to read from. Defaults to the deployment's configured network. May also be sent as the X-Network header; the query parameter wins. */ + network?: "testnet" | "mainnet" | null; + address?: string; + /** @description Token contract ID to filter by */ + contractId?: string | null; + token?: string; + fromLedger?: number | null; + toLedger?: number | null; + /** @description Inclusive lower bound on ledgerClosedAt */ + fromDate?: string | null; + /** @description Inclusive upper bound on ledgerClosedAt */ + toDate?: string | null; + /** @description Comma-separated list of event types */ + eventType?: string[] | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description File download of the full matching transfer set (no pagination). */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/csv": string; + "application/octet-stream": string; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/webhooks/linq": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Payout webhook (provider callback) + * @description Internal endpoint. Not part of the public data API: (provider callback / wallet-only cash-out surface served on behalf of the Veil wallet client). + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Acknowledged */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @enum {boolean} */ + ok: true; + }; + }; + }; + /** @description Unrecognised event */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Invalid signature */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/offramp/availability": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Is cash-out enabled on this deployment + * @description Internal endpoint. Not part of the public data API: (provider callback / wallet-only cash-out surface served on behalf of the Veil wallet client). Answers the availability question without pricing anything: it makes no provider call, so it costs none of the shared rate allowance. + */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @enum {boolean} */ + available: true; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Offramp not configured on this deployment */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/offramp/rate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Indicative naira-per-coin rate + * @description Internal endpoint. Not part of the public data API: (provider callback / wallet-only cash-out surface served on behalf of the Veil wallet client). + */ + get: { + parameters: { + query?: { + /** @description Asset to price. Send it: without one the answer is priced for `usdc`, which is naira per dollar and wrong for every other asset by that asset's price. Omitting it is deprecated and will be refused once the installed wallet builds ask for a coin; a response always names the coin it was priced for. */ + coin?: "xlm" | "usdc"; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** + * @description The asset `rate` is quoted per unit of. Always present, including for a request that named none — which is priced as `usdc`. + * @enum {string} + */ + coin: "xlm" | "usdc"; + /** @description Naira per one unit of the requested coin, not naira per dollar. */ + rate: number; + /** @enum {boolean} */ + indicative: true; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Payout provider error */ + 502: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Offramp not configured on this deployment */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/offramp/verify-bank": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Verify a bank account before creating an order + * @description Internal endpoint. Not part of the public data API: (provider callback / wallet-only cash-out surface served on behalf of the Veil wallet client). + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + bankCode: string; + accountNumber: string; + } & { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + accountName: string; + bankName: string; + accountNumber: string; + bankCode: string; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Payout provider error */ + 502: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Offramp not configured on this deployment */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/offramp/trustline": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Check a refund address holds a USDC trustline + * @description Internal endpoint. Not part of the public data API: (provider callback / wallet-only cash-out surface served on behalf of the Veil wallet client). + */ + get: { + parameters: { + query: { + address: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + address: string; + valid: boolean; + trustsUSDC: boolean; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Payout provider error */ + 502: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Offramp not configured on this deployment */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/offramp/orders": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create a cash-out order + * @description Internal endpoint. Not part of the public data API: (provider callback / wallet-only cash-out surface served on behalf of the Veil wallet client). + */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + amountNGN?: number; + amountStableCoin?: number; + bankAccount: string; + bankCode: string; + bankName: string; + accountName: string; + refundAddress?: string; + walletAddress: string; + idempotencyKey: string; + } & { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Replayed existing order (same idempotencyKey) */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + id: string; + /** @description Deposit address the stablecoin is sent to. */ + walletAddress: string; + chain: string; + coin: string; + amountStableCoin: number; + amountNGN: number; + rate: number; + /** + * @description Client-facing status mapped from the provider's raw one. + * @enum {string} + */ + status: "pending" | "waiting_for_deposit" | "processing" | "completed" | "failed" | "expired"; + /** @description Raw provider status, when the order is not a replay. */ + providerStatus?: string; + replayed?: boolean; + }; + }; + }; + /** @description Order created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + id: string; + /** @description Deposit address the stablecoin is sent to. */ + walletAddress: string; + chain: string; + coin: string; + amountStableCoin: number; + amountNGN: number; + rate: number; + /** + * @description Client-facing status mapped from the provider's raw one. + * @enum {string} + */ + status: "pending" | "waiting_for_deposit" | "processing" | "completed" | "failed" | "expired"; + /** @description Raw provider status, when the order is not a replay. */ + providerStatus?: string; + replayed?: boolean; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Payout provider error */ + 502: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Offramp not configured on this deployment */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/offramp/orders/{orderId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get the status of a cash-out order + * @description Internal endpoint. Not part of the public data API: (provider callback / wallet-only cash-out surface served on behalf of the Veil wallet client). + */ + get: { + parameters: { + query?: never; + header?: never; + path: { + orderId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + id: string; + /** + * @description Client-facing status mapped from the provider's raw one. + * @enum {string} + */ + status: "pending" | "waiting_for_deposit" | "processing" | "completed" | "failed" | "expired"; + providerStatus: string; + amountStableCoin: number; + amountNGN: number; + depositAddress: string; + rate: number; + /** Format: date-time */ + createdAt: string; + /** @enum {string} */ + source: "live" | "cache"; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Payout provider error */ + 502: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + /** @description Offramp not configured on this deployment */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: never; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export type operations = Record;