> ## Documentation Index
> Fetch the complete documentation index at: https://docs.baanx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Withdrawal Operations

> Withdraw funds from custodial wallets to whitelisted external addresses

## Overview

Withdrawals allow users to transfer cryptocurrency from their custodial internal wallets to external blockchain addresses. All withdrawals require the destination address to be whitelisted for security and compliance.

## Withdrawal Flow

```mermaid theme={null}
sequenceDiagram
    participant User
    participant App
    participant API
    participant Whitelist
    participant Blockchain

    User->>App: Request withdrawal
    App->>API: GET /v1/wallet/whitelist
    API-->>App: Return whitelisted addresses
    App-->>User: Display available addresses
    User->>App: Select address & amount
    App->>API: POST /v1/wallet/internal/withdraw
    API->>Whitelist: Verify address
    Whitelist-->>API: Confirmed
    API->>Blockchain: Submit transaction
    Blockchain-->>API: Transaction hash
    API-->>App: Return success + txHash
    App-->>User: Show confirmation
```

## Withdrawing from Internal Wallets

Execute a withdrawal from an internal custodial wallet to a whitelisted address:

<CodeGroup>
  ```bash Standard Withdrawal theme={null}
  curl -X POST "https://api.example.com/v1/wallet/internal/withdraw" \
    -H "x-client-key: YOUR_PUBLIC_KEY" \
    -H "Authorization: Bearer USER_ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "amount": "100.50",
      "recipientAddrss": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
      "recipientMemo": null,
      "sourceAddress": "0x0a4b21fa733e9aeaddbf070302a85c559de13c4d",
      "sourceMemo": null,
      "currency": "usdc"
    }'
  ```

  ```bash XRP with Memo theme={null}
  curl -X POST "https://api.example.com/v1/wallet/internal/withdraw" \
    -H "x-client-key: YOUR_PUBLIC_KEY" \
    -H "Authorization: Bearer USER_ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "amount": "50",
      "recipientAddrss": "rNxp4h8apvRis6mJf9Sh8C6iRxfrDWN7AA",
      "recipientMemo": "66",
      "sourceAddress": "rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY",
      "sourceMemo": "78",
      "currency": "xrp"
    }'
  ```

  ```json Response theme={null}
  {
    "success": true
  }
  ```
</CodeGroup>

### Request Parameters

| Field             | Required | Description                                            |
| ----------------- | -------- | ------------------------------------------------------ |
| `amount`          | Yes      | Amount to withdraw (as string for precision)           |
| `recipientAddrss` | Yes      | Destination address (must be whitelisted)              |
| `recipientMemo`   | No       | Destination memo/tag (required for XRP, Stellar, etc.) |
| `sourceAddress`   | Yes      | Source wallet address (from GET /v1/wallet/internal)   |
| `sourceMemo`      | No       | Source wallet memo (if applicable)                     |
| `currency`        | Yes      | Currency to withdraw                                   |

<Note>
  Note the intentional typo in the API: `recipientAddrss` (with double 's'). This is the actual field name in the current API version.
</Note>

<Warning>
  The `recipientAddrss` (destination address) **must** be whitelisted before withdrawal. Attempting to withdraw to a non-whitelisted address will fail with a validation error.
</Warning>

## Getting Source Address Details

Before withdrawing, retrieve the source wallet address from the internal wallets list:

<CodeGroup>
  ```bash Request theme={null}
  curl -X GET "https://api.example.com/v1/wallet/internal" \
    -H "x-client-key: YOUR_PUBLIC_KEY" \
    -H "Authorization: Bearer USER_ACCESS_TOKEN"
  ```

  ```json Response theme={null}
  [
    {
      "id": "098aeb90-e7f7-4f81-bc2e-4963330122c5",
      "balance": "150.50",
      "currency": "usdc",
      "address": "0x0a4b21fa733e9aeaddbf070302a85c559de13c4d",
      "addressMemo": null,
      "addressId": "7c1839ee-918e-4787-b74f-deeb48ead58b",
      "type": "INTERNAL"
    },
    {
      "id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "balance": "75.00",
      "currency": "xrp",
      "address": "rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY",
      "addressMemo": "78",
      "addressId": "8d9e0f1g-2h3i-4j5k-6l7m-8n9o0p1q2r3s",
      "type": "INTERNAL"
    }
  ]
  ```
</CodeGroup>

Use the `address` field as `sourceAddress` and `addressMemo` (if not null) as `sourceMemo` in withdrawal requests.

## Network-Specific Considerations

### EVM Networks (Ethereum, Linea)

<Tabs>
  <Tab title="Requirements">
    * Standard ERC-20 token withdrawal
    * No memo required
    * Gas fees paid from wallet balance
    * Typical confirmation: 5-15 minutes (Ethereum), 1-2 minutes (Linea)

    ```json theme={null}
    {
      "amount": "100",
      "recipientAddrss": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
      "recipientMemo": null,
      "sourceAddress": "0x0a4b21fa733e9aeaddbf070302a85c559de13c4d",
      "sourceMemo": null,
      "currency": "usdc"
    }
    ```
  </Tab>

  <Tab title="Gas Fees">
    Gas fees are automatically deducted from the withdrawal amount or wallet balance. Users receive the full requested amount at the destination.

    **Example:**

    * Requested withdrawal: 100 USDC
    * Network fee: \~$0.50 (Linea) or ~$2-5 (Ethereum)
    * User receives: 100 USDC (platform covers fees)
  </Tab>
</Tabs>

### Solana

<Tabs>
  <Tab title="Requirements">
    * SPL token withdrawal
    * No memo required
    * Transaction fees paid by platform
    * Typical confirmation: 5-30 seconds

    ```json theme={null}
    {
      "amount": "50",
      "recipientAddrss": "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK",
      "recipientMemo": null,
      "sourceAddress": "DfKNsYfrCEHb7ScJkuMTtPTeDiyjmBBm9NMHnbR7uFHz",
      "sourceMemo": null,
      "currency": "usdc"
    }
    ```
  </Tab>

  <Tab title="Transaction Fees">
    Solana transaction fees are typically 0.000005 SOL (\~\$0.0001), making it the most cost-effective network for withdrawals.
  </Tab>
</Tabs>

### XRP Ledger

<Tabs>
  <Tab title="Requirements">
    * Destination tag (memo) **required** for exchange addresses
    * Native XRP transfer
    * Typical confirmation: 3-5 seconds
    * Minimum withdrawal: 20 XRP (reserve requirement)

    ```json theme={null}
    {
      "amount": "50",
      "recipientAddrss": "rNxp4h8apvRis6mJf9Sh8C6iRxfrDWN7AA",
      "recipientMemo": "66",
      "sourceAddress": "rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY",
      "sourceMemo": "78",
      "currency": "xrp"
    }
    ```
  </Tab>

  <Tab title="Destination Tags">
    **Always include destination tag when provided:**

    * Exchanges (Binance, Coinbase, etc.) require destination tags
    * Personal wallets may not require tags
    * Missing tags can result in lost funds or delayed processing

    <Warning>
      Verify the destination tag with the recipient before withdrawal. Incorrect tags may send funds to the wrong account.
    </Warning>
  </Tab>
</Tabs>

## Transaction Tracking

After successful withdrawal, the API returns `success: true`. Transaction details appear in wallet history:

<CodeGroup>
  ```bash Request theme={null}
  curl -X GET "https://api.example.com/v1/wallet/history?walletId=098aeb90&walletType=INTERNAL&walletCurrency=usdc&page=0" \
    -H "x-client-key: YOUR_PUBLIC_KEY" \
    -H "Authorization: Bearer USER_ACCESS_TOKEN"
  ```

  ```json Response theme={null}
  [
    {
      "name": "Withdrawal",
      "amount": "100.50",
      "currency": "usdc",
      "sign": "debit",
      "date": "2024-02-02T15:01:09.091Z"
    }
  ]
  ```
</CodeGroup>

<Note>
  The transaction history shows withdrawal events but does not include on-chain transaction hashes. To get the transaction hash for blockchain explorer tracking, implement additional transaction logging in your application.
</Note>

## Implementation Examples

### Complete Withdrawal Flow

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function withdrawToWhitelist(userToken, currency, amount, recipientIndex = 0) {
    // 1. Get internal wallets
    const walletsResponse = await fetch(
      'https://api.example.com/v1/wallet/internal',
      {
        headers: {
          'x-client-key': 'YOUR_PUBLIC_KEY',
          'Authorization': `Bearer ${userToken}`
        }
      }
    );
    const wallets = await walletsResponse.json();

    // 2. Find source wallet
    const sourceWallet = wallets.find(w => w.currency === currency);
    if (!sourceWallet) {
      throw new Error(`No ${currency} wallet found`);
    }

    // 3. Check balance
    if (parseFloat(sourceWallet.balance) < parseFloat(amount)) {
      throw new Error('Insufficient balance');
    }

    // 4. Get whitelisted addresses
    const whitelistResponse = await fetch(
      `https://api.example.com/v1/wallet/whitelist?currency=${currency}`,
      {
        headers: {
          'x-client-key': 'YOUR_PUBLIC_KEY',
          'Authorization': `Bearer ${userToken}`
        }
      }
    );
    const whitelisted = await whitelistResponse.json();

    if (whitelisted.length === 0) {
      throw new Error('No whitelisted addresses. Please add one first.');
    }

    const recipient = whitelisted[recipientIndex];

    // 5. Execute withdrawal
    const withdrawResponse = await fetch(
      'https://api.example.com/v1/wallet/internal/withdraw',
      {
        method: 'POST',
        headers: {
          'x-client-key': 'YOUR_PUBLIC_KEY',
          'Authorization': `Bearer ${userToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          amount: amount.toString(),
          recipientAddrss: recipient.walletAddress,
          recipientMemo: recipient.walletMemo,
          sourceAddress: sourceWallet.address,
          sourceMemo: sourceWallet.addressMemo,
          currency: currency
        })
      }
    );

    if (!withdrawResponse.ok) {
      const error = await withdrawResponse.json();
      throw new Error(error.message);
    }

    return await withdrawResponse.json();
  }

  // Usage
  try {
    const result = await withdrawToWhitelist(userToken, 'usdc', '100.50');
    console.log('Withdrawal successful:', result);
  } catch (error) {
    console.error('Withdrawal failed:', error.message);
  }
  ```

  ```python Python theme={null}
  import requests
  from typing import Dict, Optional

  def withdraw_to_whitelist(
      user_token: str,
      currency: str,
      amount: str,
      recipient_index: int = 0
  ) -> Dict:
      base_url = 'https://api.example.com'
      headers = {
          'x-client-key': 'YOUR_PUBLIC_KEY',
          'Authorization': f'Bearer {user_token}'
      }

      # 1. Get internal wallets
      wallets_response = requests.get(
          f'{base_url}/v1/wallet/internal',
          headers=headers
      )
      wallets = wallets_response.json()

      # 2. Find source wallet
      source_wallet = next((w for w in wallets if w['currency'] == currency), None)
      if not source_wallet:
          raise ValueError(f'No {currency} wallet found')

      # 3. Check balance
      if float(source_wallet['balance']) < float(amount):
          raise ValueError('Insufficient balance')

      # 4. Get whitelisted addresses
      whitelist_response = requests.get(
          f'{base_url}/v1/wallet/whitelist',
          headers=headers,
          params={'currency': currency}
      )
      whitelisted = whitelist_response.json()

      if len(whitelisted) == 0:
          raise ValueError('No whitelisted addresses. Please add one first.')

      recipient = whitelisted[recipient_index]

      # 5. Execute withdrawal
      withdraw_response = requests.post(
          f'{base_url}/v1/wallet/internal/withdraw',
          headers={**headers, 'Content-Type': 'application/json'},
          json={
              'amount': str(amount),
              'recipientAddrss': recipient['walletAddress'],
              'recipientMemo': recipient['walletMemo'],
              'sourceAddress': source_wallet['address'],
              'sourceMemo': source_wallet['addressMemo'],
              'currency': currency
          }
      )

      withdraw_response.raise_for_status()
      return withdraw_response.json()

  # Usage
  try:
      result = withdraw_to_whitelist(user_token, 'usdc', '100.50')
      print('Withdrawal successful:', result)
  except Exception as error:
      print(f'Withdrawal failed: {str(error)}')
  ```
</CodeGroup>

### Withdrawal UI Component

<CodeGroup>
  ```jsx React Component theme={null}
  import { useState, useEffect } from 'react';

  function WithdrawalForm({ userToken, currency }) {
    const [wallets, setWallets] = useState([]);
    const [whitelisted, setWhitelisted] = useState([]);
    const [amount, setAmount] = useState('');
    const [selectedAddress, setSelectedAddress] = useState('');
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState('');

    useEffect(() => {
      fetchData();
    }, [currency]);

    const fetchData = async () => {
      const [walletsRes, whitelistRes] = await Promise.all([
        fetch('https://api.example.com/v1/wallet/internal', {
          headers: {
            'x-client-key': 'YOUR_PUBLIC_KEY',
            'Authorization': `Bearer ${userToken}`
          }
        }),
        fetch(`https://api.example.com/v1/wallet/whitelist?currency=${currency}`, {
          headers: {
            'x-client-key': 'YOUR_PUBLIC_KEY',
            'Authorization': `Bearer ${userToken}`
          }
        })
      ]);

      const walletsData = await walletsRes.json();
      const whitelistData = await whitelistRes.json();

      setWallets(walletsData.filter(w => w.currency === currency));
      setWhitelisted(whitelistData);
    };

    const handleWithdraw = async (e) => {
      e.preventDefault();
      setLoading(true);
      setError('');

      try {
        const sourceWallet = wallets[0];
        const recipient = whitelisted.find(w => w.id === selectedAddress);

        if (parseFloat(amount) > parseFloat(sourceWallet.balance)) {
          throw new Error('Insufficient balance');
        }

        const response = await fetch(
          'https://api.example.com/v1/wallet/internal/withdraw',
          {
            method: 'POST',
            headers: {
              'x-client-key': 'YOUR_PUBLIC_KEY',
              'Authorization': `Bearer ${userToken}`,
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              amount: amount,
              recipientAddrss: recipient.walletAddress,
              recipientMemo: recipient.walletMemo,
              sourceAddress: sourceWallet.address,
              sourceMemo: sourceWallet.addressMemo,
              currency: currency
            })
          }
        );

        if (!response.ok) {
          const errorData = await response.json();
          throw new Error(errorData.message);
        }

        alert('Withdrawal successful!');
        setAmount('');
        setSelectedAddress('');
        await fetchData();
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    const sourceWallet = wallets[0];

    return (
      <div className="withdrawal-form">
        <h3>Withdraw {currency.toUpperCase()}</h3>

        {sourceWallet && (
          <div className="balance-display">
            <label>Available Balance</label>
            <span className="balance">
              {sourceWallet.balance} {currency.toUpperCase()}
            </span>
          </div>
        )}

        {whitelisted.length === 0 ? (
          <div className="warning">
            No whitelisted addresses. Please add one before withdrawing.
          </div>
        ) : (
          <form onSubmit={handleWithdraw}>
            <div className="form-field">
              <label>Amount</label>
              <input
                type="number"
                step="0.01"
                value={amount}
                onChange={(e) => setAmount(e.target.value)}
                placeholder="0.00"
                required
              />
            </div>

            <div className="form-field">
              <label>Destination Address</label>
              <select
                value={selectedAddress}
                onChange={(e) => setSelectedAddress(e.target.value)}
                required
              >
                <option value="">Select address...</option>
                {whitelisted.map(addr => (
                  <option key={addr.id} value={addr.id}>
                    {addr.name} - {addr.walletAddress.slice(0, 10)}...
                    {addr.walletMemo && ` (Memo: ${addr.walletMemo})`}
                  </option>
                ))}
              </select>
            </div>

            {error && <div className="error">{error}</div>}

            <button type="submit" disabled={loading || !amount || !selectedAddress}>
              {loading ? 'Processing...' : 'Withdraw'}
            </button>
          </form>
        )}
      </div>
    );
  }
  ```
</CodeGroup>

## Error Handling

### Insufficient Balance

```json theme={null}
{
  "error": "Insufficient balance",
  "code": "WALLET_INSUFFICIENT_BALANCE",
  "message": "Wallet balance is insufficient for this withdrawal"
}
```

**Solution:** Check wallet balance before withdrawal. Display available balance to user.

### Address Not Whitelisted

```json theme={null}
{
  "error": "Address not whitelisted",
  "code": "WALLET_ADDRESS_NOT_WHITELISTED",
  "message": "Recipient address must be whitelisted before withdrawal"
}
```

**Solution:** Add the address to whitelist first using `POST /v1/wallet/whitelist`.

### Invalid Source Address

```json theme={null}
{
  "error": "Invalid source address",
  "code": "WALLET_INVALID_SOURCE",
  "message": "Source wallet not found or does not belong to this user"
}
```

**Solution:** Verify the source address and memo match values from `GET /v1/wallet/internal`.

### Missing Required Memo

```json theme={null}
{
  "error": "Memo required",
  "code": "WALLET_MEMO_REQUIRED",
  "message": "This network requires a recipient memo/destination tag"
}
```

**Solution:** Provide `recipientMemo` for XRP and similar networks that require destination tags.

### Amount Below Minimum

```json theme={null}
{
  "error": "Amount too low",
  "code": "WALLET_AMOUNT_TOO_LOW",
  "message": "Withdrawal amount below minimum threshold"
}
```

**Solution:** Check network-specific minimum withdrawal amounts (e.g., XRP has a 20 XRP reserve requirement).

## Best Practices

<Card title="Validate Before Submission" icon="check-circle">
  Check wallet balance, address whitelist status, and required fields before submitting withdrawal requests to provide better user feedback.
</Card>

<Card title="Show Transaction Status" icon="spinner">
  Display clear status indicators during withdrawal processing. Blockchain confirmations can take seconds to minutes depending on the network.
</Card>

<Card title="Confirm with Users" icon="triangle-exclamation">
  Always show a confirmation dialog before executing withdrawals, displaying the amount, destination address, and expected fees.
</Card>

<Card title="Handle Memos Carefully" icon="tag">
  For XRP and similar networks, prominently display the destination tag requirement and validate that it's included in the withdrawal request.
</Card>

<Card title="Display Network Fees" icon="coins">
  Inform users about approximate network fees for different blockchains to help them choose cost-effective withdrawal options.
</Card>

<Card title="Implement Retry Logic" icon="rotate">
  Network issues can cause temporary failures. Implement exponential backoff retry logic for transient errors.
</Card>

<Card title="Test with Small Amounts" icon="vial">
  Encourage users to test new whitelisted addresses with small withdrawal amounts before large transfers.
</Card>

## Security Considerations

<Warning>
  **Irreversible Transactions**: Blockchain transactions cannot be reversed. Double-check all withdrawal details before submission.
</Warning>

<Warning>
  **Whitelist Verification**: Only withdraw to addresses you control and have verified. The whitelist prevents unauthorized destinations but cannot prevent user error.
</Warning>

<Note>
  **Rate Limiting**: Implement rate limiting on withdrawal requests to prevent abuse and protect user accounts from rapid fund drainage attacks.
</Note>

<Note>
  **Two-Factor Authentication**: Consider requiring 2FA or email confirmation for withdrawal requests, especially for large amounts.
</Note>

## US Environment

For US-based users, route withdrawal requests to the US environment:

<Tabs>
  <Tab title="Header Method">
    ```bash theme={null}
    curl -X POST "https://api.example.com/v1/wallet/internal/withdraw" \
      -H "x-client-key: YOUR_PUBLIC_KEY" \
      -H "Authorization: Bearer USER_ACCESS_TOKEN" \
      -H "x-us-env: true" \
      -H "Content-Type: application/json" \
      -d '{...}'
    ```
  </Tab>

  <Tab title="Query Parameter Method">
    ```bash theme={null}
    curl -X POST "https://api.example.com/v1/wallet/internal/withdraw?region=us" \
      -H "x-client-key: YOUR_PUBLIC_KEY" \
      -H "Authorization: Bearer USER_ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{...}'
    ```
  </Tab>
</Tabs>

## Next Steps

<CardGroup cols={2}>
  <Card title="Whitelist Management" icon="list-check" href="/guides/wallet/custodial/whitelist">
    Learn how to manage whitelisted addresses
  </Card>

  <Card title="Internal Wallets" icon="wallet" href="/guides/wallet/custodial/internal-wallets">
    Understand internal wallet operations
  </Card>

  <Card title="Transaction History" icon="clock-rotate-left" href="/guides/wallet/overview#transaction-history">
    View wallet transaction history
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference">
    Complete API documentation
  </Card>
</CardGroup>
