Skip to main content
Version: 0.96.0

Transfer Tokens and Data

Transfer tokens along with arbitrary data (programmable token transfers) using a four-step workflow: verify token support, estimate fees, send the transfer, and track execution.

Workflow

StepCommandPurpose
1. VerifygetSupportedTokensCheck token support on the lane
2. Estimatesend --only-get-feeGet fee quote
3. Sendsend --transfer-tokens --dataExecute transfer
4. TrackshowMonitor execution

Use Cases

Programmable token transfers enable:

  • DeFi operations: Deposit tokens to a protocol with instructions
  • NFT purchases: Send payment with metadata
  • Cross-chain swaps: Transfer tokens with swap parameters
  • Governance: Send tokens with voting instructions

Prerequisites

Configure RPC endpoints and wallet:

Bash
.env
RPC_SEPOLIA=https://ethereum-sepolia-rpc.publicnode.com
RPC_ARB_SEPOLIA=https://arbitrum-sepolia-rpc.publicnode.com
USER_KEY=0xYourPrivateKeyHere

Required balances on source chain:

  • Tokens to transfer
  • Gas for source transaction
  • CCIP fee (native token or LINK)

Step 1: Verify Token Support

Check that your token is supported on the lane:

Bash
ccip-cli getSupportedTokens \
-n ethereum-testnet-sepolia \
-a 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
-t 0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05

Verify:

FieldRequirement
DestinationTarget chain is listed
Rate LimitTransfer amount is within limit
AvailableCapacity exists for transfer

Step 2: Estimate Fee

Get the fee quote including both tokens and data:

Bash
ccip-cli send \
-s ethereum-testnet-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
-d ethereum-testnet-sepolia-arbitrum-1 \
--receiver 0xYourReceiverContract \
--transfer-tokens 0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05=1.0 \
--data "deposit:pool-123" \
--only-get-fee

Output:

Fee: 0.001345678901234567 ETH (native)

Step 3: Send Transfer with Data

Basic transfer with data

Bash
ccip-cli send \
-s ethereum-testnet-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
-d ethereum-testnet-sepolia-arbitrum-1 \
--receiver 0xYourReceiverContract \
--transfer-tokens 0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05=1.0 \
--data "deposit:pool-123" \
--wallet ledger

Multiple tokens with data

Bash
ccip-cli send \
-s ethereum-testnet-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
-d ethereum-testnet-sepolia-arbitrum-1 \
--receiver 0xYourReceiverContract \
--transfer-tokens 0xToken1=1.0 \
--transfer-tokens 0xToken2=100.5 \
--data "swap:token1-to-token2" \
--wallet ledger

With hex-encoded data

For ABI-encoded function calls or structured data:

Bash
ccip-cli send \
-s ethereum-testnet-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
-d ethereum-testnet-sepolia-arbitrum-1 \
--receiver 0xYourReceiverContract \
--transfer-tokens 0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05=1.0 \
--data 0x095ea7b3000000000000000000000000abcdef... \
--wallet ledger
Bash
ccip-cli send \
-s ethereum-testnet-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
-d ethereum-testnet-sepolia-arbitrum-1 \
--receiver 0xYourReceiverContract \
--transfer-tokens 0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05=1.0 \
--data "deposit:pool-123" \
--fee-token LINK \
--wallet ledger

With custom gas limit

Programmable transfers often need higher gas limits for complex receiver logic:

Bash
ccip-cli send \
-s ethereum-testnet-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
-d ethereum-testnet-sepolia-arbitrum-1 \
--receiver 0xYourReceiverContract \
--transfer-tokens 0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05=1.0 \
--data "deposit:pool-123" \
--gas-limit 500000 \
--wallet ledger

With estimated gas limit

Let the CLI estimate the required gas:

Bash
ccip-cli send \
-s ethereum-testnet-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
-d ethereum-testnet-sepolia-arbitrum-1 \
--receiver 0xYourReceiverContract \
--transfer-tokens 0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05=1.0 \
--data "deposit:pool-123" \
--estimate-gas-limit 15 \
--wallet ledger

Step 4: Track Transfer

Check message status:

Bash
ccip-cli show 0xYourTransactionHash

Wait for execution to complete:

Bash
ccip-cli show 0xYourTransactionHash --wait

Or include --wait in the send command:

Bash
ccip-cli send \
-s ethereum-testnet-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
-d ethereum-testnet-sepolia-arbitrum-1 \
--receiver 0xYourReceiverContract \
--transfer-tokens 0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05=1.0 \
--data "deposit:pool-123" \
--wallet ledger \
--wait

Example

Bash
# Verify token support
ccip-cli getSupportedTokens \
-n ethereum-testnet-sepolia \
-a 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
-t 0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05

# Estimate fee
ccip-cli send \
-s ethereum-testnet-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
-d ethereum-testnet-sepolia-arbitrum-1 \
--receiver 0xAB4f961939BFE6A93567cC57C59eEd7084CE2131 \
--transfer-tokens 0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05=1.0 \
--data "deposit:pool-123" \
--fee-token LINK \
--only-get-fee

# Send transfer with data
ccip-cli send \
-s ethereum-testnet-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
-d ethereum-testnet-sepolia-arbitrum-1 \
--receiver 0xAB4f961939BFE6A93567cC57C59eEd7084CE2131 \
--transfer-tokens 0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05=1.0 \
--data "deposit:pool-123" \
--fee-token LINK \
--gas-limit 400000 \
--wallet ledger

# Track execution
ccip-cli show 0xabc123... --wait

Chain-Specific Behavior

The receiver contract must implement the IAny2EVMMessageReceiver interface to handle both tokens and data:

Solidity
function ccipReceive(Client.Any2EVMMessage calldata message) external {
// message.data contains your arbitrary data
// message.destTokenAmounts contains transferred tokens
}

Token approvals:

The CLI handles approvals automatically. Use --approve-max for unlimited allowance:

Bash
--approve-max

Failures

For failed transfers, see Debugging Failed Messages.

IssueCauseSolution
Transfer stuckRate limiter depletedWait for refill or reduce amount
Execution failedInsufficient gasRetry with manualExec --gas-limit
Receiver revertsContract logic errorFix receiver contract
Token not supportedToken not registeredUse a supported token
Insufficient allowanceApproval failedRun send again or approve manually

Gas Limit Considerations

Programmable token transfers typically require more gas than simple transfers:

Receiver LogicRecommended Gas Limit
Simple storage200,000 - 300,000
DeFi interaction400,000 - 600,000
Complex operations600,000 - 1,000,000

Use --estimate-gas-limit to automatically calculate the required gas with a buffer.