send
Send a CCIP message from a source chain to a destination chain.
Synopsis
ccip-cli send -s <source> -d <dest> -r <router> [options]
Description
The send command constructs and submits a CCIP message to transfer data and/or tokens between blockchains. It handles fee calculation, token approvals, and transaction submission.
Options
Required
| Option | Alias | Type | Description |
|---|---|---|---|
--source | -s | string | Source network (chain ID, selector, or name) |
--dest | -d | string | Destination network (chain ID, selector, or name) |
--router | -r | string | Router contract address on source |
Message
| Option | Alias | Type | Default | Description |
|---|---|---|---|---|
--receiver | --to | string | - | Receiver address on destination. Defaults to sender if same chain family. Required for cross-family transfers. |
--data | - | string | - | Message data payload. Hex byte arrays (0x...) and base64 strings are decoded as raw bytes. Prefix with 0x: to ABI-encode as a Solidity string (e.g., --data "0x:hello" produces abi.encode(["string"], ["hello"])). All other values are raw UTF-8 encoded. |
--transfer-tokens | -t | string[] | - | Token transfers as token=amount. See Token Amount Format. |
--fee-token | - | string | Native | Fee token address or symbol (e.g., LINK). Omit to pay in native token. |
Gas & Execution
| Option | Alias | Type | Default | Description |
|---|---|---|---|---|
--gas-limit | -L, --compute-units | number | - | Gas limit for receiver callback. See Gas Limit Behavior. |
--estimate-gas-limit | - | number | - | Estimate gas limit with % margin (e.g., 10 for +10%). Conflicts with --gas-limit. |
--allow-out-of-order-exec | --ooo | boolean | true | Allow out-of-order execution. Supported on v1.5+ lanes. |
Extra Args
| Option | Alias | Type | Description |
|---|---|---|---|
--extra | -x | string[] | Extra args as key=value. Values parsed as JSON with BigInt support; fallback to string. Repeated keys become arrays. |
Extra args fields like finality, ccvs, ccvArgs, executor, executorArgs, tokenReceiver, and tokenArgs require CCIP v2+ lanes (GenericExtraArgsV3). Using them on older lanes will fail. The SDK auto-detects V3 when any V3-only field is present. Check your lane version in the CCIP Directory.
For array fields (ccvs, ccvArgs, tokenArgs), repeating a key creates an array automatically: -x ccvs=0xA -x ccvs=0xB produces ["0xA", "0xB"]. However, a single value is treated as a string, not an array. If your array has exactly one element, you must use the JSON array syntax:
# WRONG — produces string "0xVerifier1", not an array
-x ccvs=0xVerifier1
# CORRECT — produces ["0xVerifier1"]
-x ccvs='["0xVerifier1"]'
--extra values override values set by other flags. For example, --gas-limit 100000 -x gasLimit=200000 uses 200000.
Chain-Specific
| Option | Alias | Type | Description |
|---|---|---|---|
--token-receiver | - | string | Solana token receiver address if different from program receiver. Required when sending tokens + data to Solana. |
--account | --receiver-object-id | string[] | Solana accounts (append =rw for writable) or Sui receiver object IDs. |
--canton-config | - | string | Path to Canton config JSON file (required when source or dest is Canton). See Configuration. |
--indexer | - | string[] | CCIP v2 indexer URLs for CCV verifications (used when lane involves Canton). |
Wallet
| Option | Alias | Type | Description |
|---|---|---|---|
--wallet | -w | string | Wallet source. See Configuration. |
--approve-max | - | boolean | Approve maximum token allowance instead of exact amount needed. |
Dry-Run
| Option | Type | Description |
|---|---|---|
--only-get-fee | boolean | Print the fee and exit without sending. |
--only-estimate | boolean | Print gas estimate and exit. Requires --estimate-gas-limit. |
Other
| Option | Type | Default | Description |
|---|---|---|---|
--wait | boolean | false | Wait for finality, commit, and execution before returning. |
See Configuration for global options (--rpcs, --rpcs-file, --format, --no-api, etc.).
Command Builder
Build your send command interactively:
ccip-cli send Builder
Send a CCIP message from source to destination chain
ccip-cli send --rpcs-file ./.env --format prettyToken Amount Format
The --transfer-tokens / -t option accepts token=amount pairs. The CLI converts human-readable amounts to smallest units using the token's decimals.
# Single token (1.5 tokens)
-t 0xTokenAddress=1.5
# Multiple tokens
-t 0xToken1=1.0 -t 0xToken2=50
# By symbol (if resolvable on source chain)
-t USDC=100
Tokens without decimals()
decimals() is an optional view in ERC-20 — some tokens don't implement it. When the token doesn't, the CLI resolves its decimals automatically from the token pool's getTokenDecimals() (pool v1.5.1+) — the same value the pool itself uses for its conversion. A plain token=amount just works; no decimals input is needed. If neither the token nor its pool can declare them, the send fails with TOKEN_DECIMALS_UNAVAILABLE rather than guessing — these decimals determine the actual transfer amount.
The source pool also declares its decimals in the message's extraData (the destPoolData it returns from lockOrBurn), so destination preflights never need the token's on-chain view either. The on-chain message itself ignores it.
Gas Limit Behavior
If you're sending tokens without --data, the gas limit defaults to 0 — no receiver callback is executed, and the tokens are delivered directly. Only set --gas-limit or --estimate-gas-limit when your receiver contract processes the message in ccipReceive.
When --data is present and --gas-limit is omitted, the SDK applies a default of 200,000 for EVM and Solana destinations, or 1,000,000 for Sui. If your receiver needs more gas, set it explicitly or use --estimate-gas-limit to auto-calculate with a margin.
Lane-Specific Parameters
| Option | EVM→EVM | EVM→Solana | Solana→EVM | EVM→Sui | Canton→EVM |
|---|---|---|---|---|---|
--gas-limit / -L | Gas limit for ccipReceive | Compute units | Gas limit | Gas limit | Gas limit for dest |
--estimate-gas-limit | Auto-estimate + % margin | Auto-estimate | Auto-estimate | - | - |
--token-receiver | v2+ lanes only (--extra) | Required if tokens + data | - | Optional | - |
--account | - | Receiver accounts (append =rw) | - | Receiver object IDs | - |
--ooo | Default: true | Default: true | Default: true | Default: true | Default: true |
--extra / -x | v2+ lanes only | - | - | - | feeTokenHoldingCids, ccvRawAddresses, executorMode |
--canton-config | - | - | - | - | Required |
-r / --router | Router address | Router address | Router address | Router address | CCIPSender instance id |
Examples
Send a data message
ccip-cli send \
-s ethereum-testnet-sepolia \
-d arbitrum-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
--to 0xAB4f961939BFE6A93567cC57C59eEd7084CE2131 \
--data "hello world"
Transfer tokens
ccip-cli send \
-s ethereum-testnet-sepolia \
-d arbitrum-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
--to 0xAB4f961939BFE6A93567cC57C59eEd7084CE2131 \
-t 0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05=0.1 \
--fee-token LINK
Programmable token transfer (tokens + data)
ccip-cli send \
-s ethereum-testnet-sepolia \
-d arbitrum-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
--to 0xAB4f961939BFE6A93567cC57C59eEd7084CE2131 \
-t 0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05=1.0 \
--data "deposit:pool-123" \
--estimate-gas-limit 15
Check fee without sending
ccip-cli send \
-s ethereum-testnet-sepolia \
-d arbitrum-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
--to 0xAB4f961939BFE6A93567cC57C59eEd7084CE2131 \
-t 0xToken=1.0 \
--only-get-fee
EVM to Solana
ccip-cli send \
-s ethereum-testnet-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
-d solana-devnet \
--to <SOLANA_PROGRAM_ADDRESS> \
--token-receiver <SOLANA_TOKEN_ACCOUNT> \
--account <ACCOUNT_1> \
--account <ACCOUNT_2>=rw \
-t 0xToken=1.0 \
--data "instruction-data" \
--wallet ledger
Solana to EVM
ccip-cli send \
-s solana-devnet \
-r <SOLANA_ROUTER> \
-d ethereum-testnet-sepolia \
--to 0xReceiverContract \
-t <SPL_TOKEN_MINT>=1.0 \
--wallet ledger
EVM to Sui
ccip-cli send \
-s ethereum-testnet-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
-d sui-testnet \
--to 0xSuiReceiverAddress \
--account 0xObjectId1 \
--account 0xObjectId2 \
-t 0xToken=1.0 \
--wallet ledger
Canton to EVM
On Canton source, --wallet is not needed — the JWT from --canton-config (either the static jwt field or a token resolved from auth) is used to submit the send transaction:
ccip-cli send \
-s canton-testnet \
-d ethereum-testnet-sepolia \
-r prod-ccipsender \
--canton-config ./canton-config.json \
--rpc https://ledger.example.com/api/json \
--rpc https://ethereum-sepolia-rpc.example.com \
--to 0xReceiverContract \
-t link-token=1.0 \
--no-estimate-gas-limit
On Canton source, -r is the CCIPSender instance id (not an EVM router address). Use --only-get-fee to preview — Canton returns 0 since fees are computed on-ledger. Pass Canton send extras via --extra:
-x feeTokenHoldingCids='["#holding-cid-1"]' \
-x executorMode=none
Extra args (CCIP v2+ lanes)
ccip-cli send \
-s ethereum-testnet-sepolia \
-d arbitrum-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
--to 0xAB4f961939BFE6A93567cC57C59eEd7084CE2131 \
--data "hello world" \
-x finality=5 \
-x ccvs='["0xVerifier1"]' \
-x executor=0xExecutorAddress
Send and wait for execution
ccip-cli send \
-s ethereum-testnet-sepolia \
-d arbitrum-sepolia \
-r 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 \
--to 0xAB4f961939BFE6A93567cC57C59eEd7084CE2131 \
--data "hello world" \
--wait
See Also
- show — Track message status after sending
- get-supported-tokens — Find supported tokens for transfer
- Configuration — Wallet and RPC setup
Exit Codes
| Code | Meaning |
|---|---|
0 | Success — transaction submitted (or fee/estimate returned for dry-run) |
1 | Error (network failure, transaction reverted, invalid arguments) |