Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
104 changes: 81 additions & 23 deletions ccip-api-ref/docs-cli/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Pass directly or specify a file path:
| Aptos | Hex private key, or path to text file containing it |
| Sui | Hex or base64 private key |
| TON | 64-byte hex private key (`0x`-prefixed), mnemonic phrase (space-separated), path to key file, or `ledger[:index]` |
| Canton | 64-character hex Ed25519 seed (optional; party ID comes from `--canton-config`) |
| Canton | Not required — party ID comes from `--canton-config`; transactions submit via JWT |

### Foundry Cast Keystore

Expand Down Expand Up @@ -243,33 +243,34 @@ Canton operations require a JSON config file passed via `--canton-config <path>`

```json
{
"party": "sender::1220...",
"ccipParty": "ccip::1220...",
"jwt": "eyJ...",
"party": "u_7c1f39da042a::1220c250c23c...",
"ccipParty": "ccipOwner::1220e382f4e5...",
"auth": {
"type": "clientCredentials",
"authUrl": "https://auth.example.com/oauth2/default"
},
"edsUrl": "https://eds.example.com",
"transferInstructionUrl": "https://transfer-instruction.example.com",
"externalEdsUrlsByOwner": {
"owner::1220...": "https://external-eds.example.com"
},
"indexerUrl": "https://indexer.example.com",
"chainId": "canton:TestNet",
"senderInstanceId": "ccipsender",
"defaultSendGasLimit": 50000,
"feeTransferFactoryAmount": "1.0",
"ccvs": ["instanceId@party::1220..."],
"packages": {
"perPartyRouter": "ccip-runtime",
"ccipSender": "ccip-sender",
"ccipReceiver": "ccip-receiver"
}
"perPartyRouter": "ccip-runtime-v2",
"ccipReceiver": "ccip-receiver-v2",
"ccipSender": "ccip-sender-v2"
},
"senderInstanceId": "prod-ccipsender",
"ccvs": ["0x5b92820da106..."]
}
```

Set `CANTON_CLIENT_ID` and `CANTON_CLIENT_SECRET` env vars for the `clientCredentials` flow, or use `"jwt": "eyJ..."` in place of the `auth` block for a pre-obtained token. See [Canton Authentication](#canton-authentication) for all flows.

| Field | Required | Description |
| -------------------------- | -------- | -------------------------------------------------------------------------------- |
| `party` | Yes | User ledger party for actAs and transaction visibility |
| `ccipParty` | Yes | CCIP operator party (CCIPSender signatory / fee recipient) |
| `jwt` | Yes | JSON Web Token for Canton Ledger API authentication |
| `jwt` | No\* | JSON Web Token for Canton Ledger API authentication |
| `auth` | No\* | OIDC auth config (alternative to `jwt` — see [below](#canton-authentication)) |
| `edsUrl` | Yes | Base URL for the Explicit Disclosure Service (EDS) |
| `transferInstructionUrl` | Yes | Base URL for the Transfer Instruction API |
| `externalEdsUrlsByOwner` | No | Map of owner party → external EDS URL |
Expand All @@ -281,22 +282,79 @@ Canton operations require a JSON config file passed via `--canton-config <path>`
| `ccvs` | No | CCV instance addresses for execute disclosures and send defaults |
| `packages` | No | DAR package names for ACS template filters |

\* Either `jwt` or `auth` is required. If both are present, `jwt` takes precedence.

> [!NOTE]
> The top-level `jwt` field is shorthand for `auth: { type: "static", jwt }` — both are equivalent. If both are present, `jwt` takes precedence.

### Canton Authentication {#canton-authentication}

The `auth` object supports three flows. The `static` flow wraps a pre-obtained JWT (equivalent to the top-level `jwt` field); `clientCredentials` and `authorizationCode` obtain a JWT automatically via [OpenID Connect (OIDC)](https://openid.net/connect/):

| `auth.type` | Use case | Required `auth` fields |
| ------------------- | -------------------------------- | --------------------------------------- |
| `static` | Pre-obtained JWT | `jwt` |
| `clientCredentials` | Machine-to-machine (CI/CD) | `authUrl`, `clientId`†, `clientSecret`† |
| `authorizationCode` | Interactive browser login (PKCE) | `authUrl`, `clientId`† |

† `clientId` and `clientSecret` may be omitted from the config file and resolved from `CANTON_CLIENT_ID` / `CANTON_CLIENT_SECRET` env vars instead. **Keep secrets in env vars, not in config files.**

**Client credentials example** (CI/CD, machine-to-machine):

```json
{
"auth": {
"type": "clientCredentials",
"authUrl": "https://auth.example.com"
}
}
```

```bash
export CANTON_CLIENT_ID="my-client-id"
export CANTON_CLIENT_SECRET="my-client-secret"
```

**Authorization code example** (interactive browser login for human users):

```json
{
"auth": {
"type": "authorizationCode",
Comment thread
friedemannf marked this conversation as resolved.
"authUrl": "https://auth.example.com",
"callbackUrl": "http://localhost:8400/callback"
}
}
```

```bash
export CANTON_CLIENT_ID="my-client-id"
```

The `authorizationCode` flow opens a browser for login and starts a local callback server to receive the authorization code. The `callbackUrl` defaults to `http://localhost:8400/callback` — override it if port 8400 is in use or your OIDC provider requires a different redirect URI. PKCE with S256 is required.

When `auth` is set, the CLI resolves a JWT upfront (before connecting to the ledger) via the SDK's runtime-agnostic OAuth 2.0 protocol helpers (backed by [`oauth4webapi`](https://github.com/panva/oauth4webapi)). The SDK itself never orchestrates an OAuth flow — it only consumes what it's given (`jwt` or a `tokenGetter`). For `clientCredentials` and `authorizationCode`, the CLI injects a `tokenGetter` so tokens are refreshed automatically per request. Optional `auth` fields: `audience` (Auth0-specific), `scopes` (defaults to `daml_ledger_api` for client credentials, `openid daml_ledger_api` for authorization code), `callbackUrl` (authorization code only, defaults to `http://localhost:8400/callback`).

> [!NOTE]
> The `authorizationCode` flow is orchestrated by the CLI: it starts a local callback server (`node:http`) and opens the default browser (`open`/`xdg-open`). These Node-specific steps live in the CLI, not the SDK, so the SDK stays runtime-agnostic (no `node:*` imports) and can be embedded in web/Electron apps. Web embedders compose the SDK's protocol helpers (`buildAuthorizationRequest`, `validateAuthorizationCallback`, `exchangeAuthorizationCode`) with their own redirect/callback handling.

### Canton Wallet

On Canton, the party ID comes from config. For external signing, pass a 64-character hex Ed25519 seed via `--wallet`:
On Canton, the party ID comes from config and transactions are submitted directly using the JWT from config (either the static `jwt` field or a token resolved from `auth`). The `--wallet` flag is not required for Canton operations.

```bash
ccip-cli send canton-testnet ethereum-testnet-sepolia \
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 \
-r ccipsender \
-w <64-char-ed25519-seed> \
-t link-token=1.0
--to 0xReceiverContract \
-t link-token=1.0 \
--no-estimate-gas-limit
```

Without `--wallet`, the CLI submits transactions directly using the JWT from config.

## Shell Completion

Enable tab-completion for commands and options by adding the completion script to your shell profile:
Expand Down
27 changes: 21 additions & 6 deletions ccip-api-ref/docs-cli/manual-exec.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -158,19 +158,34 @@ ccip-cli manual-exec 0xabc123... \
--receiver-object-ids 0xabc... 0xdef...
```

### Canton execution by update ID
### Canton source by update ID

For Canton destinations, pass the ledger update ID from the source send transaction:
When the **source is Canton**, pass the ledger update ID from the Canton send transaction:

```bash
ccip-cli manual-exec 1220a1b2c3d4... \
--canton-config ./canton-config.json \
--rpc https://ledger.example.com/api/json \
--receiver sender::1220... \
--wallet <64-char-ed25519-seed>
--rpc https://ethereum-sepolia-rpc.example.com \
--wallet ledger
```

Canton manual execution requires `--canton-config` with `indexerUrl` (or `--indexer`) for CCV verifications. The `--receiver` flag accepts a CCIPReceiver contract ID, a party ID, or the keccak256 hash from the message receiver field.
The `--wallet` flag is required when the destination is EVM (or another chain) — to sign the execution transaction. When the destination is Canton, `--wallet` is not needed — the JWT from `--canton-config` (either the static `jwt` field or a token resolved from `auth`) is used instead.

When the source is EVM (or another chain), pass the source transaction hash as usual — the CLI auto-detects the format. You can also use a CCIP message ID (32-byte hex) with `--api` enabled (the default), which fetches execution inputs from the CCIP API without needing source chain RPC access.

Canton manual execution requires `--canton-config` with `indexerUrl` (or `--indexer`) for CCV verifications. When the destination is Canton, use `--receiver` to specify a CCIPReceiver contract ID, party ID (`hint::1220…`), or keccak256(party) hash — it defaults to the message receiver from the original CCIP request.

### Canton destination execution

When the **destination is Canton**, `--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 execution transaction. Pass the source transaction hash or CCIP message ID (use [`ccip-cli show`](/cli/show) to find it):

```bash
ccip-cli manual-exec 0x1234... \
--canton-config ./canton-config.json \
--rpc https://ledger.example.com/api/json \
--rpc https://ethereum-sepolia-rpc.example.com
```

## Execution Flow

Expand Down Expand Up @@ -204,7 +219,7 @@ After successful execution, buffers auto-clear. Lookup tables require a grace pe

## Canton Considerations

Canton execution uses the ledger's interactive submission flow (prepare → sign → execute) when an external signer is provided via `--wallet`. Without `--wallet`, transactions are submitted directly using the JWT from config.
Canton execution submits transactions directly using the JWT from config (either the static `jwt` field or a token resolved from `auth`).

| Input | Format |
| --------- | ----------------------------------------------------------------------- |
Expand Down
6 changes: 4 additions & 2 deletions ccip-api-ref/docs-cli/send.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -244,17 +244,19 @@ ccip-cli send \

### 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:

```bash
ccip-cli send \
-s canton-testnet \
-d ethereum-testnet-sepolia \
-r ccipsender \
-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 \
--estimate-gas-limit -101
--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`:
Expand Down
44 changes: 28 additions & 16 deletions ccip-api-ref/docs-sdk/guides/manual-execution.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -290,38 +290,44 @@ Unlike `generateUnsignedSendMessage` (which may return multiple transactions for

## Canton Execution

Canton manual execution requires a `CantonConfig` with `indexerUrl` for CCV verifications. The destination chain must be connected with `CantonChain.fromUrl`:
Canton manual execution requires a `CantonConfig` with `indexerUrl` for CCV verifications. The destination chain must be connected with `CantonChain.fromUrl`. No private key or signer is needed — the JWT from `cantonConfig` authenticates the submission. The `wallet` parameter only needs a `party` ID (for `actAs` in command submissions) — this is the same `party` already specified in `cantonConfig`:

```typescript
import { CantonChain } from '@chainlink/ccip-sdk'
import { CantonChain, type CantonConfig } from '@chainlink/ccip-sdk'

const cantonConfig: CantonConfig = {
party: 'receiver::1220...',
ccipParty: 'ccip::1220...',
// Either a static JWT, or a tokenGetter for refreshable tokens (OAuth2).
// The CLI resolves its `auth` block into one of these upfront — see
// Canton Configuration. Web/Electron embedders compose the SDK's OAuth2
// protocol helpers and inject a tokenGetter here.
jwt: 'eyJ...',
edsUrl: 'https://eds.example.com',
transferInstructionUrl: 'https://transfer-instruction.example.com',
indexerUrl: 'https://indexer.example.com',
}

const dest = await CantonChain.fromUrl('https://ledger.example.com/api/json', {
cantonConfig: {
party: 'receiver::1220...',
ccipParty: 'ccip::1220...',
jwt: 'eyJ...',
edsUrl: 'https://eds.example.com',
transferInstructionUrl: 'https://transfer-instruction.example.com',
indexerUrl: 'https://indexer.example.com',
},
cantonConfig,
})

// Execute by message ID (CCIP API provides verifications)
const execution = await dest.execute({
messageId: '0x1234...abcd',
wallet: cantonWallet,
wallet: { party: cantonConfig.party }, // Canton wallet — reuses the config party, no signer needed
})

// Or execute with pre-fetched input (CCIP v2.0 only)
const execution = await dest.execute({
encodedMessage: '0x...',
verifications: [...],
wallet: cantonWallet,
wallet: { party: cantonConfig.party },
receiver: 'receiver::1220...', // optional; defaults to message receiver
})
```

Canton accepts **ledger update IDs** (`1220` + SHA-256 digest) as transaction identifiers. Use `CantonChain.isTxHash()` to validate update ID format.
Canton accepts **ledger update IDs** (`1220` + SHA-256 digest) as transaction identifiers when the source is Canton. Use `CantonChain.isTxHash()` to validate update ID format.

## Using the CLI

Expand Down Expand Up @@ -350,12 +356,18 @@ ccip-cli manual-exec 0xSourceTxHash \
--wallet $PRIVATE_KEY \
--log-index 1

# Canton destination (by ledger update ID)
# Canton source (by ledger update ID; --wallet needed for non-Canton dest)
ccip-cli manual-exec 1220a1b2c3d4... \
--canton-config ./canton-config.json \
--rpc https://ledger.example.com/api/json \
--receiver sender::1220... \
--wallet <64-char-ed25519-seed>
--rpc https://ethereum-sepolia-rpc.example.com \
--wallet $PRIVATE_KEY

# Canton destination (no --wallet needed; JWT from canton-config is used)
ccip-cli manual-exec 0x1234... \
--canton-config ./canton-config.json \
--rpc https://ledger.example.com/api/json \
--rpc https://ethereum-sepolia-rpc.example.com
```

The CLI auto-detects whether the argument is a message ID, transaction hash, or Canton update ID.
Expand Down
11 changes: 7 additions & 4 deletions ccip-api-ref/docs-sdk/guides/multi-chain.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ For EVM-only usage, see [Sending Messages](/sdk/guides/sending-messages) and [Tr
:::note
**Sui is execution-only** — it can execute incoming messages but cannot send cross-chain messages. Token pool queries are not implemented on Sui, TON, or Canton.

**Canton is partial** — it supports send and manual execution but requires a `CantonConfig` object (party IDs, JWT, EDS URLs). Fee estimation, balance queries, message tracking via `getMessagesInTx`, and registry/pool queries are not yet implemented.
**Canton is partial** — it supports send and manual execution but requires a `CantonConfig` object (party IDs, JWT or `tokenGetter`, EDS URLs). Fee estimation, balance queries, message tracking via `getMessagesInTx`, and registry/pool queries are not yet implemented.
:::

## Connecting
Expand Down Expand Up @@ -85,6 +85,9 @@ const chain = await CantonChain.fromUrl('https://ledger.example.com/api/json', {
cantonConfig: {
party: 'sender::1220...',
ccipParty: 'ccip::1220...',
// Either a static JWT, or a tokenGetter for refreshable tokens (OAuth2).
// The CLI resolves its `auth` block into one of these upfront — see
// Canton Configuration.
jwt: 'eyJ...',
edsUrl: 'https://eds.example.com',
transferInstructionUrl: 'https://transfer-instruction.example.com',
Expand All @@ -96,7 +99,7 @@ const chain = await CantonChain.fromUrl('https://ledger.example.com/api/json', {
console.log('Connected to:', chain.network.name)
```

Canton requires `cantonConfig` in the second argument to `fromUrl`. See [Canton Configuration](/cli/configuration#canton-configuration) for the full config schema.
Canton requires `cantonConfig` in the second argument to `fromUrl`. Either `jwt` (pre-obtained token) or `tokenGetter` (a `() => Promise<string>` for refreshable OAuth2 tokens) must be provided. The SDK is runtime-agnostic — it never orchestrates an OAuth flow; the CLI or embedder resolves auth upfront and injects the result. See [Canton Configuration](/cli/configuration#canton-configuration) for the full config schema.

</TabItem>
</Tabs>
Expand Down Expand Up @@ -293,7 +296,7 @@ const message = {
import { CantonChain, networkInfo } from '@chainlink/ccip-sdk'

const source = await CantonChain.fromUrl('https://ledger.example.com/api/json', {
cantonConfig: { /* party, ccipParty, jwt, edsUrl, transferInstructionUrl, ... */ },
cantonConfig: { /* party, ccipParty, jwt/tokenGetter, edsUrl, transferInstructionUrl, ... */ },
})

const destSelector = networkInfo('ethereum-testnet-sepolia').chainSelector
Expand Down Expand Up @@ -407,7 +410,7 @@ const signature = await sendTransaction(transaction, connection)

### Configuration

Canton requires a `CantonConfig` object passed via `ChainContext.cantonConfig` when calling `CantonChain.fromUrl`. Required fields: `party`, `ccipParty`, `jwt`, `edsUrl`, `transferInstructionUrl`. See [Canton Configuration](/cli/configuration#canton-configuration) for the full schema.
Canton requires a `CantonConfig` object passed via `ChainContext.cantonConfig` when calling `CantonChain.fromUrl`. Required fields: `party`, `ccipParty`, `edsUrl`, `transferInstructionUrl` — plus either `jwt` (pre-obtained token) or `tokenGetter` (a `() => Promise<string>` for refreshable OAuth2 tokens). The SDK is runtime-agnostic and never orchestrates an OAuth flow — the CLI resolves its `auth` block upfront and injects `jwt`/`tokenGetter`; web/Electron embedders compose the SDK's OAuth2 protocol helpers (`buildAuthorizationRequest`, `validateAuthorizationCallback`, `exchangeAuthorizationCode`) with their own redirect/callback handling. See [Canton Configuration](/cli/configuration#canton-configuration) for the full schema.

### Identity and Addresses

Expand Down
4 changes: 4 additions & 0 deletions ccip-api-ref/docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ const config: Config = {
//
// - `path`: used by bigint-buffer / postman-code-generators (pre-existing)
// - `undici`: used by @chainlink/ccip-sdk's Canton client (CantonChain → canton/client.ts).
//
// The Canton auth-code provider's `node:*` imports (callback server, browser
// launching) have been moved to the CLI, so no `node:` scheme stubs are
// needed here anymore — the SDK is now runtime-agnostic.
function webpackNodeFallbacks(): Plugin {
return {
name: 'webpack-node-fallbacks',
Expand Down
Loading
Loading