Programmable Token Transfers: Canton as Destination
This tutorial demonstrates how to send a programmable token transfer (PTT) from Ethereum Sepolia to Canton — TEST tokens plus arbitrary data — and execute it on Canton using the ccip-starter-kit-canton.
Introduction
A programmable token transfer delivers both tokens and a data payload to the destination. On Canton, execution:
- Resolves or creates a
CCIPReceiverfor the message finality. - Processes
TokenReceiveTicketand mints LINK via the token pool. - Delivers the attached data to your receiver logic through
ccipReceiveon Canton.
What You Will Build
In this tutorial, you will:
- Send
1TEST plus a UTF-8 data string from Sepolia withany2canton:data-and-token. - Execute on Canton with
any2canton:manual-exec. - Verify LINK balance and message execution.
Understanding Programmable Token Transfers (any2canton)
| Aspect | Token-only | Programmable token transfer |
|---|---|---|
| Send script | any2canton:token | any2canton:data-and-token |
| Data | Empty | --dataString payload |
| Canton execution | LINK mint + accept TransferInstruction | LINK mint + data delivered to receiver |
The execution path fetches proofs, Global CCIP EDS disclosures, and token pool operator EDS inputs — see the Canton as Destination flow.
Running the Tutorial
Prerequisites Check
-
Complete prerequisites.
-
Mint TEST on Sepolia:
Terminalnpm run faucet:evm-test
Step 1: Send data + TEST from Sepolia
npm run any2canton:data-and-token -- --dataString "Hello" --amount 1
Pay CCIP fee in LINK and use block-depth finality:
npm run any2canton:data-and-token -- \
--dataString "Hello" \
--amount 1 \
--feeToken link \
--finality 32
Expected send output
📧🪙 Sending data + 1 TEST from Sepolia → Canton: "Hello"
Receiver party: yourParty::1220…
LINK is minted on Canton only after manual execution.
🆔 CCIP Message ID: 0x…
⚙️ Execute on Canton once Sepolia reaches 32 block confirmations for this message:
npm run any2canton:manual-exec -- 0x<messageId>
Step 2: Execute on Canton
After finality and Committee Verifier proofs are on the indexer:
npm run any2canton:manual-exec -- <sepoliaTxHash | messageId>
Pass either the Sepolia source transaction hash (📜 Source transaction) or the CCIP Message ID (🆔 CCIP Message ID, 0x…) — any2canton:manual-exec accepts both.
Optional custom receiver hint:
npm run any2canton:manual-exec -- <sepoliaTxHash | messageId> --receiver 'yourParty::1220…'
Verification
- CCIP Explorer: Confirm SUCCESS for the transfer.
- LINK balance:
npm run check-balance -- --chain canton --token link - Canton Lighthouse: Inspect the execution transaction for token pool and receiver activity.