Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"excludeFiles": ["**/*.test.ts", "**/__tests__/**", "**/__mocks__/**"],
"rules": {
"ccip/restricted-syntax": "error",
"import/no-nodejs-modules": ["error", { "allow": ["buffer"] }]
"import/no-nodejs-modules": ["error", { "allow": ["buffer", "node:child_process", "node:http"] }]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No node modules on ccip-sdk, pls

}
},
{
Expand Down
101 changes: 78 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,76 @@ 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 on demand via the SDK's authentication package (backed by [`oauth4webapi`](https://github.com/panva/oauth4webapi)). 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), `openBrowser` (default `true`), `timeoutMs` (default `120000`).

### 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
40 changes: 24 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,40 @@ 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` (either the static `jwt` field or a token resolved from `auth`) 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...',
jwt: 'eyJ...', // or use `auth` for OIDC — see Canton Configuration
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 +352,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
10 changes: 5 additions & 5 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 OIDC auth config, EDS URLs). Fee estimation, balance queries, message tracking via `getMessagesInTx`, and registry/pool queries are not yet implemented.
:::

## Connecting
Expand Down Expand Up @@ -85,7 +85,7 @@ const chain = await CantonChain.fromUrl('https://ledger.example.com/api/json', {
cantonConfig: {
party: 'sender::1220...',
ccipParty: 'ccip::1220...',
jwt: 'eyJ...',
jwt: 'eyJ...', // or use `auth` for OIDC — see Canton Configuration
edsUrl: 'https://eds.example.com',
transferInstructionUrl: 'https://transfer-instruction.example.com',
indexerUrl: 'https://indexer.example.com',
Expand All @@ -96,7 +96,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 `auth` (OIDC config) must be provided. See [Canton Configuration](/cli/configuration#canton-configuration) for the full config schema.

</TabItem>
</Tabs>
Expand Down Expand Up @@ -293,7 +293,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/auth, edsUrl, transferInstructionUrl, ... */ },
})

const destSelector = networkInfo('ethereum-testnet-sepolia').chainSelector
Expand Down Expand Up @@ -407,7 +407,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 `auth` (OIDC config). See [Canton Configuration](/cli/configuration#canton-configuration) for the full schema.

### Identity and Addresses

Expand Down
18 changes: 18 additions & 0 deletions ccip-api-ref/docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,34 @@ 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).
// - `node:` scheme URIs: used by the Canton auth-code provider (callback server, browser
// launching). Webpack 5 throws UnhandledSchemeError for `node:` URIs; we use a
// NormalModuleReplacementPlugin to redirect them to an empty stub before resolution.
function webpackNodeFallbacks(): Plugin {
return {
name: 'webpack-node-fallbacks',
configureWebpack(_config, isServer) {
if (isServer) return {}
const webpack = require('webpack')
const { NormalModuleReplacementPlugin } = webpack
const nodeMods = ['node:child_process', 'node:http', 'node:url']
return {
resolve: {
alias: { undici: false },
fallback: { path: false },
},
plugins: nodeMods.map((mod) => {
const plugin = new NormalModuleReplacementPlugin(
new RegExp(`^${mod.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`),
(resource: { request: string }) => {
// Redirect node: imports to a stub that exports empty values
// for any named import (exec, createServer, URL, etc.).
resource.request =
'data:text/javascript,export default {};export const exec=()=>{};export const createServer=()=>{};export const URL=globalThis.URL;export const spawn=()=>{};'
},
)
return plugin
}),
}
},
}
Expand Down
Loading
Loading