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.
Minimum —
min_withdraw_atomsfrom theaccountingWithdrawTokensentry whosechain_idandasset_idmatch your destination chain and asset.Fee —
withdraw_fee_atomsfor the asset fromassets. It is recorded, not deducted: you sign the gross amount, Native debits the gross, and the destination chain releasesamount − fee.amountmust 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 theerror.codebefore you sign a fresh action.timeout— not a rejection. The withdrawal may still commit, so reconcile bycloidwithtxStatusByCloidinstead 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
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 ContractDepositLast updated