A transfer is the act of sending an asset from one wallet or address to another.

To transfer an asset, ensure that the source contains some ETH (by using a faucet if on testnet, for example). This is required because the network uses ETH to pay for transaction fees.

Crypto transactions take varying amounts of time—anywhere from hundreds of milliseconds, to tens of minutes, depending on the blockchain network and wallet set-up. For example, transactions on Bitcoin can take upwards of 30 minutes, while transactions on Base take a second or two.

Once your source has ETH in it, call the transfer function as follows:

Creating multiple transactions simultaneously can lead to failures.

All transfers, excluding gasless transfers, do not support concurrent transactions. We recommend running sequential calls and waiting for the previous transaction to confirm before continuing.

See Processing multiple transfers for same address as an example. If you need more assistance, reach out to us on Discord in #wallet-api.

SDK Documentation

You can refer to the Transfer class SDK docs for a full list of supported methods.

// Transfer 0.00001 Ether to the destination address.
let transfer = await wallet.createTransfer({
  amount: 0.00001,
  assetId: Coinbase.assets.Usdc,
  destination: anotherWallet
});

// Wait for the transfer to settle.
await transfer.wait()

Transfers of arbitrary ERC20 assets

You can transfer ERC20 assets that are not assets supported by symbol by using the contract address as the asset ID.

    let contractAddress = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
    let balance = await wallet.getBalance(contractAddress)

    // Transfer the full balance of the ERC20 asset to the destination wallet.
    let transfer = await wallet.createTransfer({
      amount: balance,
      assetId: contractAddress,
      destination: anotherWallet
    });

    // Wait for the transfer to settle.
    await transfer.wait()

Gasless Transfers

Coinbase will pay for the gas for transfers of USDC, EURC and cbBTC on Base Mainnet and Base Sepolia!

To initiate a USDC transfer on Base Mainnet with gas fees covered, set the gasless flag to true.

      import { Wallet, TimeoutError } from '@coinbase/coinbase-sdk';

      // Create a wallet on Base Mainnet
      let wallet = await Wallet.create({ networkId: Coinbase.networks.BaseMainnet });

      // Out-of-Band: Fund the wallet's default address with USDC

      // Create a gasless USDC transfer on Base Mainnet
      try {
        const transfer = await wallet.createTransfer({
          amount: 0.00001,
          assetId: Coinbase.assets.Usdc,
          destination: anotherWallet,
          gasless: true
        });
      } catch (error) {
        console.error(`Error while transferring: `, error);
      }

      // Wait for transfer to land on-chain.
      try {
        await transfer.wait();
      } catch (err) {
        if (err instanceof TimeoutError) {
          console.log("Waiting for transfer timed out");
        } else {
          console.error("Error while waiting for transfer to complete: ", error);
        }
      }

      // Check if transfer successfully completed on-chain
      if (transfer.getStatus() === 'complete') {
        console.log('Transfer completed on-chain: ', transfer.toString());
      } else {
        console.error('Transfer failed on-chain: ', transfer.toString());
      }

Batching

By default, gasless transfers are batched together, which optimizes for high transaction throughput. While batched transfers typically take longer than non-batched ones to process, this approach allows your application to handle many concurrent transactions efficiently.

You can significantly reduce processing time by disabling batching with skipBatching: true. However, this comes with an important tradeoff:

  • Disabling batching significantly reduces your application’s ability to handle concurrent transactions.

We recommend keeping batching enabled to ensure reliable performance as your transaction volume grows.

      import { Wallet, TimeoutError } from '@coinbase/coinbase-sdk';

      // Create a wallet on Base Mainnet
      let wallet = await Wallet.create({ networkId: Coinbase.networks.BaseMainnet });

      // Out-of-Band: Fund the wallet's default address with USDC

      // Create a gasless USDC transfer on Base Mainnet
      try {
        const transfer = await wallet.createTransfer({
          amount: 0.00001,
          assetId: Coinbase.assets.Usdc,
          destination: anotherWallet,
          gasless: true,
          skipBatching: true,
        });
      } catch (error) {
        console.error(`Error while transferring: `, error);
      }

      // Wait for transfer to land on-chain.
      try {
        await transfer.wait();
      } catch (err) {
        if (err instanceof TimeoutError) {
          console.log("Waiting for transfer timed out");
        } else {
          console.error("Error while waiting for transfer to complete: ", error);
        }
      }

      // Check if transfer successfully completed on-chain
      if (transfer.getStatus() === 'complete') {
        console.log('Transfer completed on-chain: ', transfer.toString());
      } else {
        console.error('Transfer failed on-chain: ', transfer.toString());
      }

Processing multiple transfers for same address

When creating multiple transfers for the same source address, it is important to create them sequentially instead of all at once. Wait for the previous transfer to have a final state (COMPLETE / FAILED) before creating a new one. Creating multiple transactions simultaneously can lead to failures due to how nonces are managed by the CDP APIs.

An example of how to process transactions sequentially:

      for (let i = 0; i < 5; i++)  {
        try {
          const transfer = await wallet.createTransfer({
            amount: 0.00001,
            assetId: Coinbase.assets.Eth,
            destination: wallet,
          });

          await transfer.wait()
        } catch (error) {
          console.error(`Error while transferring: `, error);
        }
    }

Transfer to ENS or Basenames

ENS names and Basenames are core building blocks that enable anyone to establish their onchain identity by registering human-readable names for their wallet addresses. CDP SDK supports ENS or Basename as the destination address in your transfers.

    // Transfer 0.00001 Ether to the address that belongs to the Base Name/ENS.
    let transfer := await wallet.createTransfer({
      amount: 0.00001,
      assetId: Coinbase.assets.Usdc,
      destination: "my-ens-name.base.eth",
      gasless: true
    });

    # Wait for the transfer to settle.
    await transfer.wait()