LockRelease Token Pool Deployment (Canton)

Deploy a LockRelease token pool on Canton, fund pool-owner liquidity, register on the Token Admin Registry (TAR), and enable a cross-chain lane. Use the ccip-starter-kit-canton for pool deployment and TAR registration, or submit Ledger API commands directly.

This guide assumes you have a token instrument live on Canton with supply minted on-ledger, supporting the CIP-56 TransferFactory interface and transfer pre-approvals.

LockRelease is for fixed total supply tokens where cross-chain transfers are backed by locked liquidity — not mint/burn. On send, tokens lock to the pool owner; on receive, the pool releases from its holdings.

Scope: Deploy pool, fund liquidity, register on TAR, enable a lane. Excludes EDS setup and end-to-end transfer tests.

Prerequisites

  • Instrument with known InstrumentId = { admin, id } supporting TransferFactory and pre-approvals.
  • Token liquidity held by the pool owner party for expected release volume.
  • CCIP DAR packages from contracts/dars/v2_0_0.
  • CCIP contract references from Chainlink ops.
  • Starter kit setup (if using cct:* scripts) — complete Canton as Source prerequisites and the CCT overview.

Deployment overview

StepActionStarter kitManual
1Deploy LockReleaseTokenPoolcct:deploy-lock-releaseLedger API CreateCommand
2Fund holdings + set pre-approvalLedger API choices
3Register on TARcct:set-tokenTAR choices via Ledger API
4Enable laneRate limiters + ApplyChainUpdates
5Go liveEDS + verification

Step 1 — Deploy the LockRelease Token Pool

Create one LockReleaseTokenPool. Signatory: poolOwner.

Terminal
npm run cct:deploy-lock-release -- \
  --instanceId acme-eur-lr-pool \
  --instrument 'yourParty::1220…::token-id'

Flags:

FlagRequiredDescription
--instanceIdYesUnique pool instance ID (e.g. acme-eur-lr-pool)
--instrumentYesCanton instrument ID: party::fingerprint::tokenId
--poolOwnerNoParty that holds pool liquidity (defaults to party in canton-config.json)
--ccipOwnerNoDefaults to ccipParty in canton-config.json
--decimalsNoToken decimals on Canton (default 10)

Expected output:

Deploying LockRelease token pool on Canton
   instanceId: acme-eur-lr-pool
   instrument: yourParty::1220…::token-id
   poolOwner:  yourParty::1220…
✅ LockReleaseTokenPool contract ID: 00abc…
   Pool address: acme-eur-lr-pool@yourParty::1220…

Next steps:
   1. Fund the pool owner with token liquidity
   2. Set the pool receive preapproval (AddPoolReceiveContextContractValue)
   3. Register on the Token Admin Registry:
      npm run cct:set-token -- --instrument "…" --poolInstanceId "acme-eur-lr-pool" --poolOwner "…"

Transfer-fee fields

When setting tokenTransferFeeConfigs (map keyed by destination chain selector):

FieldMeaning
isEnabledMust be true to apply the config
feeUSDCentsFlat fee in USD cents (non-negative)
destGasOverheadGas for destination execution (> 0 when set via choice)
destBytesOverheadData-availability bytes overhead (≥ 32)
feeBpsProportional fee in basis points (< 10000)

CCIP hosted addresses

FieldTestnetMainnet
Token Admin Registrytokenadminregistry-lzrnd@ccipOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551tokenadminregistry-ckswd@ccipOwner::122012714685760dc1927c4cfe119ce2126c48756154e95c06f5c181da05a5519093
RMNRemotermn_remote-nzvtd@rmnOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551rmn_remote-mnprd@rmnOwner::122012714685760dc1927c4cfe119ce2126c48756154e95c06f5c181da05a5519093
FeeQuoterfeequoter-scxln@ccipOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551feequoter-fpxih@ccipOwner::122012714685760dc1927c4cfe119ce2126c48756154e95c06f5c181da05a5519093
CCIP owner partyccipOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551ccipOwner::122012714685760dc1927c4cfe119ce2126c48756154e95c06f5c181da05a5519093
Global EDShttps://eds.testnet.ccip.chain.linkhttps://eds.ccip.chain.link
Indexerhttps://indexer-1.testnet.ccip.chain.linkhttps://indexer-1.ccip.chain.link (backup: indexer-2)
Canton chain selector92687312186494980742308837218439511688
Default CommitteeVerifiercommitteeverifier-tqkny@ccvOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551committeeverifier-mnprd@ccvOwner::122096accf0a84fc7d80d5fce5ea3135317a03eb22e62e0d8cdd7548865f984f11ff

Step 2 — Fund the pool and configure pre-approval

LockRelease liquidity comes from the pool owner's token holdings. Top up those holdings to match expected outbound volume — there is no separate on-pool deposit step. When a receiver executes an inbound transfer on Canton, tokens move from the pool owner's holdings to the recipient.

Set pool receive preapproval

Record the token's transfer preapproval in poolReceiveContext so inbound transfers to the pool owner succeed atomically on send:

curl --request POST \
  --url https://<your-participant>/api/json/v2/commands/submit-and-wait-for-transaction-tree \
  --header "authorization: Bearer $JWT" \
  --header 'content-type: application/json' \
  --data '{
  "commands": [
    {
      "ExerciseCommand": {
        "templateId": "#ccip-lock-release-token-pool:CCIP.LockReleaseTokenPool:LockReleaseTokenPool",
        "contractId": "<POOL_CONTRACT_ID>",
        "choice": "AddPoolReceiveContextContractValue",
        "choiceArgument": {
          "contextKey": "transfer-preapproval",
          "referredContract": "<PREAPPROVAL_CONTRACT_ID>"
        }
      }
    }
  ],
  "commandId": "set-pool-receive-preapproval",
  "actAs": ["<POOL_OWNER_PARTY>"]
}'
FieldValue
contextKeyKey the TransferFactory expects, e.g. transfer-preapproval
referredContractContractId of the pool owner's transfer preapproval

Related choices: AddPoolReceiveContextNonContractValue, RemovePoolReceiveContextValue, ClearPoolReceiveContext.

Step 3 — Register on the Token Admin Registry

TAR maps each instrument to a single token pool. Registration: propose → accept → set pool.

Terminal
npm run cct:set-token -- \
  --instrument 'yourParty::1220…::token-id' \
  --poolInstanceId acme-eur-lr-pool

Use the same flags as BurnMint Step 2. The script runs ProposeAdministratorAcceptAdminRoleSetPool and prints ledger update links on success.

Step 4 — Enable a lane

Lane enablement is not automated by the starter kit.

1. Deploy three rate limiters

Each lane requires RateLimiter contracts (for testing, deploy disabled with zero capacity/rate):

  • Inbound (default finality)
  • Inbound (custom finality)
  • Outbound

Created by poolOwner with matching poolInstanceId, poolOwner, and remoteChainSelector.

2. Apply the chain update

Call ApplyChainUpdates on the pool:

ChainUpdate fieldGuidance
remoteChainSelectorDestination chain selector — see CCIP Directory
remotePoolsRemote token pool address(es)
remoteTokenAddressRemote token address
inboundCCVs / outboundCCVsDefault CommitteeVerifier (testnet address above) unless custom
finalityConfigWaitForFinality for default finalized source tx
inboundRateLimiterInbound default limiter CID
inboundCustomBlockConfirmationsRateLimiterInbound custom limiter CID
outboundRateLimiterOutbound limiter CID

Once applied, the on-ledger lane is complete.

Step 5 — Go live

Verification checklist

  1. TAR maps instrument to pool with your party as admin.
  2. Pool at {instanceId}@{poolOwner} with matching InstrumentId.
  3. Pool owner holds sufficient liquidity; receive preapproval set in poolReceiveContext.
  4. Lane rate limiters in place.

Stand up EDS

The reference EDS supports LockRelease, including holdings and TransferFactory context for pre-approvals.

Test transactions

See CCIP on Canton — Overview and CCIP Explorer.

What's next

Get the latest Chainlink content straight to your inbox.