Skip to Content
SDKclient.sandbox.* surface

client.sandbox.* surface

The SDK exposes 21 sandbox methods on client.sandbox.*, mirroring the Sandbox simulation API one-to-one: four invoice CRUD methods plus two webhook delivery-log methods (the sandbox mirrors of the kind-locked production invoice and webhook surfaces), eleven simulation methods, and four project-scoped helpers. Constructed lazily — bundlers tree-shake the entire namespace when never referenced. Each method signs requests with the same HMAC scheme as the rest of the SDK and throws typed TxnodSandbox* errors per spec §7.1.

AI coding agents: a full, version-matched copy of this page and every other SDK guide ships inside the @txnod/sdk tarball at node_modules/@txnod/sdk/docs/05-sandbox.md, with an entry point at node_modules/@txnod/sdk/AGENTS.md. Prefer those offline files after install — they need no network access.

Construction

import { TxnodClient } from '@txnod/sdk'; const client = new TxnodClient({ projectId: process.env.TXNOD_API_KEY_ID!, apiSecret: process.env.TXNOD_API_SECRET!, // sk_sandbox_... environment: 'non-production', // explicit override; trusts NODE_ENV otherwise });

The environment option ('production' | 'non-production') takes precedence over TXNOD_ENVIRONMENT and NODE_ENV. Use 'non-production' for tests and staging-replicas.

iAcknowledgeRoutingRealCustomerFundsToSandboxAddresses

The escape-hatch constructor option. Default behaviour — when the SDK detects NODE_ENV === 'production' AND the configured secret starts with sk_sandbox_ — is to throw TxnodSandboxKeyInProductionError at construction and refuse to instantiate. Setting iAcknowledgeRoutingRealCustomerFundsToSandboxAddresses: true bypasses that hard-fail.

The only legitimate case for the override is a staging-replica that mirrors production env-vars (e.g. NODE_ENV=production) but is wired to a sandbox project for safe testing. Even with the override:

  • The SDK emits a non-suppressible console.error on every constructor invocation describing the misconfiguration.
  • Every outbound API request carries an X-Txnod-Client-Environment: production header, surfacing the misuse in server-side admin telemetry.
  • The override option name is intentionally verbose so it remains awkward to type — renaming requires a major-version bump and a deprecation cycle.
import { TxnodClient } from '@txnod/sdk'; // Staging-replica: NODE_ENV=production but secret is sandbox. const client = new TxnodClient({ projectId: process.env.TXNOD_API_KEY_ID!, apiSecret: process.env.TXNOD_API_SECRET!, iAcknowledgeRoutingRealCustomerFundsToSandboxAddresses: true, });

If you find yourself reaching for this in normal application code, the right fix is almost always environment: 'non-production' instead.

client.sandbox.createInvoice(body)

Creates an invoice on the sandbox project — the sandbox mirror of client.createInvoice. The production endpoint is kind-locked, so a sandbox key calling client.createInvoice is rejected with TxnodProductionProjectRequiredError; always create sandbox invoices through this method. Idempotent on (project_id, external_id). Unlike client.createInvoice, no xpub address-verification runs on the response — sandbox addresses never carry real funds.

import type { TxnodClient } from '@txnod/sdk'; declare const client: TxnodClient; const invoice = await client.sandbox.createInvoice({ amount_usd: 9.99, coin: 'usdt_trc20', external_id: 'order-42', }); // invoice.id is what every simulate* method below takes

Throws: TxnodSandboxActiveInvoiceCapReachedError (10k active-invoice cap — call client.sandbox.reset), TxnodCoinNotEnabledError, TxnodValidationError, TxnodRateQuoteUnavailableError, TxnodSandboxRateLimitExceededError.

client.sandbox.getInvoice(invoiceId)

Fetches a sandbox invoice by ULID — the sandbox mirror of client.getInvoice. Returns InvoiceDetailResponse including transactions + confirmations.

Throws: TxnodInvoiceNotFoundError (also on cross-project lookups — no enumeration).

client.sandbox.listInvoices(query?)

Cursor-paginated search over the sandbox project’s invoices — the sandbox mirror of client.searchInvoices, taking the same snake_case filters.

import type { TxnodClient } from '@txnod/sdk'; declare const client: TxnodClient; const page = await client.sandbox.listInvoices({ status: 'paid', limit: 20 }); for (const invoice of page.items) console.log(invoice.id);

Throws: TxnodValidationError.

client.sandbox.cancelInvoice(invoiceId)

Cancels a sandbox invoice in pending or detected state — the sandbox mirror of client.cancelInvoice.

Throws: TxnodInvoiceNotFoundError, TxnodInvoiceNotCancellableError (invoice already terminal), TxnodSandboxRateLimitExceededError.

client.sandbox.listWebhookEvents(query?)

Cursor-paginated delivery log of the sandbox project’s outbound webhook events — the sandbox mirror of client.listWebhookEvents, taking the same snake_case filters (status, event_type, since, invoice_id, cursor, limit). The production endpoint is kind-locked, so a sandbox key calling client.listWebhookEvents is rejected with TxnodProductionProjectRequiredError; always read sandbox delivery state through this method. Every row corresponds to an envelope delivered with mode: 'sandbox'.

import type { TxnodClient } from '@txnod/sdk'; declare const client: TxnodClient; declare const invoiceId: string; const page = await client.sandbox.listWebhookEvents({ invoice_id: invoiceId, limit: 50, }); for (const event of page.items) console.log(event.id, event.event_type);

Throws: TxnodValidationError.

client.sandbox.resendWebhookEvent(eventId)

Re-enqueues a previously-delivered sandbox webhook event with a fresh event_id — the sandbox mirror of client.resendWebhookEvent. The response carries original_event_id for lineage.

Throws: TxnodEventNotFoundError (also on cross-project lookups), TxnodEventNotResendableError (event still in flight or dead-lettered), TxnodSandboxRateLimitExceededError.

client.sandbox.simulateDetect(invoiceId, opts?)

Synthesizes invoice.detected (status pending → detected).

import type { TxnodClient } from '@txnod/sdk'; declare const client: TxnodClient; const result = await client.sandbox.simulateDetect( '01HK8MAR2QEXAMPLE000000000', { seed: 'order-42' }, ); // result.event_id: ULID // result.status: 'detected'

Throws: TxnodSandboxInvoiceNotFoundError, TxnodSandboxInvoiceTransitionInvalidError, TxnodSandboxRateLimitExceededError.

client.sandbox.simulatePaid(invoiceId)

Walks the invoice from detected → paid. Returns { event_id, status: 'paid' }. Throws the same error classes as simulateDetect.

client.sandbox.simulateOverpaid(invoiceId, params)

Walks from detected → paid (overpaid envelope). params is exactly one of { multiplier: number } or { extraUnits: string }.

import type { TxnodClient } from '@txnod/sdk'; declare const client: TxnodClient; declare const invoiceId: string; await client.sandbox.simulateOverpaid(invoiceId, { multiplier: 1.5 });

Throws: TxnodSandboxInvoiceNotFoundError, TxnodSandboxInvoiceTransitionInvalidError, TxnodValidationError, TxnodSandboxRateLimitExceededError.

client.sandbox.simulatePartial(invoiceId, params)

Emits invoice.partial; invoice stays in detected. params is exactly one of { fraction: number } or { amountUnits: string }.

import type { TxnodClient } from '@txnod/sdk'; declare const client: TxnodClient; declare const invoiceId: string; await client.sandbox.simulatePartial(invoiceId, { fraction: 0.5 });

Throws: same as simulateOverpaid.

client.sandbox.simulateExpire(invoiceId)

Forces expired. Returns { event_id, status: 'expired' }.

Throws: TxnodSandboxInvoiceNotFoundError, TxnodSandboxInvoiceTransitionInvalidError, TxnodSandboxRateLimitExceededError.

client.sandbox.simulateLatePayment(invoiceId, opts?)

Followup to simulateExpire. Walks expired → expired_paid_late.

Throws: same as simulateExpire.

client.sandbox.simulateReorg(invoiceId)

Walks a terminal-paid invoice (paid / overpaid / partial) into reverted. Emits invoice.reverted.

Throws: same as simulateExpire.

client.sandbox.simulateReconfirm(invoiceId)

After a reorg, fresh paid event with a new event_id (not a replay of the original — exercises the dispatcher’s restore-after-revert contract: the re-emitted event must escape your UNIQUE(event_id) dedupe).

Throws: TxnodSandboxInvoiceNotFoundError, TxnodSandboxInvoiceTransitionInvalidError (invoice not in reverted), TxnodSandboxRateLimitExceededError.

client.sandbox.simulateDuplicateDelivery(invoiceId)

Re-fires the most recent terminal webhook for this invoice with the SAME event_id — exercises the integrator’s idempotency layer.

Throws: TxnodSandboxInvoiceNotFoundError, TxnodSandboxInvoiceTerminalError (no terminal event yet), TxnodSandboxRateLimitExceededError.

client.sandbox.simulateEvent(invoiceId, eventInput)

Low-level escape hatch. eventInput carries { eventType, amountUnits, confirmations, blockHeight, chainSpecific?, expectedCurrentStatus, seed? }. Returns { event_id, status }.

Throws: TxnodSandboxInvoiceNotFoundError, TxnodSandboxInvoiceTransitionInvalidError (current status ≠ expectedCurrentStatus), TxnodValidationError, TxnodSandboxRateLimitExceededError.

client.sandbox.simulateClaimByTx(invoiceId, args)

Sandbox parity for client.claimInvoiceByTx. Skips the real chain-provider lookup and synthesises resolved facts from the invoice itself, so the full manual tx-hash claim lifecycle (auto-attribution, pending-review, rejection) can be exercised without on-chain spend. args is { txHash: string, chain: Chain }; the response shape is identical to the production endpoint (ClaimByTxResponse).

import type { TxnodClient } from '@txnod/sdk'; declare const client: TxnodClient; declare const invoiceId: string; const result = await client.sandbox.simulateClaimByTx(invoiceId, { txHash: '0xabc...def', chain: 'eth', }); // result.status: 'auto_attributed' | 'pending_finality' | 'pending_review' | 'rejected'

Throws: TxnodSandboxInvoiceNotFoundError, TxnodValidationError, TxnodSandboxRateLimitExceededError.

client.sandbox.clockAdvance(projectId, params)

Per-project: increments the per-chain confirmation counter for every detected invoice. params is { chain, blocks } (1 ≤ blocks ≤ 64). Per-chain rate-limited at 10 calls/min/project.

import type { TxnodClient } from '@txnod/sdk'; declare const client: TxnodClient; declare const projectId: string; const r = await client.sandbox.clockAdvance(projectId, { chain: 'btc', blocks: 6, }); // r.advanced: number, r.remaining: number

Throws: TxnodSandboxProjectRequiredError, TxnodSandboxKeyAgainstProductionProjectError, TxnodProductionKeyAgainstSandboxProjectError, TxnodValidationError, TxnodSandboxRateLimitExceededError.

client.sandbox.reset(projectId)

Soft-purges the project’s data tail (invoices, transactions, outbox events, address pool) while preserving the shell, xpubs, bindings, API key, and sandbox PAT. Use as beforeAll/afterAll in test suites.

Throws: TxnodSandboxProjectRequiredError, TxnodSandboxKeyAgainstProductionProjectError, TxnodProductionKeyAgainstSandboxProjectError, TxnodSandboxResetFailedError.

client.sandbox.destroy(projectId)

Cascade-deletes the entire sandbox project — shell, xpubs, bindings, API keys, sandbox PAT, all data tail. Irreversible.

Throws: TxnodSandboxProjectRequiredError, TxnodSandboxKeyAgainstProductionProjectError, TxnodProductionKeyAgainstSandboxProjectError, TxnodSandboxDeleteFailedError.

client.sandbox.listWallets(projectId)

Read-only listing of per-chain sandbox xpubs. Returns WalletsListResponse. Read-only — no transition errors fire.

Throws: TxnodSandboxProjectRequiredError, TxnodSandboxKeyAgainstProductionProjectError, TxnodProductionKeyAgainstSandboxProjectError.

Typed errors

Safety-class errors

These five typed errors implement the SDK’s structural defense layers per the Sandbox safety layered-defense model. Each is exported from @txnod/sdk; reference the SDK source for the canonical class shape:

Operational errors

These three typed errors fire during normal sandbox operation and are caught case-by-case in handler logic:

  • TxnodSandboxProjectRequiredError — endpoint reached with a non-sandbox project id.
  • TxnodSandboxInvoiceTransitionInvalidError — current invoice status does not permit the requested simulate-* call.
  • TxnodSandboxRateLimitExceededError — per-project simulate-* rate cap exceeded.

Additional sandbox-specific operational errors: TxnodSandboxInvoiceNotFoundError, TxnodSandboxInvoiceTerminalError, TxnodSandboxResetFailedError, TxnodSandboxDeleteFailedError.