{"skill":{"slug":"orderly-deposit-withdraw","displayName":"Orderly Deposit Withdraw","summary":"Handle token deposits and withdrawals across chains, including allowance approval, vault interactions, and cross-chain operations","description":"---\nname: orderly-deposit-withdraw\ndescription: Handle token deposits and withdrawals across chains, including allowance approval, vault interactions, and cross-chain operations\n---\n\n# Orderly Network: Deposit & Withdraw\n\nThis skill covers depositing and withdrawing assets on Orderly Network, including token approvals, vault interactions, cross-chain operations, and internal transfers.\n\n## When to Use\n\n- Building deposit/withdrawal interfaces\n- Managing collateral\n- Implementing cross-chain transfers\n- Handling token approvals\n- Internal transfers between accounts\n\n## Prerequisites\n\n- Connected Web3 wallet (EVM or Solana)\n- Tokens on supported chain\n- Understanding of ERC20 approve pattern\n- Ed25519 key pair for API operations\n\n## Supported Chains & Tokens\n\n| Chain    | Chain ID  | USDC | USDT |\n| -------- | --------- | ---- | ---- |\n| Arbitrum | 42161     | ✅   | ✅   |\n| Optimism | 10        | ✅   | ✅   |\n| Base     | 8453      | ✅   | ✅   |\n| Ethereum | 1         | ✅   | ✅   |\n| Mantle   | 5000      | ✅   | -    |\n| Solana   | 900900900 | ✅   | -    |\n\n**Note**: Fetch supported chains dynamically using `GET /v1/public/chain_info?broker_id={your_broker_id}`\n\n## Deposit Flow\n\n```\n1. Fetch chain information\n2. Check if token is native (ETH, SOL)\n3. Approve token allowance (if not native)\n4. Calculate brokerHash and tokenHash\n5. Get deposit fee from vault\n6. Execute deposit transaction with fee\n7. Wait for confirmation\n8. Balance updates on Orderly\n```\n\n## React SDK: Complete Deposit Workflow\n\n### Step 1: Fetch Chain Information\n\n```typescript\nimport { useChain } from \"@orderly.network/hooks\";\n\nfunction DepositForm() {\n  const { chains, isLoading } = useChain(\"USDC\");\n\n  if (isLoading) return <div>Loading chains...</div>;\n\n  return (\n    <select>\n      {chains.map(chain => (\n        <option key={chain.id} value={chain.id}>\n          {chain.name}\n        </option>\n      ))}\n    </select>\n  );\n}\n```\n\n### Step 2: Get Deposit Metadata\n\n```typescript\nimport { useDeposit } from '@orderly.network/hooks';\n\nfunction DepositInfo({ tokenSymbol }: { tokenSymbol: string }) {\n  const { symbol, address, decimals, chainId, network } = useDeposit(tokenSymbol);\n\n  if (!address) return <div>Loading deposit info...</div>;\n\n  return (\n    <div>\n      <h3>Deposit {symbol}</h3>\n      <p><strong>Network:</strong> {network}</p>\n      <p><strong>Chain ID:</strong> {chainId}</p>\n      <p><strong>Contract Address:</strong> {address}</p>\n      <p><strong>Decimals:</strong> {decimals}</p>\n\n      <div className=\"mt-2 text-xs text-gray-500\">\n        Send only {symbol} to this address on {network}.\n      </div>\n    </div>\n  );\n}\n```\n\n### Step 3: Execute Deposit via Contract\n\n```typescript\nimport { ethers } from 'ethers';\n\nconst ERC20_ABI = [\n  'function approve(address spender, uint256 amount) returns (bool)',\n  'function allowance(address owner, address spender) view returns (uint256)',\n  'function balanceOf(address owner) view returns (uint256)',\n];\n\nconst VAULT_ABI = [\n  'function deposit(tuple(bytes32 accountId, bytes32 brokerHash, bytes32 tokenHash, uint256 tokenAmount) input) external payable',\n  'function getDepositFee(address account, tuple(bytes32 accountId, bytes32 brokerHash, bytes32 tokenHash, uint256 tokenAmount) input) view returns (uint256)',\n];\n\nasync function depositUSDC(\n  provider: ethers.BrowserProvider,\n  amount: string,\n  vaultAddress: string,\n  usdcAddress: string,\n  brokerId: string,\n  orderlyAccountId: string\n) {\n  const signer = await provider.getSigner();\n  const userAddress = await signer.getAddress();\n\n  // 1. Approve token\n  const usdc = new ethers.Contract(usdcAddress, ERC20_ABI, signer);\n  const amountWei = ethers.parseUnits(amount, 6);\n  const allowance = await usdc.allowance(userAddress, vaultAddress);\n\n  if (allowance < amountWei) {\n    console.log('Approving USDC...');\n    const approveTx = await usdc.approve(vaultAddress, ethers.MaxUint256);\n    await approveTx.wait();\n    console.log('Approved');\n  }\n\n  // 2. Calculate hashes\n  const encoder = new TextEncoder();\n  const brokerHash = ethers.keccak256(encoder.encode(brokerId));\n  const tokenHash = ethers.keccak256(encoder.encode('USDC'));\n\n  // 3. Create deposit input struct\n  const depositInput = {\n    accountId: orderlyAccountId,\n    brokerHash: brokerHash,\n    tokenHash: tokenHash,\n    tokenAmount: amountWei,\n  };\n\n  // 4. Get deposit fee\n  const vault = new ethers.Contract(vaultAddress, VAULT_ABI, signer);\n  const depositFee = await vault.getDepositFee(userAddress, depositInput);\n  console.log('Deposit fee:', ethers.formatEther(depositFee), 'ETH');\n\n  // 5. Execute deposit with fee\n  console.log('Depositing...');\n  const depositTx = await vault.deposit(depositInput, { value: depositFee });\n  const receipt = await depositTx.wait();\n  console.log('Deposited:', receipt.hash);\n\n  return receipt;\n}\n```\n\n## REST API: Get Deposit Info\n\n```typescript\n// Get supported tokens with collateral factors\nGET /v1/public/token\n\n// Response\n{\n  \"success\": true,\n  \"data\": {\n    \"rows\": [\n      {\n        \"token\": \"USDC\",\n        \"decimals\": 6,\n        \"address\": {\n          \"42161\": \"0xaf88d065e77c8cC2239327C5EDb3A432268e5831\",\n          \"10\": \"0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85\"\n        },\n        \"collateral_factor\": 1.0\n      },\n      {\n        \"token\": \"USDT\",\n        \"decimals\": 6,\n        \"address\": { ... },\n        \"collateral_factor\": 0.95\n      }\n    ]\n  }\n}\n\n// Get chain info\nGET /v1/public/chain_info?broker_id={broker_id}\n\n// Get vault balance (TVL)\nGET /v1/public/vault_balance\n```\n\n### Understanding Collateral Factors\n\nEach token has a **collateral factor** (0.0 to 1.0) that determines how much of your deposit counts toward trading collateral:\n\n| Token | Collateral Factor | $1000 Deposit = Collateral Value |\n| ----- | ----------------- | -------------------------------- |\n| USDC  | 1.0 (100%)        | $1000                            |\n| USDT  | 0.95 (95%)        | $950                             |\n| Other | Varies            | Depends on risk assessment       |\n\n**Example**: If you deposit $1000 USDT with a 0.95 collateral factor, only $950 counts as collateral for margin calculations.\n\n## Withdrawal Flow\n\n```\n1. Get withdrawal nonce\n2. Sign withdrawal message with EIP-712 (EVM) or Ed25519 (Solana)\n3. Submit signed request with Ed25519 API auth\n4. Wait for processing\n5. Receive tokens on chain\n```\n\n## React SDK: useWithdraw Hook\n\n```typescript\nimport { useWithdraw } from '@orderly.network/hooks';\n\nfunction WithdrawForm() {\n  const { withdraw, isLoading } = useWithdraw();\n\n  const [amount, setAmount] = useState('');\n  const [destination, setDestination] = useState('');\n\n  const handleWithdraw = async () => {\n    try {\n      await withdraw({\n        symbol: \"USDC\",\n        amount: \"50\",\n        address: \"0x123...\",\n        chainId: 1, // Ethereum Mainnet\n        network: \"ethereum\"\n      });\n      alert('Withdrawal initiated!');\n    } catch (error) {\n      console.error('Withdrawal failed:', error);\n    }\n  };\n\n  return (\n    <div className=\"withdraw-form\">\n      <input\n        type=\"text\"\n        placeholder=\"Amount\"\n        value={amount}\n        onChange={(e) => setAmount(e.target.value)}\n      />\n\n      <input\n        type=\"text\"\n        placeholder=\"Destination Address\"\n        value={destination}\n        onChange={(e) => setDestination(e.target.value)}\n      />\n\n      <button onClick={handleWithdraw} disabled={isLoading}>\n        {isLoading ? 'Processing...' : 'Withdraw'}\n      </button>\n    </div>\n  );\n}\n```\n\n## REST API: Withdrawal\n\n### Step 1: Get Withdrawal Nonce\n\n```typescript\nGET /v1/withdraw_nonce\n// Requires Ed25519 API authentication\n\n// Response\n{\n  \"success\": true,\n  \"data\": {\n    \"withdraw_nonce\": 123456\n  }\n}\n```\n\n### Step 2: Create EIP-712 Signature (EVM)\n\n```typescript\n// Use ON-CHAIN domain for withdrawals (see EIP-712 Domain Configuration in orderly-api-authentication)\nconst domain = {\n  name: 'Orderly',\n  version: '1',\n  chainId: 42161,\n  verifyingContract: '0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203', // Mainnet Ledger\n  // verifyingContract: '0x1826B75e2ef249173FC735149AE4B8e9ea10abff' // Testnet Ledger\n};\n\nconst types = {\n  Withdraw: [\n    { name: 'brokerId', type: 'string' },\n    { name: 'chainId', type: 'uint256' },\n    { name: 'receiver', type: 'address' },\n    { name: 'token', type: 'string' },\n    { name: 'amount', type: 'uint256' },\n    { name: 'withdrawNonce', type: 'uint64' },\n    { name: 'timestamp', type: 'uint64' },\n  ],\n};\n\nconst withdrawMessage = {\n  brokerId: 'woofi_dex',\n  chainId: 42161,\n  receiver: '0xTargetAddress',\n  token: 'USDC',\n  amount: '10000000', // 10 USDC (6 decimals)\n  withdrawNonce: withdrawNonceFromStep1,\n  timestamp: Date.now(),\n};\n\n// Sign with wallet\nconst signature = await wallet.signTypedData(domain, types, withdrawMessage);\n```\n\n### Step 3: Submit Withdrawal Request\n\n```typescript\nPOST /v1/withdraw_request\nBody: {\n  \"message\": withdrawMessage,\n  \"signature\": \"0x...\"\n}\n// Requires Ed25519 API authentication\n\n// Response\n{\n  \"success\": true,\n  \"data\": {\n    \"withdraw_id\": 123\n  },\n  \"timestamp\": 1702989203989\n}\n```\n\n### Withdrawal for Solana\n\n```typescript\nimport { DefaultSolanaWalletAdapter } from '@orderly.network/default-solana-adapter';\nimport { signAsync } from '@noble/ed25519';\n\nconst walletAdapter = new DefaultSolanaWalletAdapter();\nawait walletAdapter.active({\n  address: solanaAddress,\n  provider: {\n    signMessage: async (msg: Uint8Array) => {\n      return await signAsync(msg, privateKey);\n    }\n  }\n});\n\nconst withdrawMessage = await walletAdapter.generateWithdrawMessage({\n  brokerId: BROKER_ID,\n  receiver: solanaAddress,\n  token: 'USDC',\n  amount: '1000',\n  timestamp: Date.now(),\n  nonce: withdrawNonceFromStep1,\n});\n\nconst signature = await walletAdapter.signMessage(withdrawMessage.message);\n\n// Submit withdrawal request\nPOST /v1/withdraw_request\nBody: {\n  \"message\": withdrawMessage.message,\n  \"signature\": signature,\n  \"userAddress\": solanaAddress,\n  \"verifyingContract\": \"0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203\"\n  // \"verifyingContract\": \"0x1826B75e2ef249173FC735149AE4B8e9ea10abff\"\n}\nHeaders: { Standard Orderly Auth Headers }\n```\n\n## Cross-Chain Withdrawal\n\nCross-chain withdrawal is required when the destination chain differs from the chain where funds were deposited.\n\n```typescript\nPOST /v1/withdraw_request\nBody: {\n  \"message\": {\n    ...withdrawMessage,\n    \"chainId\": 10,\n    \"allowCrossChainWithdraw\": true\n  },\n  \"signature\": signature,\n  \"userAddress\": walletAddress,\n  \"verifyingContract\": \"0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203\"\n}\n// Requires Ed25519 API authentication\n\n// Response\n{\n  \"success\": true,\n  \"data\": {\n    \"withdraw_id\": 123\n  },\n  \"timestamp\": 1702989203989\n}\n```\n\n**Error Code 22**: If `allowCrossChainWithdraw` is `false` when a cross-chain withdrawal is needed, the API returns:\n\n```json\n{\n  \"success\": false,\n  \"code\": 22,\n  \"message\": \"Cross-chain withdrawal required for this withdrawal request\"\n}\n```\n\n## Internal Transfer\n\nTransfer funds between Orderly accounts instantly with no gas fees.\n\n### React SDK: useInternalTransfer Hook\n\n```typescript\nimport { useInternalTransfer } from '@orderly.network/hooks';\n\nfunction TransferFunds() {\n  const [amount, setAmount] = useState('');\n  const { transfer, isLoading } = useInternalTransfer();\n\n  const handleTransfer = async () => {\n    try {\n      await transfer({\n        token: 'USDC',\n        amount: parseFloat(amount),\n      });\n      alert('Transfer successful!');\n    } catch (e) {\n      console.error(e);\n    }\n  };\n\n  return (\n    <div>\n      <input\n        type=\"number\"\n        value={amount}\n        onChange={(e) => setAmount(e.target.value)}\n        placeholder=\"Amount\"\n      />\n      <button\n        onClick={handleTransfer}\n        disabled={isLoading || !amount}\n      >\n        {isLoading ? 'Transferring...' : 'Transfer'}\n      </button>\n    </div>\n  );\n}\n```\n\n### REST API: Internal Transfer (v2)\n\nInternal transfers move funds between Orderly accounts instantly with no gas fees.\n\n**Allowed scenarios:**\n\n- Main and sub-accounts\n- Accounts within the same wallet group\n- Same address across all brokers\n- Whitelisted accounts\n- Accounts under the same whitelisted broker\n\n#### EVM (EIP-712 Signing)\n\n```typescript\n// Step 1: Get transfer nonce (Ed25519 API auth)\nGET /v1/transfer_nonce\n// Requires Ed25519 API authentication\n\n// Response\n{\n  \"success\": true,\n  \"data\": {\n    \"transfer_nonce\": \"789012\"\n  }\n}\n\n// Step 2: Sign EIP-712 message with wallet (ON-CHAIN domain - see orderly-api-authentication)\nconst receiverHex = receiverAddress.startsWith('0x')\n  ? receiverAddress.slice(2)\n  : receiverAddress;\nconst receiverBytes32 = ethers.zeroPadValue('0x' + receiverHex, 32);\n\nconst domain = {\n  name: 'Orderly',\n  version: '1',\n  chainId: 42161,\n  verifyingContract: '0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203' // Mainnet Ledger\n};\n\nconst types = {\n  InternalTransfer: [\n    { name: 'receiver', type: 'bytes32' },\n    { name: 'token', type: 'string' },\n    { name: 'amount', type: 'uint256' },\n    { name: 'transferNonce', type: 'uint64' }\n  ]\n};\n\nconst message = {\n  receiver: receiverBytes32,\n  token: 'USDC',\n  amount: ethers.parseUnits('100', 6),\n  transferNonce: Number(transferNonce)\n};\n\nconst signature = await wallet.signTypedData(domain, types, message);\n\n// Step 3: Submit to v2 endpoint\nPOST /v2/internal_transfer\nBody: {\n  \"message\": {\n    \"receiver\": \"0xReceiverAddress\",\n    \"token\": \"USDC\",\n    \"amount\": \"100\",\n    \"transferNonce\": \"789012\",\n    \"chainId\": \"42161\",\n    \"chainType\": \"EVM\"\n  },\n  \"signature\": \"0x...\",\n  \"userAddress\": \"0xSenderAddress\",\n  \"verifyingContract\": \"0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203\"\n}\n// Requires Ed25519 API authentication\n```\n\n#### Solana (Adapter Signing)\n\n```typescript\nimport { DefaultSolanaWalletAdapter } from '@orderly.network/default-solana-adapter';\n\n// Step 1: Get transfer nonce\nGET /v1/transfer_nonce\n// Requires Ed25519 API authentication\n\n// Step 2: Generate and sign message with adapter\nconst walletAdapter = new DefaultSolanaWalletAdapter();\n// ... initialize adapter ...\n\nconst transferMessage = await walletAdapter.generateTransferMessage({\n  receiver: receiverAddress,\n  token: 'USDC',\n  amount: '100',\n  transferNonce: nonceFromStep1,\n  chainId: 900900900,\n  chainType: 'SOL'\n});\n\nconst signature = await walletAdapter.signMessage(transferMessage.message);\n\n// Step 3: Submit to v2 endpoint\nPOST /v2/internal_transfer\nBody: {\n  \"message\": {\n    \"receiver\": \"SolanaReceiverAddress\",\n    \"token\": \"USDC\",\n    \"amount\": \"100\",\n    \"transferNonce\": \"789012\",\n    \"chainId\": \"900900900\",\n    \"chainType\": \"SOL\"\n  },\n  \"signature\": signature,\n  \"userAddress\": solanaAddress,\n  \"verifyingContract\": \"0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203\"\n}\n// Requires Ed25519 API authentication\n```\n\n**Important Notes:**\n\n- Transfer nonce must be obtained before each transfer (via Ed25519 auth)\n- The actual transfer requires wallet signature (EIP-712 for EVM, adapter for Solana)\n- Receiver address must be converted to bytes32 for EVM signing\n- Internal transfers are not available for Delegate Signer accounts\n- v2 endpoint includes optional memo field\n\n## Asset History\n\n```typescript\n// Get deposit/withdrawal history\nGET /v1/asset/history?token=USDC&side=DEPOSIT&page=1&size=20\n// Requires Ed25519 API authentication\n\n// Query parameters (all optional):\n//   token: USDC\n//   side: DEPOSIT | WITHDRAW\n//   status: NEW | CONFIRM | PROCESSING | COMPLETED | FAILED | PENDING_REBALANCE\n//   start_t: timestamp (e.g., 1702989203989)\n//   end_t: timestamp (e.g., 1702989203989)\n//   page: 1 (default)\n//   size: 20 (default)\n\n// Response\n{\n  \"success\": true,\n  \"data\": {\n    \"rows\": [\n      {\n        \"id\": \"tx_123\",\n        \"token\": \"USDC\",\n        \"side\": \"DEPOSIT\",\n        \"amount\": \"1000.00\",\n        \"status\": \"COMPLETED\",\n        \"tx_hash\": \"0x...\",\n        \"chain_id\": 42161,\n        \"created_at\": 1699123456\n      }\n    ],\n    \"total\": 50\n  }\n}\n```\n\n## Testnet Faucet\n\n```typescript\n// Get testnet USDC (testnet only)\nPOST /v1/faucet/usdc\nHeaders: { Standard Orderly Auth Headers }\n\n// EVM Testnet\nPOST https://testnet-operator-evm.orderly.org/v1/faucet/usdc\n\n// Solana Testnet\nPOST https://testnet-operator-sol.orderly.org/v1/faucet/usdc\n\n// Each account can use faucet max 5 times\n// EVM: 1000 USDC per request\n// Solana: 100 USDC per request\n```\n\n## Get Contract Addresses\n\n```typescript\nimport { getContractAddresses } from '@orderly.network/contracts';\n\n// EVM chains\nconst addresses = getContractAddresses('arbitrum', 'mainnet');\n// {\n//   vault: '0x816f722424B49Cf1275cc86DA9840Fbd5a6167e9',\n//   usdc: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n//   usdt: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9'\n// }\n\n// Or fetch dynamically\nGET /v1/public/chain_info?broker_id={broker_id}\n```\n\n### Verifying Contract Addresses\n\n| Environment | Verifying Contract                           |\n| ----------- | -------------------------------------------- |\n| Mainnet     | `0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203` |\n| Testnet     | `0x1826B75e2ef249173FC735149AE4B8e9ea10abff` |\n\n## Common Issues\n\n### \"Insufficient allowance\" error\n\n- Call `approve()` before deposit\n- Check if approval transaction confirmed\n- Ensure you're approving the vault address, not token address\n\n### \"Insufficient balance\" error\n\n- Check wallet balance, not just Orderly balance\n- Ensure correct token decimals\n- Account for deposit fee (native token like ETH)\n\n### Withdrawal stuck in \"PROCESSING\"\n\n- Check `/v1/asset/history` for status updates\n- Contact support if stuck > 24 hours\n\n### \"Cross-chain withdrawal required\" error (Code 22)\n\n- Set `allow_cross_chain_withdrawal: true` in request body\n- Destination chain differs from deposit chain\n- Expect longer processing time\n\n### Internal transfer not available\n\n- Not available for Delegate Signer accounts\n- Ensure accounts meet allowed transfer scenarios\n- Check that Ed25519 authentication is properly signed\n\n### EIP-712 signature mismatch\n\nCommon mistakes:\n\n- `withdrawNonce` type should be `uint64`, not `uint256`\n- `token` type should be `string`, not `address`\n- Missing `fee` field in message\n- Wrong verifying contract address\n- Wrong chain ID in domain\n\n## Related Skills\n\n- **orderly-api-authentication** - Ed25519 API authentication and EIP-712 signing\n- **orderly-sdk-react-hooks** - React SDK hooks reference\n- **orderly-onboarding** - Account setup and getting started\n","tags":{"latest":"1.0.0"},"stats":{"comments":0,"downloads":654,"installsAllTime":25,"installsCurrent":0,"stars":0,"versions":1},"createdAt":1772814212128,"updatedAt":1779077687799},"latestVersion":{"version":"1.0.0","createdAt":1772814212128,"changelog":"Orderly Network: Deposit & Withdraw v1.0.0\n\n- Initial release enabling deposit and withdrawal of assets across supported EVM and Solana chains.\n- Includes token approvals, vault interaction, internal transfers, and full cross-chain operation support.\n- Provides React SDK hooks and code samples for both deposit and withdrawal flows.\n- Details API endpoints for dynamic chain/token info, collateral factor handling, and withdrawal nonce management.\n- Outlines native and ERC20 token flows, fees, and margin collateral considerations.","license":null},"metadata":null,"owner":{"handle":"tarnadas","userId":"s178j8xey931dpnbwn6zr2ps19884c3b","displayName":"Mario Reder","image":"https://avatars.githubusercontent.com/u/5855071?v=4"},"moderation":{"isSuspicious":false,"isMalwareBlocked":false,"verdict":"clean","reasonCodes":["review.llm_review"],"summary":"Review: review.llm_review","engineVersion":"v2.4.24","updatedAt":1780089783961}}