From 454c92ef91dc2984941df0480e4edfdb8f83a766 Mon Sep 17 00:00:00 2001 From: Nwapu Trust Jah <152970136+Nwapu-TrustJah@users.noreply.github.com> Date: Fri, 16 Oct 2026 10:33:46 +0100 Subject: [PATCH 01/11] fix: `/status` is healthy while the thing the money depends on is (#241) --- openapi.json | 72 +++++++ src/__tests__/linq/statusForClients.test.ts | 2 +- src/api.ts | 6 + src/linq/__tests__/budget.test.ts | 2 +- src/linq/client.ts | 196 -------------------- src/linq/coinPrice.ts | 2 +- src/linq/statusForClients.ts | 2 +- src/openapi/schemas.ts | 41 ++-- 8 files changed, 106 insertions(+), 217 deletions(-) delete mode 100644 src/linq/client.ts diff --git a/openapi.json b/openapi.json index ed1cb141d..617fd187a 100644 --- a/openapi.json +++ b/openapi.json @@ -500,6 +500,7 @@ "/status": { "get": { "summary": "Indexer status", + "description": "Reports whether the service itself is up and whether the rails the product depends on are usable. The `dependencies` section reports the last observed outcome of each provider endpoint the product needs, with the time it was observed. A failing dependency does not make the service unhealthy — it is reported as `degraded`, because the indexer keeps indexing and the API keeps answering while the rails are down. The `dependencies` section is refreshed on a bounded interval: at most `dependencyStalenessSeconds` (see `dependencies.maxAgeSeconds`) may pass before a stale entry is reported as `unknown`.", "parameters": [ { "schema": { @@ -533,6 +534,14 @@ true ] }, + "status": { + "type": "string", + "enum": [ + "healthy", + "degraded" + ], + "description": "`healthy` when every dependency the product needs is usable; `degraded` when the service is up but one or more dependencies are failing or unknown. A failing dependency never makes the service unhealthy — the indexer and the API keep running." + }, "network": { "type": "string", "enum": [ @@ -598,6 +607,69 @@ } } }, + "dependencies": { + "type": "object", + "description": "Last observed outcome of each provider endpoint the product needs. Nothing here is probed on request: entries are recorded by real calls and by a cheap, side-effect-free poll of the unauthenticated rate endpoint. A KYC probe is deliberately not invented, because a KYC call moves real personal data.", + "properties": { + "maxAgeSeconds": { + "type": "integer", + "description": "Bound on staleness. An entry older than this is reported as `unknown` rather than as its last known outcome." + }, + "checks": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Provider endpoint the product depends on." + }, + "ok": { + "type": "boolean", + "nullable": true, + "description": "Last observed outcome. `null` when nothing has been observed within `maxAgeSeconds`." + }, + "status": { + "type": "string", + "enum": [ + "up", + "down", + "unknown" + ], + "description": "`up` when the last observation succeeded, `down` when it failed, `unknown` when the last observation is older than `maxAgeSeconds` or has never happened." + }, + "observedAt": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "When the outcome was observed. `null` when never observed." + }, + "ageSeconds": { + "type": "integer", + "nullable": true, + "description": "Seconds since `observedAt`. `null` when never observed." + }, + "detail": { + "type": "string", + "nullable": true, + "description": "Short, non-sensitive reason for the last outcome (for example an HTTP status or a transport error). Never contains personal data." + } + }, + "required": [ + "name", + "ok", + "status", + "observedAt", + "ageSeconds" + ] + } + } + }, + "required": [ + "maxAgeSeconds", + "checks" + ] + } "required": [ "ok", "lastIndexedLedger", diff --git a/src/__tests__/linq/statusForClients.test.ts b/src/__tests__/linq/statusForClients.test.ts index a4f5b0716..539821bb8 100644 --- a/src/__tests__/linq/statusForClients.test.ts +++ b/src/__tests__/linq/statusForClients.test.ts @@ -1,4 +1,4 @@ -import { statusForClients } from "../../linq/statusForClients"; +import { statusForClients } from "../../link/statusForClients"; // What installed Veil apps treat as a finished order. const legacyIsTerminal = (status: string) => diff --git a/src/api.ts b/src/api.ts index 438566146..334d4e451 100644 --- a/src/api.ts +++ b/src/api.ts @@ -32,6 +32,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"; // ─── RPC Health Check Cache ─────────────────────────────────────────────── // Keyed by network (#163): one cache entry would let a healthy testnet RPC @@ -464,6 +465,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 @@ -519,6 +521,8 @@ export function createApp(): express.Application { lagLedgers: latestLedger - (lastIndexedLedger ?? latestLedger), ...stats, networks: byNetwork, + dependencies, + dependency_status_bound_ms: dependencyStatusBoundMs, }); } else { res.setHeader("X-Data-Stale", "true"); @@ -540,6 +544,8 @@ export function createApp(): express.Application { // usually true of one chain only, and the top-level nulls above // cannot say which. networks: byNetwork, + dependencies, + dependency_status_bound_ms: dependencyStatusBoundMs, }); } } catch (err) { 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/client.ts b/src/linq/client.ts deleted file mode 100644 index 201fe9434..000000000 --- a/src/linq/client.ts +++ /dev/null @@ -1,196 +0,0 @@ -/** - * Server-side client for Linq's B2B offramp API. - * - * The API key creates orders that pay real naira to real bank accounts, so it - * lives here and never leaves the backend. A mobile build cannot hold it: - * anything in an APK is extractable, and this one is not rate-limited - * read-only access — it is the ability to spend. - * - * Every call is narrow and returns typed data or throws {@link LinqError} with - * Linq's own message, because their 400s explain the actual problem (bad bank - * code, unsupported coin) far better than anything we would invent. - */ - -import { LinqError, linqCall, retryOnProviderFailure, TOTAL_BUDGET_MS, type LinqCallOptions } from "./http"; - -// Re-exported so every existing import of these from `./client` keeps working. -export { LinqError, retryOnProviderFailure } from "./http"; - -/** - * A provider 429 is retried this many times before it reaches the user. - * - * All Veil users share one provider key, and the provider limits it. A user - * creating a cash-out got "rate limit exceeded, please wait" and succeeded on - * a second tap: the limit had cleared within a second or two, so the app should - * have waited, not the user. A 429 means the request was refused before any - * work was done, so repeating it (order creation included, with the same - * idempotencyKey) cannot create anything twice. - */ -const RATE_LIMIT_RETRIES = 2; - -/** Every offramp request: the shared client, with this rail's 429 retry. */ -function call(path: string, options: LinqCallOptions = {}): Promise { - return linqCall(path, { rateLimitRetries: RATE_LIMIT_RETRIES, ...options }); -} - -// ─── Rate ──────────────────────────────────────────────────────────────────── - -export interface LinqRate { - rate: number; - currency: string; - coin: string; -} - -/** - * Indicative NGN rate. Unauthenticated, and explicitly for display only — the - * binding rate is the one locked into an order at creation. - */ -export function getRate(): Promise { - return call("/b2b/rate", { auth: false }); -} - -// ─── Bank verification ─────────────────────────────────────────────────────── - -export interface VerifiedBank { - accountName: string; - bankName: string; - accountNumber: string; - bankCode: string; -} - -/** - * Resolve an account number to the name the bank holds for it. - * - * Always called before creating an order: a wrong account name is a failed - * payout after the USDC has already been sent, and the failure surfaces - * minutes later in a webhook rather than while the user is still on screen. - */ -export function verifyBank(bankCode: string, accountNumber: string): Promise { - return call("/b2b/verifybank", { - method: "POST", - body: { bankCode, accountNumber }, - }); -} - -// ─── Stellar trustline ─────────────────────────────────────────────────────── - -export interface TrustlineCheck { - address: string; - valid: boolean; - trustsUSDC: boolean; -} - -/** - * Whether a Stellar account can receive USDC, for validating a refund address. - * - * Linq rejects muxed (M…) and CONTRACT (C…) addresses outright, which matters - * here specifically: a Veil wallet is a contract account, so the refund address - * must be the classic fee-payer, never the wallet address the user sees. - * - * A 503 means Horizon was unreachable — the answer is unknown, not "no". Linq's - * own docs are explicit that rejecting on 503 turns a Stellar outage into a - * failed sell, so that case resolves to "assume it can" and is checked again - * before any refund is actually paid. - */ -export async function checkStellarTrustline(address: string): Promise { - try { - return await call( - `/b2b/stellar/trustline?address=${encodeURIComponent(address)}`, - ); - } catch (err) { - if (err instanceof LinqError && err.status === 503) { - return { address, valid: true, trustsUSDC: true }; - } - throw err; - } -} - -// ─── Orders ────────────────────────────────────────────────────────────────── - -export interface CreateOfframpParams { - /** Exactly one of these two. */ - amountNGN?: number; - amountStableCoin?: number; - bankAccount: string; - bankCode: string; - bankName: string; - accountName: string; - /** Must already hold a USDC trustline. On Veil this is the fee-payer G-account. */ - refundAddress?: string; - customerRef?: string; - idempotencyKey: string; -} - -export interface OfframpOrderResponse { - id: string; - walletAddress: string; - coinType: string; - coin: string; - chain: string; - amountStableCoin: number; - amountNGN: number; - rate: number; - currency: string; - status: string; - depositDigest?: string; -} - -/** - * Create a Stellar USDC → NGN order. - * - * `chain: "stellar"` is sent explicitly and never inferred. Linq's default is - * Sui, and `coin` does not select the chain — omitting it mints a Sui deposit - * wallet even when the coin says USDC, and their docs warn that funds sent to - * an address on the wrong chain are unrecoverable. - * - * `manualDeposit: true` because the payout should follow what actually - * arrives. With the default the full locked NGN is paid out even if less USDC - * turns up, and the shortfall is ours — a bad trade for a wallet that is not - * taking FX risk, and the loss would land on whoever operates the Linq account - * rather than the user who mistyped. - */ -export async function createOfframpOrder( - params: CreateOfframpParams, -): Promise { - const { amountNGN, amountStableCoin } = params; - // async, so this surfaces as a rejection rather than a synchronous throw — - // a caller awaiting the promise should not also need a try/catch around the - // call itself. - if ((amountNGN == null) === (amountStableCoin == null)) { - throw new LinqError( - "Provide exactly one of amountNGN or amountStableCoin", - 400, - ); - } - // One deadline for the attempt AND its retry, so the two budgets add up to - // TOTAL_BUDGET_MS rather than multiplying. - const deadline = Date.now() + TOTAL_BUDGET_MS; - const request = () => - call("/b2b/offramp", { - method: "POST", - deadline, - body: { - ...params, - chain: "stellar", - coin: "usdc", - currency: "NGN", - manualDeposit: true, - }, - }); - return retryOnProviderFailure(request, 1_500, deadline); -} - -export interface OfframpStatus { - id: string; - status: string; - amountStableCoin: number; - amountNGN: number; - currency: string; - created: string; - updated: string; -} - -/** Poll an order. The settled amounts here are authoritative, not the request. */ -export function getOfframpStatus(orderId: string): Promise { - return call(`/b2b/status?id=${encodeURIComponent(orderId)}`); -} 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/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 d3416f5f8..277ba90f3 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, @@ -773,8 +780,8 @@ export const ngnCustomerStatusResponseSchema = z.object({ 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 configured, each fee is computed for it; without it each fee is the " + + "floor. Optional because shipped clients predate it.", ).openapi({ example: "25" }), }); From d0bedaf0a21aae245b2be0c383f5f801da964ddd Mon Sep 17 00:00:00 2001 From: Nwapu Trust-Jah Chikum Date: Sun, 18 Oct 2026 17:04:13 +0100 Subject: [PATCH 02/11] fix: track dependency health and rail usability on /status endpoint (#241) --- openapi.json | 241 ++++---------------- package-lock.json | 39 ++-- src/__tests__/linq/statusForClients.test.ts | 2 +- src/api.ts | 6 +- src/dependencies.ts | 45 ++++ src/dependencyHealth.ts | 3 + src/linq/dependencyHealth.ts | 3 + 7 files changed, 118 insertions(+), 221 deletions(-) create mode 100644 src/dependencies.ts create mode 100644 src/dependencyHealth.ts create mode 100644 src/linq/dependencyHealth.ts diff --git a/openapi.json b/openapi.json index 617fd187a..273d33136 100644 --- a/openapi.json +++ b/openapi.json @@ -500,7 +500,6 @@ "/status": { "get": { "summary": "Indexer status", - "description": "Reports whether the service itself is up and whether the rails the product depends on are usable. The `dependencies` section reports the last observed outcome of each provider endpoint the product needs, with the time it was observed. A failing dependency does not make the service unhealthy — it is reported as `degraded`, because the indexer keeps indexing and the API keeps answering while the rails are down. The `dependencies` section is refreshed on a bounded interval: at most `dependencyStalenessSeconds` (see `dependencies.maxAgeSeconds`) may pass before a stale entry is reported as `unknown`.", "parameters": [ { "schema": { @@ -534,14 +533,6 @@ true ] }, - "status": { - "type": "string", - "enum": [ - "healthy", - "degraded" - ], - "description": "`healthy` when every dependency the product needs is usable; `degraded` when the service is up but one or more dependencies are failing or unknown. A failing dependency never makes the service unhealthy — the indexer and the API keep running." - }, "network": { "type": "string", "enum": [ @@ -554,12 +545,10 @@ "nullable": true }, "latestLedger": { - "type": "integer", - "nullable": true + "type": "integer" }, "lagLedgers": { - "type": "integer", - "nullable": true + "type": "integer" }, "startedAt": { "type": "string", @@ -571,114 +560,71 @@ "totalIndexed": { "type": "integer" }, - "networks": { - "type": "object", - "additionalProperties": { + "status": { + "type": "string", + "enum": [ + "healthy", + "degraded" + ] + }, + "dependencies": { + "type": "array", + "items": { "type": "object", "properties": { - "lastIndexedLedger": { - "type": "integer", - "nullable": true + "name": { + "type": "string" }, - "latestLedger": { - "type": "integer", - "nullable": true + "status": { + "type": "string", + "enum": [ + "ok", + "degraded", + "down", + "unknown" + ] }, - "lagLedgers": { + "lastCheckedAt": { + "type": "string", + "nullable": true, + "format": "date-time" + }, + "lastOkAt": { + "type": "string", + "nullable": true, + "format": "date-time" + }, + "latencyMs": { "type": "integer", "nullable": true }, - "running": { - "type": "boolean" - }, - "reason": { - "type": "string" + "detail": { + "type": "string", + "nullable": true } }, "required": [ - "lastIndexedLedger", - "latestLedger", - "lagLedgers", - "running" - ], - "additionalProperties": { - "nullable": true - } + "name", + "status", + "lastCheckedAt", + "lastOkAt", + "latencyMs", + "detail" + ] } } }, - "dependencies": { - "type": "object", - "description": "Last observed outcome of each provider endpoint the product needs. Nothing here is probed on request: entries are recorded by real calls and by a cheap, side-effect-free poll of the unauthenticated rate endpoint. A KYC probe is deliberately not invented, because a KYC call moves real personal data.", - "properties": { - "maxAgeSeconds": { - "type": "integer", - "description": "Bound on staleness. An entry older than this is reported as `unknown` rather than as its last known outcome." - }, - "checks": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Provider endpoint the product depends on." - }, - "ok": { - "type": "boolean", - "nullable": true, - "description": "Last observed outcome. `null` when nothing has been observed within `maxAgeSeconds`." - }, - "status": { - "type": "string", - "enum": [ - "up", - "down", - "unknown" - ], - "description": "`up` when the last observation succeeded, `down` when it failed, `unknown` when the last observation is older than `maxAgeSeconds` or has never happened." - }, - "observedAt": { - "type": "string", - "nullable": true, - "format": "date-time", - "description": "When the outcome was observed. `null` when never observed." - }, - "ageSeconds": { - "type": "integer", - "nullable": true, - "description": "Seconds since `observedAt`. `null` when never observed." - }, - "detail": { - "type": "string", - "nullable": true, - "description": "Short, non-sensitive reason for the last outcome (for example an HTTP status or a transport error). Never contains personal data." - } - }, - "required": [ - "name", - "ok", - "status", - "observedAt", - "ageSeconds" - ] - } - } - }, - "required": [ - "maxAgeSeconds", - "checks" - ] - } "required": [ "ok", "lastIndexedLedger", "latestLedger", - "lagLedgers" - ], - "additionalProperties": { - "nullable": true - } + "lagLedgers", + "startedAt", + "uptimeSeconds", + "totalIndexed", + "status", + "dependencies" + ] } } } @@ -4785,93 +4731,6 @@ } } }, - "/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 configured, each 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.", - "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", diff --git a/package-lock.json b/package-lock.json index de0825ce6..4187187b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -558,6 +558,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", @@ -1097,31 +1098,6 @@ "node": ">=12" } }, - "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, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "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, - "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", @@ -1129,7 +1105,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -3249,6 +3224,7 @@ "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -4280,6 +4256,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -5224,6 +5201,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 +5666,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 +6236,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 +7870,7 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/engines": "5.22.0" }, @@ -8845,6 +8826,7 @@ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9026,6 +9008,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 +9220,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9420,6 +9404,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 +9546,7 @@ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9957,6 +9943,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/src/__tests__/linq/statusForClients.test.ts b/src/__tests__/linq/statusForClients.test.ts index 539821bb8..a4f5b0716 100644 --- a/src/__tests__/linq/statusForClients.test.ts +++ b/src/__tests__/linq/statusForClients.test.ts @@ -1,4 +1,4 @@ -import { statusForClients } from "../../link/statusForClients"; +import { statusForClients } from "../../linq/statusForClients"; // What installed Veil apps treat as a finished order. const legacyIsTerminal = (status: string) => diff --git a/src/api.ts b/src/api.ts index 334d4e451..beb35a1a1 100644 --- a/src/api.ts +++ b/src/api.ts @@ -506,7 +506,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", @@ -537,8 +537,8 @@ 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 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/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 From 12e1017b41ee49635fec01fc523f6d7060bd0fbd Mon Sep 17 00:00:00 2001 From: Nwapu Trust-Jah Chikum Date: Sun, 18 Oct 2026 19:22:42 +0100 Subject: [PATCH 03/11] docs(openapi): restore fees schemas and rebuild openapi.json --- openapi.json | 150 +++++++++++++++++++++++++++++++++++------ src/openapi/schemas.ts | 49 +++++++------- 2 files changed, 155 insertions(+), 44 deletions(-) diff --git a/openapi.json b/openapi.json index 273d33136..1836d293b 100644 --- a/openapi.json +++ b/openapi.json @@ -527,28 +527,17 @@ "schema": { "type": "object", "properties": { - "ok": { - "type": "boolean", - "enum": [ - true - ] - }, - "network": { - "type": "string", - "enum": [ - "testnet", - "mainnet" - ] - }, "lastIndexedLedger": { "type": "integer", "nullable": true }, "latestLedger": { - "type": "integer" + "type": "integer", + "nullable": true }, "lagLedgers": { - "type": "integer" + "type": "integer", + "nullable": true }, "startedAt": { "type": "string", @@ -560,6 +549,41 @@ "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": [ @@ -615,16 +639,15 @@ } }, "required": [ - "ok", "lastIndexedLedger", "latestLedger", "lagLedgers", - "startedAt", - "uptimeSeconds", - "totalIndexed", "status", "dependencies" - ] + ], + "additionalProperties": { + "nullable": true + } } } } @@ -4731,6 +4754,93 @@ } } }, + "/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", diff --git a/src/openapi/schemas.ts b/src/openapi/schemas.ts index 277ba90f3..4add9404d 100644 --- a/src/openapi/schemas.ts +++ b/src/openapi/schemas.ts @@ -754,34 +754,11 @@ export const offrampErrorResponseSchema = z.object({ error: z.string(), }); -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" }), }); @@ -810,3 +787,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(), +}); + + + + From 51a146e3a4aa3885aaca773eebfdb2994dbe3643 Mon Sep 17 00:00:00 2001 From: Nwapu Trust-Jah Chikum Date: Wed, 21 Oct 2026 18:19:35 +0100 Subject: [PATCH 04/11] fix(linq): restore client.ts and update openapi spec --- src/linq/client.ts | 196 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 src/linq/client.ts diff --git a/src/linq/client.ts b/src/linq/client.ts new file mode 100644 index 000000000..201fe9434 --- /dev/null +++ b/src/linq/client.ts @@ -0,0 +1,196 @@ +/** + * Server-side client for Linq's B2B offramp API. + * + * The API key creates orders that pay real naira to real bank accounts, so it + * lives here and never leaves the backend. A mobile build cannot hold it: + * anything in an APK is extractable, and this one is not rate-limited + * read-only access — it is the ability to spend. + * + * Every call is narrow and returns typed data or throws {@link LinqError} with + * Linq's own message, because their 400s explain the actual problem (bad bank + * code, unsupported coin) far better than anything we would invent. + */ + +import { LinqError, linqCall, retryOnProviderFailure, TOTAL_BUDGET_MS, type LinqCallOptions } from "./http"; + +// Re-exported so every existing import of these from `./client` keeps working. +export { LinqError, retryOnProviderFailure } from "./http"; + +/** + * A provider 429 is retried this many times before it reaches the user. + * + * All Veil users share one provider key, and the provider limits it. A user + * creating a cash-out got "rate limit exceeded, please wait" and succeeded on + * a second tap: the limit had cleared within a second or two, so the app should + * have waited, not the user. A 429 means the request was refused before any + * work was done, so repeating it (order creation included, with the same + * idempotencyKey) cannot create anything twice. + */ +const RATE_LIMIT_RETRIES = 2; + +/** Every offramp request: the shared client, with this rail's 429 retry. */ +function call(path: string, options: LinqCallOptions = {}): Promise { + return linqCall(path, { rateLimitRetries: RATE_LIMIT_RETRIES, ...options }); +} + +// ─── Rate ──────────────────────────────────────────────────────────────────── + +export interface LinqRate { + rate: number; + currency: string; + coin: string; +} + +/** + * Indicative NGN rate. Unauthenticated, and explicitly for display only — the + * binding rate is the one locked into an order at creation. + */ +export function getRate(): Promise { + return call("/b2b/rate", { auth: false }); +} + +// ─── Bank verification ─────────────────────────────────────────────────────── + +export interface VerifiedBank { + accountName: string; + bankName: string; + accountNumber: string; + bankCode: string; +} + +/** + * Resolve an account number to the name the bank holds for it. + * + * Always called before creating an order: a wrong account name is a failed + * payout after the USDC has already been sent, and the failure surfaces + * minutes later in a webhook rather than while the user is still on screen. + */ +export function verifyBank(bankCode: string, accountNumber: string): Promise { + return call("/b2b/verifybank", { + method: "POST", + body: { bankCode, accountNumber }, + }); +} + +// ─── Stellar trustline ─────────────────────────────────────────────────────── + +export interface TrustlineCheck { + address: string; + valid: boolean; + trustsUSDC: boolean; +} + +/** + * Whether a Stellar account can receive USDC, for validating a refund address. + * + * Linq rejects muxed (M…) and CONTRACT (C…) addresses outright, which matters + * here specifically: a Veil wallet is a contract account, so the refund address + * must be the classic fee-payer, never the wallet address the user sees. + * + * A 503 means Horizon was unreachable — the answer is unknown, not "no". Linq's + * own docs are explicit that rejecting on 503 turns a Stellar outage into a + * failed sell, so that case resolves to "assume it can" and is checked again + * before any refund is actually paid. + */ +export async function checkStellarTrustline(address: string): Promise { + try { + return await call( + `/b2b/stellar/trustline?address=${encodeURIComponent(address)}`, + ); + } catch (err) { + if (err instanceof LinqError && err.status === 503) { + return { address, valid: true, trustsUSDC: true }; + } + throw err; + } +} + +// ─── Orders ────────────────────────────────────────────────────────────────── + +export interface CreateOfframpParams { + /** Exactly one of these two. */ + amountNGN?: number; + amountStableCoin?: number; + bankAccount: string; + bankCode: string; + bankName: string; + accountName: string; + /** Must already hold a USDC trustline. On Veil this is the fee-payer G-account. */ + refundAddress?: string; + customerRef?: string; + idempotencyKey: string; +} + +export interface OfframpOrderResponse { + id: string; + walletAddress: string; + coinType: string; + coin: string; + chain: string; + amountStableCoin: number; + amountNGN: number; + rate: number; + currency: string; + status: string; + depositDigest?: string; +} + +/** + * Create a Stellar USDC → NGN order. + * + * `chain: "stellar"` is sent explicitly and never inferred. Linq's default is + * Sui, and `coin` does not select the chain — omitting it mints a Sui deposit + * wallet even when the coin says USDC, and their docs warn that funds sent to + * an address on the wrong chain are unrecoverable. + * + * `manualDeposit: true` because the payout should follow what actually + * arrives. With the default the full locked NGN is paid out even if less USDC + * turns up, and the shortfall is ours — a bad trade for a wallet that is not + * taking FX risk, and the loss would land on whoever operates the Linq account + * rather than the user who mistyped. + */ +export async function createOfframpOrder( + params: CreateOfframpParams, +): Promise { + const { amountNGN, amountStableCoin } = params; + // async, so this surfaces as a rejection rather than a synchronous throw — + // a caller awaiting the promise should not also need a try/catch around the + // call itself. + if ((amountNGN == null) === (amountStableCoin == null)) { + throw new LinqError( + "Provide exactly one of amountNGN or amountStableCoin", + 400, + ); + } + // One deadline for the attempt AND its retry, so the two budgets add up to + // TOTAL_BUDGET_MS rather than multiplying. + const deadline = Date.now() + TOTAL_BUDGET_MS; + const request = () => + call("/b2b/offramp", { + method: "POST", + deadline, + body: { + ...params, + chain: "stellar", + coin: "usdc", + currency: "NGN", + manualDeposit: true, + }, + }); + return retryOnProviderFailure(request, 1_500, deadline); +} + +export interface OfframpStatus { + id: string; + status: string; + amountStableCoin: number; + amountNGN: number; + currency: string; + created: string; + updated: string; +} + +/** Poll an order. The settled amounts here are authoritative, not the request. */ +export function getOfframpStatus(orderId: string): Promise { + return call(`/b2b/status?id=${encodeURIComponent(orderId)}`); +} From f00b555248fc67fbd225785c221c3eb8e3392d0f Mon Sep 17 00:00:00 2001 From: Nwapu Trust-Jah Chikum Date: Wed, 21 Oct 2026 19:05:19 +0100 Subject: [PATCH 05/11] fix(ci): update lockfile and regenrate openapi spec --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d87d93af8..1e33dc26a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1099,9 +1099,9 @@ } }, "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==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", "dev": true, "license": "MIT", "optional": true, From cc26017b2bcc5db93a78d02d05bd0b9228a4ffa6 Mon Sep 17 00:00:00 2001 From: Nwapu Trust-Jah Chikum Date: Thu, 22 Oct 2026 06:12:18 +0100 Subject: [PATCH 06/11] fix(docs): force add docs/openapi.json and update lockfile --- docs/openapi.json | 6281 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 6281 insertions(+) create mode 100644 docs/openapi.json 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" + ] + } + } + } + } + } + } + } + } +} From 388f070df39c69238db15bce4e44c27c1fa807a3 Mon Sep 17 00:00:00 2001 From: Nwapu Trust-Jah Chikum Date: Thu, 22 Oct 2026 06:25:11 +0100 Subject: [PATCH 07/11] fix(ci): update lockfile and regenerate openapi artifacts --- package-lock.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package-lock.json b/package-lock.json index 1e33dc26a..10568c051 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5507,6 +5507,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, From e2de40eb471337944babcf6b00db3d8ee0eca254 Mon Sep 17 00:00:00 2001 From: Nwapu Trust-Jah Chikum Date: Thu, 22 Oct 2026 06:51:27 +0100 Subject: [PATCH 08/11] fix(schema): update types, lockfile, and openapi artifacts --- src/schema.d.ts | 3301 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3301 insertions(+) create mode 100644 src/schema.d.ts 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; From 7c1b3ede3b0c2c161516ad98dcb6e3fe507e7bf5 Mon Sep 17 00:00:00 2001 From: Nwapu Trust-Jah Chikum Date: Thu, 22 Oct 2026 07:05:49 +0100 Subject: [PATCH 09/11] fix(deps): regenerate package-lock.json to include all platform dependencies --- package-lock.json | 66 +++++++++-------------------------------------- 1 file changed, 12 insertions(+), 54 deletions(-) diff --git a/package-lock.json b/package-lock.json index 10568c051..9c486a62c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1098,17 +1098,6 @@ "node": ">=12" } }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", - "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", @@ -2265,22 +2254,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": { @@ -2989,9 +2981,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, @@ -3633,40 +3625,6 @@ "node": ">=14.0.0" } }, - "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", From af8149845c56ab8955a84996616b649a6eab08a6 Mon Sep 17 00:00:00 2001 From: Nwapu Trust-Jah Chikum Date: Thu, 22 Oct 2026 07:18:10 +0100 Subject: [PATCH 10/11] fix(ci): lock platform dependencies and sync openapi artifacts --- package-lock.json | 67 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 5 +++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 9c486a62c..f887569a4 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", @@ -1098,6 +1101,36 @@ "node": ">=12" } }, + "node_modules/@emnapi/core": { + "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", + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", @@ -3625,6 +3658,40 @@ "node": ">=14.0.0" } }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", diff --git a/package.json b/package.json index aa6123659..fbcd4539b 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", From 1bb16f3ddada0309c8ceef48336bcff67fcd3694 Mon Sep 17 00:00:00 2001 From: Nwapu Trust-Jah Chikum Date: Thu, 22 Oct 2026 07:50:32 +0100 Subject: [PATCH 11/11] fix(sdk): regenerate clients/react-query/src/schema.d.ts to match openapi spec --- clients/react-query/src/schema.d.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/clients/react-query/src/schema.d.ts b/clients/react-query/src/schema.d.ts index 000d159e5..413c38d01 100644 --- a/clients/react-query/src/schema.d.ts +++ b/clients/react-query/src/schema.d.ts @@ -348,10 +348,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; @@ -370,6 +366,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; }; @@ -2319,7 +2328,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;