Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions examples/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,29 @@ canton_explorer_url: ""

| Command | Description |
|-----------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------|
| `evm send-message --receiver-party <p> [--payload <text>] [--fee-token {link\|native}]` | Send a message from EVM to Canton with no token transfer. |
| `evm send-token --receiver-party <p> --amount <wei> [--fee-token {link\|native}]` | Send a token-only transfer (LINK) from EVM to Canton. |
| `evm send-message --receiver-party <p> [--payload <text>] [--fee-token {link\|native}] [--finality {finality\|safe\|N}]` | Send a message from EVM to Canton with no token transfer. |
| `evm send-token --receiver-party <p> --amount <wei> [--fee-token {link\|native}] [--finality {finality\|safe\|N}]` | Send a token-only transfer (LINK) from EVM to Canton. |
| `evm execute --message-id <0xhash> [--wait <duration>]` | Execute on EVM a message that was sent from Canton with the `noneExecution` executor. |
| `canton execute --message-id <0xhash> [--finality {finality\|safe\|N}] [--wait <duration>]` | Execute on Canton a message sent from EVM. Must use matching `--finality` if send used faster-than-finality. |

Because no executor currently supports Canton as a destination, the EVM-side
send commands always use the `noExecution` tag in extraArgs.

**Finality (`--finality`):** defaults to `finality` (wait for full Sepolia
finalization, slowest). Use `1` for one block confirmation (~12s), `safe` for
Ethereum safe head, or any depth `1`–`65535`. When using faster finality, pass
the same value to `canton execute`. The CLI selects (or creates) a
`CCIPReceiver` whose finality config matches `--finality`, sets `requiredCCVs`
from the indexer attestation (`verifier_dest_address`), and can keep separate
receivers for full finality and faster-than-finality on the same party.

### Canton → EVM

| Command | Description |
|------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `canton send-message --receiver <0xhex> [--payload <text>] [--executor {default\|none}] [--fee-token {link\|native}]` | Send a message-only CCIP message from Canton to EVM. |
| `canton send-token --receiver <0xhex> --amount <decimal> [--payload <text>] [--executor {default\|none}] [--fee-token {link\|native}]` | Send a LINK token transfer CCIP message from Canton to EVM. Note: when using LINK as the fee token, two separate input holdings must be provided, one for the fee and one for the token transfer. |
| `canton execute --message-id <0xhash> [--wait <duration>]` | Execute on Canton a message sent from EVM. |
| `canton execute --message-id <0xhash> [--wait <duration>]` | Execute on Canton a message sent from EVM (see EVM → Canton section for `--finality`). |
| `canton list-events --event {sent\|executed}` | List active `CCIPMessageSent` or `ExecutionStateChanged` contracts visible to the configured party. |
| `canton list-holdings [--cid]` | List all holdings for the configured party. Specify `--cid` to include the holding's Contract ID in the output. |
| `canton create-transfer --amount <decimal> [--token {link\|native}] [--receiver <party>] [--input <contractId>]` | Initiates a transfer of an given amount of an asset to another party/itself. Defaults to LINK, use `--token native` to switch to Amulet. |
Expand Down
13 changes: 10 additions & 3 deletions examples/cli/cmd/canton.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/smartcontractkit/chainlink-canton/contracts"
"github.com/smartcontractkit/chainlink-canton/examples/cli/internal/cantonops"
"github.com/smartcontractkit/chainlink-canton/examples/cli/internal/clients"
"github.com/smartcontractkit/chainlink-canton/examples/cli/internal/finality"
"github.com/smartcontractkit/chainlink-canton/examples/cli/internal/input"
oapiCommon "github.com/smartcontractkit/chainlink-canton/openapi/gen/eds/common"
oapiTransferInstruction "github.com/smartcontractkit/chainlink-canton/openapi/gen/transferInstructionV1"
Expand Down Expand Up @@ -367,6 +368,7 @@ func newCantonExecuteCmd(g *Globals) *cobra.Command {
var (
messageIDHex string
wait time.Duration
finalityName string
)
c := &cobra.Command{
Use: "execute",
Expand All @@ -377,6 +379,10 @@ func newCantonExecuteCmd(g *Globals) *cobra.Command {
if err != nil {
return err
}
fin, err := finality.Parse(finalityName)
if err != nil {
return err
}
messageId := common.HexToHash(messageIDHex)
fmt.Printf("Waiting for verifier results for %s (timeout %s)...\n", messageId.Hex(), wait)
resp, err := cantonops.WaitForVerifierResult(ctx, b.IndexerClient, messageId.Hex(), wait)
Expand All @@ -385,17 +391,18 @@ func newCantonExecuteCmd(g *Globals) *cobra.Command {
}
fmt.Printf("Verifier results for %s successfully retrieved.\n", messageId.Hex())

return cantonExecute(ctx, b, resp.Results[0].VerifierResult)
return cantonExecute(ctx, b, resp.Results[0].VerifierResult, fin)
},
}
c.Flags().StringVar(&messageIDHex, "message-id", "", "CCIP message id (0x-prefixed hex) (required)")
c.Flags().DurationVar(&wait, "wait", 15*time.Minute, "max time to wait for verifier results")
c.Flags().StringVar(&finalityName, "finality", "finality", "must match the send: finality (full), safe, or block depth 1-65535")
_ = c.MarkFlagRequired("message-id")

return c
}

func cantonExecute(ctx context.Context, b *clients.Bundle, vr protocol.VerifierResult) error {
func cantonExecute(ctx context.Context, b *clients.Bundle, vr protocol.VerifierResult, fin finality.Parsed) error {
withToken := vr.Message.TokenTransfer != nil

encodedMessage, err := vr.Message.Encode()
Expand All @@ -421,7 +428,7 @@ func cantonExecute(ctx context.Context, b *clients.Bundle, vr protocol.VerifierR
if err != nil {
return err
}
receiverCid, err := cantonops.GetOrCreateReceiver(ctx, b.Participant)
receiverCid, err := cantonops.GetOrCreateReceiver(ctx, b.Participant, fin.Receiver, verifierRawAddress)
if err != nil {
return err
}
Expand Down
25 changes: 21 additions & 4 deletions examples/cli/cmd/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ import (
"github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v2_0_0/onramp"
"github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/latest/ccip_offramp"
"github.com/smartcontractkit/chainlink-ccv/build/devenv/evm"
"github.com/smartcontractkit/chainlink-ccv/protocol"

"github.com/smartcontractkit/chainlink-canton/contracts"
"github.com/smartcontractkit/chainlink-canton/examples/cli/internal/cantonops"
"github.com/smartcontractkit/chainlink-canton/examples/cli/internal/clients"
"github.com/smartcontractkit/chainlink-canton/examples/cli/internal/evmops"
"github.com/smartcontractkit/chainlink-canton/examples/cli/internal/finality"
"github.com/smartcontractkit/chainlink-canton/examples/cli/internal/input"
)

Expand Down Expand Up @@ -71,6 +71,7 @@ func newEVMSendMessageCmd(g *Globals) *cobra.Command {
receiverParty string
payload string
feeTokenName string
finalityName string
)
c := &cobra.Command{
Use: "send-message",
Expand All @@ -81,6 +82,10 @@ func newEVMSendMessageCmd(g *Globals) *cobra.Command {
if err != nil {
return err
}
fin, err := finality.Parse(finalityName)
if err != nil {
return err
}
feeToken, err := resolveEthFeeToken(b, feeTokenName)
if err != nil {
return err
Expand All @@ -90,12 +95,13 @@ func newEVMSendMessageCmd(g *Globals) *cobra.Command {
receiverPartyHashed = contracts.HashedPartyFromString(b.Participant.PartyID)
}

return evmSend(ctx, b, receiverPartyHashed, []byte(payload), feeToken, nil)
return evmSend(ctx, b, receiverPartyHashed, []byte(payload), feeToken, nil, fin)
},
}
c.Flags().StringVar(&receiverParty, "receiver-party", "", "destination Canton party id (defaults to own party)")
c.Flags().StringVar(&payload, "payload", "Hello, Canton!", "message payload (text)")
c.Flags().StringVar(&feeTokenName, "fee-token", "link", "fee token (link|native)")
c.Flags().StringVar(&finalityName, "finality", "finality", "source finality: finality (full), safe, or block depth 1-65535")

return c
}
Expand All @@ -105,6 +111,7 @@ func newEVMSendTokenCmd(g *Globals) *cobra.Command {
receiverParty string
amountStr string
feeTokenName string
finalityName string
)
c := &cobra.Command{
Use: "send-token",
Expand All @@ -115,6 +122,10 @@ func newEVMSendTokenCmd(g *Globals) *cobra.Command {
if err != nil {
return err
}
fin, err := finality.Parse(finalityName)
if err != nil {
return err
}
amount, err := parseAmount(amountStr)
if err != nil {
return fmt.Errorf("invalid --amount %q (support decimal syntax and exponents, e.g. 1e18): %w", amountStr, err)
Expand All @@ -132,12 +143,13 @@ func newEVMSendTokenCmd(g *Globals) *cobra.Command {
receiverPartyHashed = contracts.HashedPartyFromString(b.Participant.PartyID)
}

return evmSend(ctx, b, receiverPartyHashed, nil, feeToken, tokenAmounts)
return evmSend(ctx, b, receiverPartyHashed, nil, feeToken, tokenAmounts, fin)
},
}
c.Flags().StringVar(&receiverParty, "receiver-party", "", "destination Canton party id (defaults to own party)")
c.Flags().StringVar(&amountStr, "amount", "", "token amount in wei (required; supports exponents, e.g. 1e18)")
c.Flags().StringVar(&feeTokenName, "fee-token", "link", "fee token (link|native)")
c.Flags().StringVar(&finalityName, "finality", "finality", "source finality: finality (full), safe, or block depth 1-65535")
_ = c.MarkFlagRequired("amount")

return c
Expand All @@ -153,14 +165,15 @@ func evmSend(
payload []byte,
feeToken common.Address,
tokens []routerwrapper.ClientEVMTokenAmount,
fin finality.Parsed,
) error {
router, err := routerwrapper.NewRouter(b.Profile.EthRouterAddress, b.ETHClient)
if err != nil {
return fmt.Errorf("bind router: %w", err)
}

extraArgs, err := evm.NewV3ExtraArgs(
protocol.FinalityWaitForFinality,
fin.EVM,
uint32(0),
b.Profile.NoExecutionTag.Hex(),
nil, nil, nil, nil,
Expand Down Expand Up @@ -222,6 +235,7 @@ func evmSend(
tw.AppendHeader(table.Row{"Field", "Value"})
tw.AppendRow(table.Row{"Receiver", "0x" + common.Bytes2Hex(msg.Receiver)})
tw.AppendRow(table.Row{"Data", "0x" + common.Bytes2Hex(msg.Data)})
tw.AppendRow(table.Row{"Finality", fin.Label})
tw.AppendRow(table.Row{"Fee Token", msg.FeeToken.Hex()})
tw.AppendRow(table.Row{"Tx Value", auth.Value.String()})
if len(msg.TokenAmounts) > 0 {
Expand Down Expand Up @@ -264,6 +278,9 @@ func evmSend(
}
fmt.Printf("✅ Message sent with ID: %s\n", common.BytesToHash(sent.MessageId[:]))
fmt.Println(b.CCIPExplorerLink(common.BytesToHash(sent.MessageId[:]).Hex()))
if fin.Label != "WaitForFinality" {
fmt.Println("Run canton execute with the same --finality value used for the send.")
}

return nil
}
Expand Down
Loading
Loading