Skip to Content
GuidesProject kinds

Project kinds

TxNod has a single discriminator per project — kind — and three legal values:

KindMeaningWhere the chain lives
productionReal money, mainnet RPC, mainnet derivation paths.Bitcoin mainnet, Ethereum mainnet, Polygon PoS, BNB Smart Chain, TRON mainnet, Cardano mainnet, TON mainnet (chain id -239).
testnetReal testnet faucet money, testnet RPC, testnet derivation paths.Bitcoin signet/testnet, Ethereum Sepolia, Polygon Amoy, BSC testnet, TRON Shasta/Nile, Cardano preprod/preview, TON testnet (chain id -3).
sandboxFully simulated lifecycle, no chain-watcher subscription, auto-derived deterministic test wallets. Use the client.sandbox.* namespace and the /api/v1/sandbox/* REST surface.None — simulation only.

Kind is fixed at project create time and immutable for the project’s lifetime. To “promote” a testnet integration to production, create a fresh kind='production' project — there is no flag-flip.

The previous shape carried a separate network: 'mainnet' | 'testnet' field on every invoice request, response, wallet, and binding row. That field is gone everywhere. The kind→network mapping happens once, at the edge — provider URL selection, BIP-32 / CIP-19 version-byte derivation, TonConnect protocol message — and is never user-visible on the wire.

Implications for partner code

createInvoice no longer takes a network argument. The invoice’s underlying chain network is implied by which project the API credential is bound to:

import { TxnodClient } from '@txnod/sdk'; // Production credential → mainnet invoices. const prod = new TxnodClient({ projectId: process.env.TXNOD_PROJECT_ID!, apiSecret: process.env.TXNOD_API_SECRET!, }); // Testnet credential → testnet invoices. Same code path, different secret. const staging = new TxnodClient({ projectId: process.env.TXNOD_TESTNET_PROJECT_ID!, apiSecret: process.env.TXNOD_TESTNET_API_SECRET!, }); await prod.createInvoice({ amount_usd: 9.99, coin: 'btc', external_id: 'order-1' }); await staging.createInvoice({ amount_usd: 9.99, coin: 'btc', external_id: 'order-1' });

InvoiceResponse likewise no longer carries a network field. If your webhook handler processes events from multiple projects (e.g. one production, one testnet), branch on the webhook envelope’s mode field — which extends to 'production' | 'testnet' | 'sandbox' — instead.

Cross-kind binding is rejected

Operator wallets carry their own kind (set when the wallet is registered through the dashboard’s Wallet wizard). Binding a wallet of one kind to a project of a different kind fails with a typed error:

Kind on projectKind on walletResult
productionproductionBind succeeds.
testnettestnetBind succeeds.
productiontestnetServer returns HTTP 422 error_code: 'wallet_kind_mismatch'. The SDK throws TxnodWalletKindMismatchError.
testnetproductionSame — HTTP 422 wallet_kind_mismatch.
sandbox(any)N/A — sandbox projects auto-derive their own deterministic test wallets at create time; the operator-binding surface never reaches sandbox projects.

The 422 carries a human-readable detail you can surface verbatim in your dashboard / wizard UX. There is no automatic fallback — pick the right kind on both sides.

Subscription billing (txnod-self) is always production

The TxNod self-billing project (txnod-self) is permanently kind='production'. Subscription payments are real money; there is no “buy a testnet subscription” or “buy a sandbox subscription” path.

See also