For the complete documentation index, see llms.txt. This page is also available as Markdown.

Withdraw

Move funds from a Native Core balance out to an EVM chain — the signed action, the amount rules, and how to confirm the payout landed.

Read Deposit & Withdraw first for the shared endpoints, discovery queries, and rate limits.

1. Validate the amount

Three rules govern the amount. The first two come from /info and are enforced at admission. The third is enforced nowhere: break it and Native Core accepts the action, debits the balance, and the release is never constructed.

  • Minimummin_withdraw_atoms from the accountingWithdrawTokens entry whose chain_id and asset_id match your destination chain and asset.

  • Feewithdraw_fee_atoms for the asset from assets. It is recorded, not deducted: you sign the gross amount, Native debits the gross, and the destination chain releases amount − fee. amount must be strictly greater than the fee.

  • Decimal cap — cap the amount at 6 decimal places. Native balances are 8-decimal and the release rescales into the destination token's decimals. USDT and USDC are 6-decimal on Ethereum, Arbitrum and Base, so a 6-decimal cap divides evenly into every destination token currently listed.

2. Sign and submit

withdraw is an owner action: sign it with your main wallet under auth_scheme: "eip712", never with an API wallet. The field reference is withdraw; the scheme is EIP-712 signing.

Use the current Unix millisecond timestamp for both withdraw_nonce and the envelope nonce, incrementing locally if two withdrawals for the same account land in the same millisecond.

Persist withdraw_nonce before you sign. It is the handle for the rest of this flow, on both chains.

The typed data is the common EIP-712 prefix followed by the action's own fields:

The domain carries no chainId, so a wallet signs it while connected to any EVM chain. Post the wallet's raw 65-byte r‖s‖v signature verbatim.

Assert the recovery locally while you build the integration. Native derives the account from the recovered signer, so typed data that does not match this definition recovers a different address — one with no account — and the withdrawal comes back as OwnerDoesNotExist, never as a signature error. A wrong struct, a stray chainId in the domain, and a field in the wrong order all fail this way.

The digest is pinned and identical across implementations, so you can also assert it directly:

Branch on submission_status, never on the HTTP status — /trade returns the same body shape for 200, 400, 429, 503 and 504.

  • accepted — the action executed and the balance is debited.

  • rejected — fix the error.code before you sign a fresh action.

  • timeoutnot a rejection. The withdrawal may still commit, so reconcile by cloid with txStatusByCloid instead of re-signing.

The decision playbook is Handle outcomes & timeouts; the code catalog is Error Responses.

The authority is the recovered signer, so the debited account is whoever signed. dst_address is only the EVM payout target — watch it on the destination chain.

3. Confirm the Native debit

withdraws returns the executed withdrawal records for an address. Find yours by withdraw_nonce.

This read is a 3-day window. It confirms the debit; it is not durable history, so record the withdrawal on your side at submit time.

Each record's tx_hash is the Native debit. Open it on the Native explorer to inspect the withdrawal by hand:

4. Watch the destination chain

Poll usedNonces(dstAddress, withdrawNonce) on the destination chain until it returns true, on an interval that suits that chain. You never call withdraw() yourself — the vault rejects anything but the operator's multi-signed call.

usedNonces is the vault's permanent replay guard, keyed by the payout address and your withdraw_nonce. It flips to true in the same transaction that transfers the tokens, so true is terminal.

Compute the credited amount yourself: amount − withdraw_fee_atoms, from values you already hold.

To watch the token's Transfer event instead, pin the destination ERC20 addresses for the routes you support at integration time. /info does not map (asset_id, chain_id) onto an address, and symbol matching breaks on the wrapped-native routes — ETH is WETH, BNB is WBNB — and on Arbitrum USDT, which is USD₮0.

A withdrawal debited on Native that still shows usedNonces == false after a long wait is stalled, not lost. The release pipeline retries and never drops a debited withdrawal; the decimal cap in step 1 is the most common cause.

What can go wrong

Symptom
Cause
What to do

WithdrawAmountBelowMinimum

Below min_withdraw_atoms for the route

Re-read accountingWithdrawTokens

WithdrawAmountNotAboveFee

Amount is not strictly greater than the asset's withdraw fee

Re-read withdraw_fee_atoms in assets

WithdrawInsufficientBalance

Available balance does not cover the gross amount

Re-read userBalances; locked balance does not count

WithdrawDuplicateNonce

withdraw_nonce reused, or below the 3-day pruned floor

Use a fresh millisecond timestamp

ActionNotAllowedForSpotCreditAccount

The signer is a credit account

Credit accounts cannot use this path — see Account Types

OwnerDoesNotExist for an account you know exists

The typed data does not match, so the recovered signer is a different address

Recover your own signature locally and compare it to the signing address

submission_status: "timeout"

Outcome not observed in the wait budget — it may still commit

Reconcile by cloid; never re-sign under a new nonce

Accepted, usedNonces stays false

Amount is not exactly representable in the destination token's decimals

Cap amounts at 6 decimal places; contact Native for a stalled one

Next steps

Vault ContractDeposit

Last updated