client.sandbox.* surface
The SDK exposes 14 sandbox methods on client.sandbox.*, mirroring the Sandbox simulation API one-to-one. 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/sdktarball atnode_modules/@txnod/sdk/docs/05-sandbox.md, with an entry point atnode_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_PROJECT_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.erroron every constructor invocation describing the misconfiguration. - Every outbound API request carries an
X-Txnod-Client-Environment: productionheader, 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_PROJECT_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.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 { extra_units: 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 { amount_units: 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 stable, deterministic event_id (not a fresh ULID — exercises the dispatcher’s idempotent re-emission contract).
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 { event_type, amount_units, confirmations, block_height, chain_specific?, expected_current_status, seed? }. Returns { event_id, status }.
Throws: TxnodSandboxInvoiceNotFoundError, TxnodSandboxInvoiceTransitionInvalidError (current status ≠ expectedCurrentStatus), 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: numberThrows: 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:
TxnodSandboxKeyInProductionError— sandbox API secret detected with a production-flagged environment (layer 2).TxnodEnvironmentUnknownError— sandbox secret with no environment signal at all; safer to fail-closed (layer 2).TxnodSandboxXpubInProductionError— testnet-prefixed xpub configured for a production-flagged environment (layer 3).TxnodSandboxKeyAgainstProductionProjectError— sandbox API key targeting a production project (layer 4).TxnodProductionKeyAgainstSandboxProjectError— production API key targeting a sandbox project (layer 4).
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.
Related
- Sandbox simulation API — the REST surface this client wraps.
- Sandbox safety — the seven-layer defense and recommended CI assertion.
- verifyWebhookSignature — every event carries
mode: 'production' | 'testnet' | 'sandbox'; assert in your handler. - Sandbox projects — project-creation flow.