# Bulk Withdrawal ERC20 Tokens

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

## Overview

The ERC20 Bulk Withdrawal enables you to send multiple token transfers to different addresses in a single transaction. This functionality utilizes a special batcher contract to combine multiple token transfers, reducing gas costs and simplifying transaction management compared to sending multiple individual token transfers.

Key benefits:

- **Lower gas costs**: Consolidate multiple transfers into a single on-chain transaction
- **Simplified transaction management**: Single approval workflow for multiple transfers
- **Supported on multiple EVM chains**: Available on Ethereum, Polygon, Arbitrum, Optimism, and other EVM-compatible networks

### Technical Details

When using the bulk withdrawal feature:

1. A batcher contract specific to your network processes your transaction.
1. The contract calls `batchTransferFrom` to send tokens to multiple recipients.
1. The batcher contract uses the ERC20 `transferFrom` method to move tokens from your wallet to the recipients.
1. The transaction appears on-chain as a single contract interaction.
1. Each recipient receives their tokens directly from your wallet.

### Limitations

- All transfers in a batch must use the same token type.
- Maximum batch size varies by network (typically around 100 recipients per transaction).
- BitGo does not support batch transfers for CryptoPunks and other non-standard ERC20 tokens.

## Prerequisites

* [Get Started](/docs/get-started-intro)
* [Create Wallets](/docs/wallets-create-wallets)
* [Deposit Assets](/docs/deposit-assets)
* [Enable Bulk Withdrawals of ERC20 Tokens](/docs/withdraw-enable-bulk-erc20)
* Have sufficient balance of the native coin to cover gas fees.

## Cookbooks

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

<Cookbook slug="bulk-withdrawal-erc20-mpc" title="Bulk Withdrawal ERC20 (MPC)" />

<Cookbook slug="bulk-withdrawal-erc20-multisig" title="Bulk Withdrawal ERC20 (Multisig)" />

## 1. Build a Bulk Withdrawal Transaction

To create a bulk withdrawal transaction, you'll provide multiple recipients in your transaction request, each specifying the recipient address and amount.

<Tabs>
<Tab title="Multisig">

```javascript
async function buildBulkTokenWithdrawal() {
  const walletInstance = await bitgo
    .coin('opeth')
    .wallets()
    .get({ id: '<YOUR_WALLET_ID>' });
  
  // Build a transaction with multiple recipients
  const buildResult = await walletInstance.prebuildTransaction({
    recipients: [
      {
        address: '0x1111111111111111111111111111111111111111',
        amount: '100000000'
      },
      {
        address: '0x2222222222222222222222222222222222222222',
        amount: '200000000'
      },
      {
        address: '0x3333333333333333333333333333333333333333',
        amount: '300000000'
      }
    ],
    tokenName: 'opeth:usdc'
  });
  
  return buildResult;
}
```
```json
POST /api/v2/{coin}/wallet/{walletId}/tx/build

{
  "recipients": [
    {
      "address": "0x1111111111111111111111111111111111111111",
      "amount": "100000000"
    },
    {
      "address": "0x2222222222222222222222222222222222222222",
      "amount": "200000000"
    },
    {
      "address": "0x3333333333333333333333333333333333333333",
      "amount": "300000000"
    }
  ],
  "tokenName": "opeth:usdc"
}
```

</Tab>
<Tab title="MPC">

```javascript
async function sendBulkTokenWithdrawal() {
  const walletInstance = await bitgo
    .coin('opeth')
    .wallets()
    .get({ id: '<YOUR_WALLET_ID>' });
  
  // Send a transaction with multiple recipients
  return walletInstance.sendMany({
    type: 'transfer',
    recipients: [
      {
        address: '0x1111111111111111111111111111111111111111',
        amount: '100000000',
        tokenName: 'opeth:usdc',
      },
      {
        address: '0x2222222222222222222222222222222222222222',
        amount: '200000000',
        tokenName: 'opeth:usdc',
      },
      {
        address: '0x3333333333333333333333333333333333333333',
        amount: '300000000',
        tokenName: 'opeth:usdc',
      }
    ],
    walletPassphrase: 'VerySecurePassword1234',
    feeOptions: {
      maxFeePerGas: '81130354893',
      maxPriorityFeePerGas: '71130354893',
    }
  });
}
```

</Tab>
</Tabs>

In a bulk withdrawal transaction, the batcher contract consolidates the recipients array into a single on-chain interaction. The batcher contract then distributes the tokens to each recipient address according to the specified amounts.

## 2. Sign and Send Transaction

After building the transaction, sign it and send it to BitGo for processing. The transaction signing process follows the standard pattern for your wallet type.

<Tabs>
<Tab title="Multisig">

```javascript
async function sendBulkTokenWithdrawal() {
  const walletInstance = await bitgo
    .coin('opeth')
    .wallets()
    .get({ id: '<YOUR_WALLET_ID>' });
  
  // Build, sign and send in one operation
  const result = await walletInstance.sendMany({
    recipients: [
      {
        address: '0x1111111111111111111111111111111111111111',
        amount: '100000000'
      },
      {
        address: '0x2222222222222222222222222222222222222222',
        amount: '200000000'
      },
      {
        address: '0x3333333333333333333333333333333333333333',
        amount: '300000000'
      }
    ],
    tokenName: 'opeth:usdc',
    walletPassphrase: 'VerySecurePassword1234',
  });
  
  return result;
}
```

</Tab>
<Tab title="MPC">

Sign the MPC transaction by passing the transaction request ID along with either your unencrypted private key or your wallet passphrase with the encrypted private key. BitGo Express handles the signature-share exchange with BitGo and broadcasts the transaction to the blockchain.

>Endpoint: [Sign MPC transaction](/reference/expresswalletsigntxtss)

```shell cURL
export BITGO_EXPRESS_HOST="<YOUR_LOCAL_HOST>"
export COIN="<ASSET_ID>"
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export TX_REQUEST_ID="<TX_REQUEST_ID>"

curl -X POST \
  http://$BITGO_EXPRESS_HOST/api/v2/$COIN/wallet/$WALLET_ID/signtxtss \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "txRequestId": "'"$TX_REQUEST_ID"'",
    "prv": "string, # Either pass just your `prv` or pass your `walletPassphrase`, `keychain`, and `encryptedPrv`
    "walletPassphrase": "string",
    "keychain": {
      "encryptedPrv": "string"
    }
  }'
```
```shell cURL (External-Signer Mode)
export BITGO_EXPRESS_HOST="<YOUR_LOCAL_HOST>"
export COIN="<ASSET_ID>"
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export TX_REQUEST_ID="<TX_REQUEST_ID>"

curl -X POST \
  http://$BITGO_EXPRESS_HOST/api/v2/$COIN/wallet/$WALLET_ID/signtxtss \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "txRequestId": "e2f0f3fc-8704-4c9c-946b-601e889175c1"
  }'
```
```js JavaScript
import dotenv from "dotenv";
dotenv.config();
import { BitGoAPI } from "@bitgo/sdk-api";
import { Tsol } from "@bitgo/sdk-coin-sol";
import { z } from "zod";

const env = z
  .object({
    USERNAME: z.string().min(1),
    PASSWORD: z.string().min(1),
    ENV: z.union([z.literal("test"), z.literal("staging"), z.literal("prod")]),
    OTP: z.string().optional(),
    WALLET_ID: z.string(),
  })
  .parse(process.env);

const bitgo = new BitGoAPI({ env: env.ENV });
bitgo.register("tsol", Tsol.createInstance);
const coin = bitgo.coin("tsol");

async function auth() {
  await bitgo.authenticate({
    username: env.USERNAME,
    password: env.PASSWORD,
    otp: env.OTP,
  });
  await bitgo.lock();
  await bitgo.unlock({ otp: "000000", duration: 3600 });
}

async function main() {
  await auth();

  const wallet = await coin.wallets().get({ id: env.WALLET_ID });

  const params = {
    txRequestId: "string", // previously created txRequestId
    // one of:
    prv: "un-encrypted prv",
    // or
    keychain: {
      encryptedPrv: "encrypted prv"
    },
    walletPassphrase: "password to decrypt the encryptedPrv"
  };
  wallet.signTransaction(params).then(function (result) {
    // print result details
    console.dir(result);
  });
}
```

</Tab>
</Tabs>

#### Step Result

The response includes the transaction details and the status of the transaction:

```json
{
  "transfer": {
    "coin": "opeth:usdc",
    "id": "63727a81cdbc820007b27caa7b76016d",
    "wallet": "63726fde0a3c94000758f2790536041d",
    "txid": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
    "height": 0,
    "date": "2023-11-14T17:27:29.128Z",
    "type": "send",
    "value": "-600000000",
    "valueString": "-600000000",
    "baseValueString": "-600000000",
    "baseValueWithoutFeesString": "-600000000",
    "feeString": "0",
    "payGoFee": 0,
    "payGoFeeString": "0",
    "state": "signed",
    "instant": false
  },
  "txid": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
  "status": "signed"
}
```

## See Also

* [API Reference: Build Transaction](/api/v2/wallet/tx/build)
* [API Reference: Send Transaction](/api/v2/wallet/tx/send)
* [Withdraw Overview](/docs/withdraw-overview)
