Programmable Token Transfers: Canton as Source

This tutorial demonstrates how to send a programmable token transfer (PTT) — tokens plus arbitrary data — from Canton testnet to Ethereum Sepolia using the ccip-starter-kit-canton.

Introduction

A programmable token transfer combines a token transfer with a data payload delivered to the destination receiver contract. On Sepolia, the receiver's ccipReceive function receives both the tokens and the calldata.

This tutorial uses the canton2any:data-and-token script to send LINK plus a UTF-8 string in a single CCIP message.

What You Will Build

In this tutorial, you will:

  • Run canton2any:data-and-token with --dataString and --amount.
  • Pay CCIP fees in Amulet or LINK.
  • Track delivery in the CCIP Explorer.
  • Verify token delivery and optional manual execution on Sepolia.

Understanding Programmable Token Transfers (canton2any)

AspectToken-onlyProgrammable token transfer
DataEmptyUTF-8 string (or 0x: prefixed ABI-encoded string)
TokensLINK onlyLINK + data in one message
DestinationTokens to receiverTokens + ccipReceive calldata on Sepolia

The send path on Canton combines token pool lock/burn (TokenPoolTicket) with CommitteeVerifier tickets in a single PerPartyRouter_CCIPSend, as described in the Canton as Source flow.

How the Script Works

scripts/canton2any/ccipSendDataAndToken.ts builds a message with both data (from encodeMessageData) and tokenAmounts (Canton LINK instrument), then submits via CantonChain.sendMessage with the same fee and execution options as the other canton2any scripts.

Running the Programmable Token Transfer

Prerequisites Check

  1. Complete prerequisites.

  2. Confirm LINK and fee-token balances:

    Terminal
    npm run check-balance
    

Execute the Script

Send 0.001 LINK with a data payload:

Terminal
npm run canton2any:data-and-token -- --dataString "Hello" --amount 0.001

Pay fee in LINK and skip auto-execution:

Terminal
npm run canton2any:data-and-token -- --dataString "Hello" --amount 0.001 --feeToken link --no-exec

Custom Sepolia receiver and a higher gas limit (default is 50000):

Terminal
npm run canton2any:data-and-token -- \
  --dataString "Hello" \
  --amount 0.001 \
  --evmReceiver 0xYourReceiverAddress \
  --gasLimit 100000

Understanding the Output

📧🪙 Sending data + 0.001 LINK from Canton → Sepolia: "Hello"
🆔 CCIP Message ID: 0x…
🔗 CCIP Explorer: https://ccip.chain.link/#/side-drawer/msg/0x…

Verification and Monitoring

CCIP Explorer

Monitor message status and destination execution state in the CCIP Explorer.

Manual execution on Sepolia

After --no-exec sends, run manual execution once CCV verification completes:

Terminal
npm run canton2any:manual-exec -- <cantonUpdateIdOrMessageId>

Verify on Sepolia

Check TEST token balance and inspect the receiver contract's events on Sepolia Etherscan to confirm both token delivery and data processing.

Get the latest Chainlink content straight to your inbox.