# Enable Bulk Withdrawals of ERC20 Tokens

Source: https://developers.bitgo.com/docs/withdraw-enable-bulk-erc20

## Overview

To make bulk withdrawals of ERC20 tokens, you must approve a batcher smart contract transferring tokens on your behalf. When granting permission, you can set a limit that ensures transactions of the token can't exceed a specified amount.

Approving the batcher smart contract is a one-time operation, but each token requires a separate approval in each wallet. For example, once you enable `token A` in `wallet 1`:

* To bulk withdraw `token A` from `wallet 2`, make a separate approval for `token A` in `wallet 2`.
* To bulk withdraw `token B` from `wallet 1`, make a separate approval for `token B` in `wallet 1`.

In addition, if you want to change the limit amount for bulk withdrawals, you must make another approval.

## Prerequisites

* [Get Started](/docs/get-started-intro)
* [Create Wallets](/docs/wallets-create-wallets)
* [Deposit Assets](/docs/deposit-assets) (you must have the ERC20 token in the wallet)

## Cookbooks

Need just the steps? Expand a cookbook below to get started:

<Cookbook slug="enable-bulk-erc20-curl" title="Enable Bulk ERC20 (cURL)" />
<Cookbook slug="enable-bulk-erc20-javascript" title="Enable Bulk ERC20 (JavaScript)" />

## 1. (Optional) Check Token Allowance

Before approving tokens, you can check if your wallet has already approved the batcher contract:

>Endpoint: [Get the token allowance for a specific token contract address](/reference/v2wallettokenallowance)

```shell cURL
export COIN="<ASSET_ID_OF_NATIVE_COIN>"
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"

curl -X GET \
  https://app.bitgo-test.com/api/v2/$COIN/wallet/$WALLET_ID/allowance \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

#### Step Result

In this example, the large number shown in the response is `2^256-1`, which is the maximum approval amount (unlimited).

```json
{
  "allowance": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
}
```

## 2. Approve Batcher Smart Contract

>Endpoint: [Build an approval transaction for ERC20 token](/reference/v2wallettokenapprovalbuild)

<Tabs>
<Tab title="Custody MPC">

```javascript
async function approveToken() {
  const walletInstance = await bitgo
    .coin('opeth')
    .wallets()
    .get({ id: '<YOUR_WALLET_ID>' });
  
  // For custodial MPC wallets, use sendMany with type: 'tokenApproval'
  const result = await walletInstance.sendMany({
    isTss: true,
    type: 'tokenApproval',
    tokenName: 'opeth:usdc'
  });
  
  return result;
}
```
```shell cURL
export COIN="<ASSET_ID_OF_NATIVE_COIN>"
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export TOKEN_NAME="<ASSET_ID_OF_TOKEN>"

curl -X POST \
  https://app.bitgo-test.com/api/v2/$COIN/wallet/$WALLET_ID/token/approval/build \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "intent": {
      "intentType": "tokenApproval",
      "tokenName": "'"$TOKEN_NAME"'"
    }
  }'
```

</Tab>
<Tab title="Custody Multisig">

```javascript
async function approveToken() {
  const walletInstance = await bitgo
    .coin('opeth')
    .wallets()
    .get({ id: '<YOUR_WALLET_ID>' });
  
  // For custodial multisig wallets, use sendMany with type: 'tokenApproval'
  const result = await walletInstance.sendMany({
    type: 'tokenApproval',
    tokenName: 'opeth:usdc'
  });
  
  return result;
}
```
```shell cURL
export COIN="<ASSET_ID_OF_NATIVE_COIN>"
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export TOKEN_NAME="<ASSET_ID_OF_TOKEN>"

curl -X POST \
  https://app.bitgo-test.com/api/v2/$COIN/wallet/$WALLET_ID/token/approval/build \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "type": "tokenApproval"
    "tokenName": "'"$TOKEN_NAME"'"
  }'
```

</Tab>
<Tab title="BitGo Offline Vault MPC">

```javascript
async function approveToken() {
  const walletInstance = await bitgo
    .coin('opeth')
    .wallets()
    .get({ id: '<YOUR_WALLET_ID>' });
  
  // For BitGo Offline Vault MPC wallets, similar to Self-Custody MPC but without passphrase
  // User will need to sign the transaction offline
  const result = await walletInstance.sendMany({
    isTss: true,
    type: 'tokenApproval',
    tokenName: 'opeth:usdc',
    feeOptions: {
      maxFeePerGas: '30000000001',
      maxPriorityFeePerGas: '30000000000'
    }
  });
  
  return result;
}
```
```shell cURL
export COIN="<ASSET_ID_OF_NATIVE_COIN>"
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export TOKEN_NAME="<ASSET_ID_OF_TOKEN>"

curl -X POST \
  https://app.bitgo-test.com/api/v2/$COIN/wallet/$WALLET_ID/token/approval/build \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "intent": {
      "intentType": "tokenApproval",
      "tokenName": "'"$TOKEN_NAME"'",
      "feeOptions": {
        "maxFeePerGas": "30000000001",
        "maxPriorityFeePerGas": "30000000000"
      }
    }
  }'
```

</Tab>
<Tab title="Self-Custody MPC">

```javascript
async function approveToken() {
  const walletInstance = await bitgo
    .coin('opeth')
    .wallets()
    .get({ id: '<YOUR_WALLET_ID>' });
  
  // For self-custody MPC wallets, use sendMany with isTss flag
  const result = await walletInstance.sendMany({
    isTss: true,
    type: 'tokenApproval',
    tokenName: 'opeth:usdc',
    walletPassphrase: 'VerySecurePassword1234',
    feeOptions: {
      maxFeePerGas: '30000000001',
      maxPriorityFeePerGas: '30000000000'
    }
  });
  
  return result;
}
```
```shell cURL
export COIN="<ASSET_ID_OF_NATIVE_COIN>"
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export TOKEN_NAME="<ASSET_ID_OF_TOKEN>"

curl -X POST \
  https://app.bitgo-test.com/api/v2/$COIN/wallet/$WALLET_ID/token/approval/build \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "intent": {
      "intentType": "tokenApproval",
      "tokenName": "'"$TOKEN_NAME"'",
      "feeOptions": {
        "maxFeePerGas": "30000000001",
        "maxPriorityFeePerGas": "30000000000"
      },
      "type": "tokenApproval"
    }
  }'
```

</Tab>
<Tab title="BitGo Offline Vault Multisig">

```javascript
async function approveToken() {
  const walletInstance = await bitgo
    .coin('opeth')
    .wallets()
    .get({ id: '<YOUR_WALLET_ID>' });
  
  // For BitGo Offline Vault Multisig wallets, get the unsigned transaction
  const tokenApprovalBuild = await walletInstance.buildErc20TokenApproval('opeth:usdc');
  
  // Sign offline using the OVC
  // ...
  
  // Send the half-signed transaction
  const sendResult = await walletInstance.sendTransaction({
    txHex: halfSignedTxHex,
  });
  
  return sendResult;
}
```
```shell cURL
export COIN="<ASSET_ID_OF_NATIVE_COIN>"
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export TOKEN_NAME="<ASSET_ID_OF_TOKEN>"

curl -X POST \
  https://app.bitgo-test.com/api/v2/$COIN/wallet/$WALLET_ID/token/approval/build \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "tokenName": "'"$TOKEN_NAME"'"
  }'
```

</Tab>
<Tab title="Self-Custody Multisig">

```javascript
async function approveToken() {
  const walletInstance = await bitgo
    .coin('opeth')
    .wallets()
    .get({ id: '<YOUR_WALLET_ID>' });
  
  // self-custody Multisig wallets can use buildErc20TokenApproval with walletPassphrase
  // to build, sign, and send in one operation
  const result = await walletInstance.buildErc20TokenApproval(
    'opeth:opeth:usdc',     // tokenName
    'VerySecurePassword1234'  // walletPassphrase
  );
  
  return result;
}
```
```shell cURL
export COIN="<ASSET_ID_OF_NATIVE_COIN>"
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export TOKEN_NAME="<ASSET_ID_OF_TOKEN>"

curl -X POST \
  https://app.bitgo-test.com/api/v2/$COIN/wallet/$WALLET_ID/token/approval/build \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "tokenName": "'"$TOKEN_NAME"'"
  }'
```

</Tab>
</Tabs>

#### Step Result

```json
{
  "pendingApproval": {
    "id": "63727a81cdbc820007b27caa7b76016d",
    "coin": "eth",
    "wallet": "63726fde0a3c94000758f2790536041d",
    "state": "pendingFinalApproval",
    "scope": "wallet",
    "info": {
      "type": "transactionRequest",
      "transactionRequest": {
        "coinSpecific": {
          "eth": {
            "eip1559": {
              "maxPriorityFeePerGas": 1500000000,
              "maxFeePerGas": 133302263906
            },
            "recipients": [
              {
                "amount": "0",
                "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
                "data": "0x095ea7b3000000000000000000000000..."
              }
            ]
          }
        }
      }
    }
  }
}
```

## Next Steps

<Tabs>
<Tab title="Custody Wallets">

Since no assets are withdrawing in this transaction, BitGo automatically bypasses any policies to require additional admin approval.

You can now [Bulk Withdraw ERC20 Tokens](/docs/withdraw-bulk-erc20) to multiple recipients in a single transaction.

</Tab>
<Tab title="Self-Custody Wallets">

Transactions for approving the batcher smart contract follow the transaction flow of the wallet type, and must receive all required signatures and approvals before broadcasting to the blockchain. To learn more, see [Withdraw Overview](/docs/withdraw-overview).

Once you sign and broadcast your approval transaction, you can [Bulk Withdraw ERC20 Tokens](/docs/withdraw-bulk-erc20) to multiple recipients in a single transaction.

</Tab>
</Tabs>

## See Also

* [Withdraw Overview](/docs/withdraw-overview)
