Example 03: Programmable Token Transfer with Fast Transfers (FTF)
This example sends a programmable token transfer from an EOA on Ethereum Sepolia to a receiver that supports Fast Transfers on Polygon Amoy:
- Data payload:
"Hello, World" - Tokens:
CCIP-BnM
Script path: script/examples/Example03.s.sol
What You Will Do
- Ensure a destination
BasicMessageReceiverWithCCVsexists on Amoy. - Mint 1
CCIP-BnMon Sepolia usingscript/Faucet.s.sol. - Send one CCIP message containing both data and tokens.
Receiver Compatibility Note
This chapter uses Fast Transfers (blockConfirmations > 0), so destination receiver should return a non-zero minimum block depth.
- If your receiver is default-finality-only, this message can fail on destination.
- Deploy
BasicMessageReceiverWithCCVsbefore running this chapter. - For default-finality programmable token transfer (
blockConfirmations = 0), use Example04.
Before You Start
Important: Keystore first
Use a local keystore account for script execution:
cast wallet import myAccount --interactive Enter private key: Enter password: `myAccount` keystore was saved successfully. Address: <YOUR_EOA_ADDRESS_SHOULD_APPEAR_HERE>This chapter assumes
--account myAccount.
If you do not have a receiver deployed yet
Run:
forge script script/examples/Example02.s.sol:DeployBasicMessageReceiverWithCCVs \ --rpc-url polygonAmoy \ --account myAccount \ --broadcast \ --sig "run(address)" \ <DESTINATION_ROUTER>Then configure min block depth for your source chain:
forge script script/examples/Example02.s.sol:SetBasicMessageReceiverWithCCVsMinBlockDepth \ --rpc-url polygonAmoy \ --account myAccount \ --broadcast \ --sig "run(address,uint64,uint16)" \ <BASIC_MESSAGE_RECEIVER_WITH_CCVS_ADDRESS> \ <SOURCE_CHAIN_SELECTOR> \ <MIN_BLOCK_DEPTH>For this Fast Transfers example, use
<MIN_BLOCK_DEPTH> > 0and check the source pool minimum too.The script argument accepts
<MIN_BLOCK_DEPTH>(auint16passed toBasicMessageReceiverWithCCVs.setMinBlockDepth). On-chain, the receiver does not return that integer directly to CCIP:getCCVsAndFinalityConfigsetsallowedFinalityConfigtoFinalityCodec._encodeBlockDepth(minBlockDepth)— the samebytes4finality encoding CCIP 2.0 uses elsewhere for allowed finality (depth0means wait for full/default finality).Save the receiver address from the
[RESULT]log and use it in Step 2 below.
Step 1: Get 1 CCIP-BnM Token on Sepolia
Use the token and finality check from Example 01:
forge script script/Faucet.s.sol:Faucet \
--rpc-url ethereumSepolia \
--account myAccount \
--broadcast \
--sig "run(address)" \
<CCIP_BNM_SOURCE_TOKEN_ADDRESS>
Step 2: Send Hello World + CCIP-BnM
forge script script/examples/Example03.s.sol:Example03 \
--rpc-url ethereumSepolia \
--account myAccount \
--broadcast \
--sig "run(address,uint64,address,string,address,uint256,uint32,uint16,address)" \
<SOURCE_ROUTER> \
<DESTINATION_CHAIN_SELECTOR> \
<BASIC_MESSAGE_RECEIVER_WITH_CCVS_ADDRESS> \
"Hello, World" \
<CCIP_BNM_SOURCE_TOKEN_ADDRESS> \
<AMOUNT> \
<GAS_LIMIT> \
<BLOCK_CONFIRMATIONS_GT_ZERO> \
<FEE_TOKEN_ADDRESS>
Parameter notes:
<AMOUNT>uses token decimals (1e18is 1 token for 18-decimal tokens).<GAS_LIMIT>must be> 0because the receiver contract callback handles data.<BLOCK_CONFIRMATIONS_GT_ZERO>must meet the source pool and receiver minimums.- Executor may enforce a minimum block confirmation value and revert if too low.
- If requested confirmations exceed chain finality, default finality is used.
<FEE_TOKEN_ADDRESS>: Pass the LINK token address on the source chain here. If you want to pay for CCIP fees in native coin instead, pass0x0000000000000000000000000000000000000000
Verify Result
Example03 logs a CCIP message ID. Track it in the CCIP Explorer:
- https://ccip.chain.link
Optional receiver checks on Amoy:
cast call <BASIC_MESSAGE_RECEIVER_WITH_CCVS_ADDRESS> "latestMessage()(bytes)" --rpc-url polygonAmoy
cast call <BASIC_MESSAGE_RECEIVER_WITH_CCVS_ADDRESS> "latestSender()(address)" --rpc-url polygonAmoy
cast call <BASIC_MESSAGE_RECEIVER_WITH_CCVS_ADDRESS> "latestSourceChainSelector()(uint64)" --rpc-url polygonAmoy