# Test mode (/reference/test-mode)

## Simulate a deposit to one of your test addresses

`POST /v1/test/deposits`

Writes an inbound transfer to the customer's address on `chain` exactly as the watcher would have: a `detected` movement with `deposit.detected`, then (with `confirm: true`, the default) the asset's real confirmation threshold, the ledger credit and `deposit.confirmed`. The customer's balance moves; a payout can be requested against it.

`decimals` must be echoed, as on `POST /v1/payouts` — rehearse the mismatch here rather than in production.

`confirm: false` leaves the deposit `detected` so you can rehearse a pending state, then drive it with `/confirm`, `/fail` or `/orphan`.

Test wallet keys only (`403 test_mode_only` otherwise). Needs `wallets:create`.

### Request body (required)

- `customer_id` (optional, string (uuid)) — Whose address receives it. They must already hold one on `chain` (POST /v1/wallets). Send this or `address`, not both.
- `address` (optional, string) — The address that receives it — an invoice's payment address, as a payer would copy it from the checkout page. Must belong to this test project. Send this or `customer_id`, not both.
- `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
- `asset` (required, string) — A symbol from GET /v1/chains for this test project.
- `decimals` (required, integer) — Must equal the asset's configured decimals. See GET /v1/chains.
- `amount` (required, string) — Atomic units, as a string. No decimal point.
- `confirm` (optional, boolean) — false = stop at `detected`.

### Responses

- `201` — The deposit, as `GET /v1/deposits/{id}` would return it.
  - `object` (required, string)
  - `id` (required, string (uuid))
  - `ref` (required, string) — The MolnPay ID of this deposit — `MOLN-` and twelve hex characters, shown on the receipt in the console and quoted to support. Stable for the life of the record; unlike `id`, safe to put in front of a person.
  - `customer_id` (optional, string | null (uuid))
  - `wallet_id` (optional, string | null (uuid))
  - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
  - `asset` (optional, object)
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `symbol` (required, string)
    - `decimals` (required, integer) — Authoritative scale for this asset ON THIS CHAIN. USDT and USDC are 18 on BSC and 6 everywhere else — never hardcode 6.
    - `contract` (optional, string | null) — null for the chain's native coin.
    - `logo_url` (optional, string | null) — The coin's logo, served from MolnPay's own CDN (assets.molnpay.com) — not a third party, so it is safe to render on a payment page. Decoration: it can 404 for a coin whose art we have not uploaded yet, and it is the same art our own console renders — build a picker on it rather than maintaining your own symbol-to-logo map, but never block a render on it. Sizes are 32, 64 and 128; the URL gives 64.
  - `amount` (required, string) — Amount received, atomic units.
  - `amount_decimal` (optional, string)
  - `tx_hash` (required, string)
  - `log_index` (optional, integer) — Distinguishes several transfers of the same asset within one transaction.
  - `from_address` (optional, string | null)
  - `confirmations` (optional, integer)
  - `required_confirmations` (optional, integer | null)
  - `status` (required, string, one of: detected, confirmed, failed, orphaned) — Only `confirmed` is money. `detected` is seen-on-chain but not credited. `orphaned` was confirmed and then reversed — **read `status_reason` before acting on it**: only `reorg` means the chain took the money back and you must reverse your own credit.
  - `status_reason` (optional, string | null) — Why a `failed` or `orphaned` deposit ended that way; `null` on any other status. - `reorg` — the chain reorganised and the transfer no longer exists. The credit is reversed and **you must reverse yours**. - `swap_output` — the transfer was the output of one of your own in-place swaps landing in your own address. It was briefly credited as a deposit in error and that duplicate credit has been reversed. No money moved and there is **nothing for you to reverse**. Not an enum: new reasons may be added, so treat an unrecognised value the same as `reorg` and reconcile manually. A deposit orphaned before 2026-09-19 predates this field; those rows report `reorg` unless a backfill has since identified them as `swap_output`.
  - `block_time` (optional, string | null (date-time))
  - `explorer_url` (optional, string)
  - `network_fee` (optional, string | null) — What the SENDER's transaction cost the network, in the chain's NATIVE coin, atomic units. Nobody is charged this by us and nothing is deducted from `amount` — it is published so a receipt can show what the explorer shows. It is the WHOLE TRANSACTION's fee, so when one transaction credits several deposits each reports the same figure and they must not be summed. Null = we could not determine it (unmined when we looked, a chain with no fee-by-hash lookup, or an RPC failure) — never "it was free".
  - `network_fee_decimal` (optional, string | null) — `network_fee` in whole units.
  - `network_fee_asset` (optional, string | null) — The native coin `network_fee` is denominated in — BNB, ETH, TRX.
  - `network_fee_usd` (optional, string | null) — `network_fee` in US dollars **at the price it held when the fee was paid**, not at spot — frozen when we first priced it, so an old receipt keeps saying what it cost that day. Null where we could not price it (a chain we have no quote for, a moment beyond the price history, a failed lookup) — never "it was free".
  - `ledger_tx_id` (optional, string | null (uuid))
  - `created_at` (optional, string (date-time))
- `400` — `decimals_mismatch` or a malformed amount.
- `401` — Missing, malformed, expired or revoked API key.
- `403` — `test_mode_only` — this key is live. Or the key lacks `wallets:create`, or is not a `wallet` key.
- `404` — `wallet_not_found` — the customer has no address on that chain yet.
- `422` — `unknown_asset`.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## A pending test deposit reaches finality and is credited

`POST /v1/test/deposits/{id}/confirm`

### Parameters

- `id` (path, required, string (uuid))

### Responses

- `200` — The deposit, now `confirmed`.
  - `object` (required, string)
  - `id` (required, string (uuid))
  - `ref` (required, string) — The MolnPay ID of this deposit — `MOLN-` and twelve hex characters, shown on the receipt in the console and quoted to support. Stable for the life of the record; unlike `id`, safe to put in front of a person.
  - `customer_id` (optional, string | null (uuid))
  - `wallet_id` (optional, string | null (uuid))
  - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
  - `asset` (optional, object)
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `symbol` (required, string)
    - `decimals` (required, integer) — Authoritative scale for this asset ON THIS CHAIN. USDT and USDC are 18 on BSC and 6 everywhere else — never hardcode 6.
    - `contract` (optional, string | null) — null for the chain's native coin.
    - `logo_url` (optional, string | null) — The coin's logo, served from MolnPay's own CDN (assets.molnpay.com) — not a third party, so it is safe to render on a payment page. Decoration: it can 404 for a coin whose art we have not uploaded yet, and it is the same art our own console renders — build a picker on it rather than maintaining your own symbol-to-logo map, but never block a render on it. Sizes are 32, 64 and 128; the URL gives 64.
  - `amount` (required, string) — Amount received, atomic units.
  - `amount_decimal` (optional, string)
  - `tx_hash` (required, string)
  - `log_index` (optional, integer) — Distinguishes several transfers of the same asset within one transaction.
  - `from_address` (optional, string | null)
  - `confirmations` (optional, integer)
  - `required_confirmations` (optional, integer | null)
  - `status` (required, string, one of: detected, confirmed, failed, orphaned) — Only `confirmed` is money. `detected` is seen-on-chain but not credited. `orphaned` was confirmed and then reversed — **read `status_reason` before acting on it**: only `reorg` means the chain took the money back and you must reverse your own credit.
  - `status_reason` (optional, string | null) — Why a `failed` or `orphaned` deposit ended that way; `null` on any other status. - `reorg` — the chain reorganised and the transfer no longer exists. The credit is reversed and **you must reverse yours**. - `swap_output` — the transfer was the output of one of your own in-place swaps landing in your own address. It was briefly credited as a deposit in error and that duplicate credit has been reversed. No money moved and there is **nothing for you to reverse**. Not an enum: new reasons may be added, so treat an unrecognised value the same as `reorg` and reconcile manually. A deposit orphaned before 2026-09-19 predates this field; those rows report `reorg` unless a backfill has since identified them as `swap_output`.
  - `block_time` (optional, string | null (date-time))
  - `explorer_url` (optional, string)
  - `network_fee` (optional, string | null) — What the SENDER's transaction cost the network, in the chain's NATIVE coin, atomic units. Nobody is charged this by us and nothing is deducted from `amount` — it is published so a receipt can show what the explorer shows. It is the WHOLE TRANSACTION's fee, so when one transaction credits several deposits each reports the same figure and they must not be summed. Null = we could not determine it (unmined when we looked, a chain with no fee-by-hash lookup, or an RPC failure) — never "it was free".
  - `network_fee_decimal` (optional, string | null) — `network_fee` in whole units.
  - `network_fee_asset` (optional, string | null) — The native coin `network_fee` is denominated in — BNB, ETH, TRX.
  - `network_fee_usd` (optional, string | null) — `network_fee` in US dollars **at the price it held when the fee was paid**, not at spot — frozen when we first priced it, so an old receipt keeps saying what it cost that day. Null where we could not price it (a chain we have no quote for, a moment beyond the price history, a failed lookup) — never "it was free".
  - `ledger_tx_id` (optional, string | null (uuid))
  - `created_at` (optional, string (date-time))
- `401` — Missing, malformed, expired or revoked API key.
- `403` — `test_mode_only`, or the key lacks `wallets:create`.
- `404` — `deposit_not_found`.
- `409` — `deposit_not_pending` — only a `detected` deposit can be confirmed.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## A pending test deposit turns out not to be money

`POST /v1/test/deposits/{id}/fail`

Closes a `detected` deposit as `failed` and emits `deposit.failed`, as the watcher does for a reverted transaction.

### Parameters

- `id` (path, required, string (uuid))

### Responses

- `200` — The deposit, now `failed`.
  - `object` (required, string)
  - `id` (required, string (uuid))
  - `ref` (required, string) — The MolnPay ID of this deposit — `MOLN-` and twelve hex characters, shown on the receipt in the console and quoted to support. Stable for the life of the record; unlike `id`, safe to put in front of a person.
  - `customer_id` (optional, string | null (uuid))
  - `wallet_id` (optional, string | null (uuid))
  - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
  - `asset` (optional, object)
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `symbol` (required, string)
    - `decimals` (required, integer) — Authoritative scale for this asset ON THIS CHAIN. USDT and USDC are 18 on BSC and 6 everywhere else — never hardcode 6.
    - `contract` (optional, string | null) — null for the chain's native coin.
    - `logo_url` (optional, string | null) — The coin's logo, served from MolnPay's own CDN (assets.molnpay.com) — not a third party, so it is safe to render on a payment page. Decoration: it can 404 for a coin whose art we have not uploaded yet, and it is the same art our own console renders — build a picker on it rather than maintaining your own symbol-to-logo map, but never block a render on it. Sizes are 32, 64 and 128; the URL gives 64.
  - `amount` (required, string) — Amount received, atomic units.
  - `amount_decimal` (optional, string)
  - `tx_hash` (required, string)
  - `log_index` (optional, integer) — Distinguishes several transfers of the same asset within one transaction.
  - `from_address` (optional, string | null)
  - `confirmations` (optional, integer)
  - `required_confirmations` (optional, integer | null)
  - `status` (required, string, one of: detected, confirmed, failed, orphaned) — Only `confirmed` is money. `detected` is seen-on-chain but not credited. `orphaned` was confirmed and then reversed — **read `status_reason` before acting on it**: only `reorg` means the chain took the money back and you must reverse your own credit.
  - `status_reason` (optional, string | null) — Why a `failed` or `orphaned` deposit ended that way; `null` on any other status. - `reorg` — the chain reorganised and the transfer no longer exists. The credit is reversed and **you must reverse yours**. - `swap_output` — the transfer was the output of one of your own in-place swaps landing in your own address. It was briefly credited as a deposit in error and that duplicate credit has been reversed. No money moved and there is **nothing for you to reverse**. Not an enum: new reasons may be added, so treat an unrecognised value the same as `reorg` and reconcile manually. A deposit orphaned before 2026-09-19 predates this field; those rows report `reorg` unless a backfill has since identified them as `swap_output`.
  - `block_time` (optional, string | null (date-time))
  - `explorer_url` (optional, string)
  - `network_fee` (optional, string | null) — What the SENDER's transaction cost the network, in the chain's NATIVE coin, atomic units. Nobody is charged this by us and nothing is deducted from `amount` — it is published so a receipt can show what the explorer shows. It is the WHOLE TRANSACTION's fee, so when one transaction credits several deposits each reports the same figure and they must not be summed. Null = we could not determine it (unmined when we looked, a chain with no fee-by-hash lookup, or an RPC failure) — never "it was free".
  - `network_fee_decimal` (optional, string | null) — `network_fee` in whole units.
  - `network_fee_asset` (optional, string | null) — The native coin `network_fee` is denominated in — BNB, ETH, TRX.
  - `network_fee_usd` (optional, string | null) — `network_fee` in US dollars **at the price it held when the fee was paid**, not at spot — frozen when we first priced it, so an old receipt keeps saying what it cost that day. Null where we could not price it (a chain we have no quote for, a moment beyond the price history, a failed lookup) — never "it was free".
  - `ledger_tx_id` (optional, string | null (uuid))
  - `created_at` (optional, string (date-time))
- `401` — Missing, malformed, expired or revoked API key.
- `403` — `test_mode_only`, or the key lacks `wallets:create`.
- `404` — `deposit_not_found`.
- `409` — `deposit_not_pending`.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## A confirmed test deposit is reorged out

`POST /v1/test/deposits/{id}/orphan`

Reverses the credit of a `confirmed` deposit and emits `deposit.orphaned` with `status_reason: "reorg"` — the event your integration must handle by reversing its own credit. This is the only way to rehearse a reorg; rehearse it. Note that `orphaned` alone is not a reorg: `status_reason` is what distinguishes one from a credit we withdrew ourselves.

### Parameters

- `id` (path, required, string (uuid))

### Responses

- `200` — The deposit, now `orphaned`.
  - `object` (required, string)
  - `id` (required, string (uuid))
  - `ref` (required, string) — The MolnPay ID of this deposit — `MOLN-` and twelve hex characters, shown on the receipt in the console and quoted to support. Stable for the life of the record; unlike `id`, safe to put in front of a person.
  - `customer_id` (optional, string | null (uuid))
  - `wallet_id` (optional, string | null (uuid))
  - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
  - `asset` (optional, object)
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `symbol` (required, string)
    - `decimals` (required, integer) — Authoritative scale for this asset ON THIS CHAIN. USDT and USDC are 18 on BSC and 6 everywhere else — never hardcode 6.
    - `contract` (optional, string | null) — null for the chain's native coin.
    - `logo_url` (optional, string | null) — The coin's logo, served from MolnPay's own CDN (assets.molnpay.com) — not a third party, so it is safe to render on a payment page. Decoration: it can 404 for a coin whose art we have not uploaded yet, and it is the same art our own console renders — build a picker on it rather than maintaining your own symbol-to-logo map, but never block a render on it. Sizes are 32, 64 and 128; the URL gives 64.
  - `amount` (required, string) — Amount received, atomic units.
  - `amount_decimal` (optional, string)
  - `tx_hash` (required, string)
  - `log_index` (optional, integer) — Distinguishes several transfers of the same asset within one transaction.
  - `from_address` (optional, string | null)
  - `confirmations` (optional, integer)
  - `required_confirmations` (optional, integer | null)
  - `status` (required, string, one of: detected, confirmed, failed, orphaned) — Only `confirmed` is money. `detected` is seen-on-chain but not credited. `orphaned` was confirmed and then reversed — **read `status_reason` before acting on it**: only `reorg` means the chain took the money back and you must reverse your own credit.
  - `status_reason` (optional, string | null) — Why a `failed` or `orphaned` deposit ended that way; `null` on any other status. - `reorg` — the chain reorganised and the transfer no longer exists. The credit is reversed and **you must reverse yours**. - `swap_output` — the transfer was the output of one of your own in-place swaps landing in your own address. It was briefly credited as a deposit in error and that duplicate credit has been reversed. No money moved and there is **nothing for you to reverse**. Not an enum: new reasons may be added, so treat an unrecognised value the same as `reorg` and reconcile manually. A deposit orphaned before 2026-09-19 predates this field; those rows report `reorg` unless a backfill has since identified them as `swap_output`.
  - `block_time` (optional, string | null (date-time))
  - `explorer_url` (optional, string)
  - `network_fee` (optional, string | null) — What the SENDER's transaction cost the network, in the chain's NATIVE coin, atomic units. Nobody is charged this by us and nothing is deducted from `amount` — it is published so a receipt can show what the explorer shows. It is the WHOLE TRANSACTION's fee, so when one transaction credits several deposits each reports the same figure and they must not be summed. Null = we could not determine it (unmined when we looked, a chain with no fee-by-hash lookup, or an RPC failure) — never "it was free".
  - `network_fee_decimal` (optional, string | null) — `network_fee` in whole units.
  - `network_fee_asset` (optional, string | null) — The native coin `network_fee` is denominated in — BNB, ETH, TRX.
  - `network_fee_usd` (optional, string | null) — `network_fee` in US dollars **at the price it held when the fee was paid**, not at spot — frozen when we first priced it, so an old receipt keeps saying what it cost that day. Null where we could not price it (a chain we have no quote for, a moment beyond the price history, a failed lookup) — never "it was free".
  - `ledger_tx_id` (optional, string | null (uuid))
  - `created_at` (optional, string (date-time))
- `401` — Missing, malformed, expired or revoked API key.
- `403` — `test_mode_only`, or the key lacks `wallets:create`.
- `404` — `deposit_not_found`.
- `409` — `deposit_not_confirmed` — only a `confirmed` deposit can be orphaned.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.