Skip to main content
Moving value in the Derive v3 API is always a signed action: your wallet (or a scoped session key) EIP-712-signs an Action envelope, and the protocol re-verifies the signature before applying it. Four methods cover the distinct destinations:

private/transfer_spot

Move collateral between two subaccounts you own.

private/transfer_spot_external

Send collateral to a subaccount owned by a different wallet, bounded by your recipient allow-list.

private/withdraw

Withdraw collateral on-chain to an L1 recipient address.

private/transfer_positions

Move open positions between subaccounts, booked as an RFQ trade.
Each method needs a specific protocol scope on the signing key, and each carries a nonce, signer, signature, and signature_expiry_sec built exactly as described in Action signing. The action module for each flow is filled in by the server and does not appear on the wire — it is part of the signed struct hash and must match the deployment’s module address in Action signing.
Amounts, prices, and fees are human decimals on the wire — decimal strings (e.g. "100.5") or JSON numbers. The exception is private/withdraw, whose on-chain amount uses the asset’s native ERC-20 decimals (see below).

Transfer collateral between your subaccounts

private/transfer_spot moves a spot balance from one of your subaccounts to another subaccount you own (existing, or a new one created in the same call). Positions are not moved by this method — use private/transfer_positions for that. Required scope: transfer:existing_subaccount or transfer:new_subaccount (see Access scopes).

Transfer positions between subaccounts

private/transfer_positions moves open positions from one subaccount to another. It is booked as an RFQ-module trade: both sides sign a transfer quote over the same legs, and the transfer clears at the agreed prices. The signing key needs a transfer:* scope covering the destination (transfer:existing_subaccount, transfer:new_subaccount, or transfer:different_owner_subaccount). Both quotes sign the same legs hash; each side authorizes its own max_fee. The response returns the resulting maker_quote and taker_quote.

Transfer collateral to another wallet

private/transfer_spot_external sends collateral to a subaccount owned by a different wallet. The destination owner must be on the sender’s whitelisted-recipient allow-list, and the signing key needs transfer:different_owner_subaccount.

Managing the recipient allow-list

Withdrawals or transfers to external subaccounts can only reach wallets you have explicitly whitelisted. Manage the list with private/update_whitelisted_recipients. Permissions that can modify this list are:
  • owner
  • admin scoped session key

Withdraw

private/withdraw removes collateral from a subaccount and settles it to an Ethereum L1 recipient. It is a signed action requiring the withdraw scope. Withdrawals signed by a session key must go to an address on the owner’s whitelisted-recipient allow-list, including the owner wallet itself. See Managing the recipient allow-list above.
amount_in_underlying is denominated in the asset’s native ERC-20 decimals (e.g. 6 for USDC), not the decimal convention used by the transfer methods above. Match the on-chain token’s decimals exactly.
recipient is optional and defaults to the account’s owner wallet — not the signer, and not the subaccount. The signature commits to it, so the server cannot redirect a payout: a recipient disagreeing with what you signed fails signature verification rather than being honored.A session key may only pay out to an address on the owner’s whitelist — including the owner wallet itself, which is not whitelisted implicitly. Whitelist it before granting a key the withdraw scope, or the withdrawal is rejected with RecipientNotWhitelisted. The owner and admin-scoped keys bound to no subaccounts skip the whitelist entirely.
public/withdraw_debug returns the EIP-712-encoded data and hashes for a withdraw action, so you can byte-compare against your local signing to diagnose rejected signatures. It is a debugging aid, not a required step — see Action signing for the full set of *_debug signing-preview helpers.

Forwarded withdrawals

Some tokens come to Derive over a bridge from their home chain. You can withdraw these tokens back to the home chain in one step. The withdrawal goes to a withdraw address: an Ethereum address bound to one recipient on one destination chain. When the payout lands there, Derive bridges it to the recipient. Derive pays the bridge fee. You pay only the normal withdrawal fee. chain_id is the destination’s chain id. Solana uses 101 (mainnet-beta) and 103 (devnet); every other chain_id is an EVM chain. A withdraw address forwards only the tokens in the routes list that private/register_withdraw_address returns. Send it nothing else: private/withdraw does not check the token, and a token the address doesn’t forward is held there (held, no_route) until Derive recovers it and refunds your wallet by hand.

Flow

1

Get the withdraw address

Call private/register_withdraw_address with the destination chain_id and recipient.
2

Whitelist it (scoped session keys only)

Skip this step if the owner wallet, or an admin key bound to no subaccounts, signs the withdrawal. A scoped session key can only withdraw to a whitelisted address, for EVM and Solana destinations alike. The owner first adds the withdraw address with private/update_whitelisted_recipients, or private/withdraw fails with 11033 transfer_not_whitelisted (the protocol’s RecipientNotWhitelisted).
3

Withdraw to it

Call private/withdraw with recipient set to the withdraw address. The signature commits to the recipient, so get the address before you sign.
4

Track it

private/get_withdrawal_history shows the forward under forward (see below).
Withdrawals signed by the owner wallet or by an admin key bound to no subaccounts need no whitelisting. Every other session key needs the withdraw address on whitelisted_recipients first. The whitelisted flag in private/get_withdraw_addresses shows which addresses are ready.

Register a withdraw address

private/register_withdraw_address returns the withdraw address for one (recipient, chain_id) and starts forwarding for it. It is idempotent: the same params return the same address. The caller must be authenticated as wallet.
string
required
Your account wallet. It must match the authenticated wallet.
integer
required
Destination chain, from the table above.
string
required
Recipient on the destination chain.
  • EVM chain: must be your account wallet. Forwards go to the same address on the home chain.
  • Solana: a base58 wallet public key. It must be on the ed25519 curve, so a program address or a token account is rejected: the bridge credits the recipient’s token account itself. Derive screens the address before it returns a withdraw address.
recipient is the 32-byte form the bridge uses. recipient_display is the address as you gave it, normalised.

List your withdraw addresses

private/get_withdraw_addresses {wallet} returns your EVM withdraw addresses (their recipient is your wallet) and every withdraw address on your whitelisted_recipients, oldest first. A Solana withdraw address you registered but never whitelisted is not listed: keep the register_withdraw_address result for it, or call register_withdraw_address again (it returns the same address).
Response
address_kind is evm or solana. whitelisted tells you whether a scoped session key can withdraw to the address now. Owner and unbounded admin signatures don’t need it.

Track a forwarded withdrawal

Each private/get_withdrawal_history entry whose recipient is a withdraw address has a forward object: A token amount below the bridge’s smallest unit stays at the withdraw address and goes with the next forward, so the amount delivered can be slightly lower than amount_forwarded. Explorer links: LayerZero https://layerzeroscan.com/tx/<forward_tx> (testnet https://testnet.layerzeroscan.com/tx/<forward_tx>), CCIP https://ccip.chain.link/msg/<message_id>.