Skip to main content

Sandbox and live

There are two environments. Which one you are in is decided by the prefix of the API key, at both the Edge and Cloud, and it is immutable for the lifetime of the process.

Key prefixIssued byEnvironment
flk_test_…the consolesandbox
flk_live_…the consolelive
zkl_test_…your Edgesandbox
zkl_live_…your Edgelive

There is no toggle, no header and no query parameter. An Edge started with a test key cannot issue live keys and cannot produce live data, and the two are separated by an environment column on every tenant table rather than by convention.

One Edge serves exactly one environment

Running both means running two containers with two API keys and two data directories. This is deliberate: it makes it impossible to cross sandbox and live data by misconfiguration.

What differs

LiveSandbox
Address derivationReal, from your registered extended public keyReal, from your sandbox extended public key. Valid addresses, never funded
Chain RPCYesNone. Zero outbound chain calls
BalancesOn-chainDerived from the ledger: simulated incoming less outgoing
DepositsChain eventsPOST /v1/sandbox/simulate-deposit
ConfirmationsReal block progressionPOST /v1/sandbox/simulate-confirmation, or autoConfirm
WebhooksReal, signedReal, signed, byte-identical format
SigningRealReal — exercises the full Edge path
BillingMeteredNever metered, at all

A sandbox balance is a ledger read, not a mock and not a chain read. That distinction matters because the alternative is what the endpoint used to do: ask a provider about an address that exists on no chain, and report the truthful zero that comes back while the transaction list showed the deposits. The balance and the transactions now agree, and the zero-outbound-calls row above holds for balance queries too.

The two rows in bold are the point. Sandbox derives genuine addresses from a genuine extended public key and signs with genuine keys, so your integration tests exercise real address formats, real checksums, real validation and the real Edge signing path. The only thing that does not happen is a chain being contacted.

That is deliberately different from a mock server. A fabricated address string passes your validation and fails on the first live deposit; a real derived address that happens to have no funds does not.

Simulating activity

# One deposit, auto-confirming after five seconds
curl -X POST $EDGE/sandbox/simulate-deposit \
-H "X-API-Key: $CHAINOS_EDGE_KEY" \
-H "Content-Type: application/json" \
-d '{ "address": "0x3fC9…7FAD",
"chain": "polygon",
"amount": "100.50",
"autoConfirm": true,
"confirmAfterSeconds": 5 }'

Deposits are of the chain's native asset, so amount is denominated in it — POL above, not USDT. Token deposits are not simulated, and a request carrying tokenContract is rejected rather than quietly treated as a native one.

chain disambiguates, and this example needs it. Ethereum, BSC, Polygon, Avalanche and Base share coin type 60 and therefore one extended public key, so the first address issued on each of the five is the same string — one address, five registry rows. The address alone cannot say which chain a deposit arrived on, so omitting chain for one of them is a 400 listing the candidates rather than a guess. On Bitcoin, TRON, Solana, XRP and Stellar no collision is possible and chain can be omitted.

The response echoes the chain and symbol it resolved, which is worth asserting on in a test: it is the one part of the outcome the request does not already state.

confirmAfterSeconds defaults to 15 and has a minimum of 1. Set it low in CI; a suite that waits fifteen seconds per fixture wastes more time than it looks like it does.

# Advance a transaction to a target confirmation count by hand
curl -X POST $EDGE/sandbox/simulate-confirmation \
-H "X-API-Key: $CHAINOS_EDGE_KEY" \
-H "Content-Type: application/json" \
-d '{ "transactionId": "tx_sbx_01J8XKB", "confirmations": 12 }'

This fires every threshold crossed on the way, which is how you test Bitcoin's 1 / 2 / 3 ladder in a single call. Omit confirmations to advance by one.

# Status, and the reset
curl -s $EDGE/sandbox/status -H "X-API-Key: $CHAINOS_EDGE_KEY"
curl -s -X DELETE $EDGE/sandbox/reset -H "X-API-Key: $CHAINOS_EDGE_KEY"

reset deletes every sandbox address, transaction and webhook delivery, and resets next_index on the sandbox extended public keys. One call, no confirmation prompt — the destructive-wipe gating applies to live only, because there is nothing here to lose.

Should sandbox use the same mnemonic?

No. Use a separate mnemonic for sandbox.

Cloud actively refuses the same one in both: the identity key derived from a mnemonic is the same in either environment, and seeing one identity claim both is 409 CROSS_ENVIRONMENT_IDENTITY. The check exists because sharing a mnemonic across environments means a sandbox Edge — usually the one running in CI, on a laptop, in a container with looser handling — holds the keys to live funds.

Generate a throwaway phrase for sandbox and keep the live one in your secret manager. The BIP-39 test vector is fine for sandbox; see Quickstart.

Going live

A checklist, in the order the failures actually happen:

  1. A live mnemonic exists, is backed up, and the backup has been restored once as a test. An untested backup is not a backup. See Mnemonic management.
  2. A live flk_live_* key is minted and in your secret manager. It is shown once.
  3. A second Edge container, with its own data directory and the live key. Do not repoint the sandbox one.
  4. EDGE_DATA_DIR is on persistent storage. Not tmpfs, not emptyDir. The Edge refuses to start otherwise, which is the intended behaviour and catches this before it matters.
  5. All seven readiness indicators are green in the console, or the ones you need are. A red chain refuses address generation with CHAIN_NOT_READY rather than issuing an address nobody holds a key for. See Edge liveness.
  6. Your webhook handler verifies the signature against raw bytes and is idempotent on event.id. Both. See Webhooks and Idempotency.
  7. Your handler survives a duplicate delivery without double-crediting. Test it by replaying a delivery from the console rather than assuming.
  8. You have read your chains' pages. Each has one operational quirk that costs money to discover live: XRP's reserve, Solana's rent, Stellar's trustlines, TRON's energy, Polygon's two USDCs.
  9. Alerting on your own webhook handler's failure rate and dead-letter depth. Ziklag alerts on delivery from our side; only you can see a handler that accepts a delivery and then drops it. See Monitoring.

What billing does with the distinction

Sandbox activity is never metered — no address counts, no settled transactions, no overage. Nothing you do in sandbox appears on an invoice, which is why the simulate endpoints have no rate ceiling worth mentioning and why load-testing your integration there is encouraged. See What is billed.