# LockRelease Token Pool Deployment (Canton)
Source: https://docs.chain.link/ccip/tutorials/canton/cross-chain-tokens/lock-release-token-pool
Last Updated: 2026-07-06

> For the complete documentation index, see [llms.txt](/llms.txt).

> **CAUTION**
>
> Addresses shown may change due to upgrades since Canton contracts are immutable. Confirm party IDs and contract addresses with [Chainlink CCIP](https://chain.link/ccip-contact).

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`](https://github.com/smartcontractkit/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 preapprovals.

**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](/ccip/concepts/canton/explicit-disclosure) and end-to-end transfer tests.

> **NOTE: Testnet only**
>
> Starter kit `cct:*` scripts target **Canton testnet** only.

## Prerequisites

- **Instrument (token managed on Canton Ledger)** with known `InstrumentId = { admin, id }` supporting `TransferFactory` and preapprovals.
- Token liquidity held by the pool owner party for expected release volume.
- **LockRelease token pool DAR** on your participant — [`ccip-lock-release-token-pool-2.1.0.dar`](https://github.com/smartcontractkit/chainlink-canton/blob/main/contracts/dars/released/ccip-lock-release-token-pool-v2-2.1.0.dar) (bundles its CCIP dependencies — the only DAR you upload).
- CCIP contract references from Chainlink ops.
- **Starter kit setup** (if using `cct:*` scripts) — complete [Canton as Source prerequisites](/ccip/tutorials/canton/source/prerequisites) and the [CCT overview](/ccip/tutorials/canton/cross-chain-tokens#starter-kit-cct-scripts).

> **CAUTION: Pool owner trust**
>
> CCIP fully trusts the pool owner. Strongly recommend pool owner equals instrument admin, decentralized across nodes.

## Deployment overview

| Step | Action                          | Starter kit               | Manual                              |
| :--- | :------------------------------ | :------------------------ | :---------------------------------- |
| 1    | Deploy `LockReleaseTokenPool`   | `cct:deploy-lock-release` | Ledger API `CreateCommand`          |
| 2    | Fund holdings + set preapproval | —                         | Ledger API choices                  |
| 3    | Register on TAR                 | `cct:set-token`           | TAR choices via Ledger API          |
| 4    | Enable lane                     | —                         | Rate limiters + `ApplyChainUpdates` |
| 5    | Go live                         | —                         | EDS + verification                  |

## Step 1 — Deploy the LockRelease Token Pool

Create one [`LockReleaseTokenPool`](https://github.com/smartcontractkit/chainlink-canton/blob/main/contracts/ccip/pools/lock-release-token-pool/daml/CCIP/LockReleaseTokenPoolV2.daml). Signatory: `poolOwner`.

### Transfer-fee fields

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

| Field               | Meaning                                                 |
| :------------------ | :------------------------------------------------------ |
| `isEnabled`         | Must be `true` to apply the config                      |
| `feeUSDCents`       | Flat fee in USD cents (non-negative)                    |
| `destGasOverhead`   | Gas for destination execution (> 0 when set via choice) |
| `destBytesOverhead` | Data-availability bytes overhead (≥ 32)                 |
| `feeBps`            | Proportional fee in basis points (\< 10000)             |

### CCIP hosted addresses

| Field                     | Testnet                                                                                                    | Mainnet                                                                                                                |
| :------------------------ | :--------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------- |
| Token Admin Registry      | `tokenadminregistry-nbehb@ccipOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551` | Contact the [CCIP team](https://chain.link/ccip-contact) for the Token Admin Registry address to register a token pool |
| RMNRemote                 | `rmn_remote-pttst@rmnOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551`          | `rmn_remote-zqxpl@rmnOwner::122012714685760dc1927c4cfe119ce2126c48756154e95c06f5c181da05a5519093`                      |
| FeeQuoter                 | `feequoter-koyox@ccipOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551`          | `feequoter-dwxyx@ccipOwner::122012714685760dc1927c4cfe119ce2126c48756154e95c06f5c181da05a5519093`                      |
| CCIP owner party          | `ccipOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551`                          | `ccipOwner::122012714685760dc1927c4cfe119ce2126c48756154e95c06f5c181da05a5519093`                                      |
| Global EDS                | `https://eds.testnet.ccip.chain.link`                                                                      | `https://eds.ccip.chain.link`                                                                                          |
| Indexer                   | `https://indexer-1.testnet.ccip.chain.link`                                                                | Primary: `https://indexer-1.ccip.chain.link` · Backup: `https://indexer-2.ccip.chain.link`                             |
| Canton chain selector     | `9268731218649498074`                                                                                      | `2308837218439511688`                                                                                                  |
| Default CommitteeVerifier | `committeeverifier-pttst@ccvOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551`   | `committeeverifier-vnmkd@ccvOwner::122096accf0a84fc7d80d5fce5ea3135317a03eb22e62e0d8cdd7548865f984f11ff`               |

## Step 2 — Fund the pool and configure preapproval

LockRelease liquidity comes from the **pool owner's token holdings**. Top up those holdings to match expected inbound 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.

> **NOTE: Manual step**
>
> The starter kit does not automate funding or preapproval. If necessary, complete this step via the Ledger API before registering on TAR.

### Set pool receive preapproval

During outgoing transfers (sending) the LockRelease Token Pool will create a Transfer from the sender party to the pool's owner party.
The pool requires this transfer to complete immediately, which requires a preapproval to be created by the pool's owner party that is passed in during the relevant TransferFactory call.

There are two options for passing the necessary preapproval to the TransferFactory:

1. Off-Chain: the explicit disclosure server will provide the preapproval as part of its ChoiceContext response, which the user will pass on to the pool. The pool will then pass the relevant values on to the TransferFactory. (See [External APIs](/ccip/concepts/canton/explicit-disclosure#external-apis-third-party-operated) for details)
2. On-Chain: hard-code the preapproval's CID in the token pool's `poolReceiveContext`, which the pool will always inject in all calls to the TransferFactory. (Covered in this section)

> **NOTE: Long-lived Preapprovals**
>
> Hard-coding the preapproval's ContractId in the pool's `poolReceiveContext` will only work if the contract itself is long-lived and will not be archived.
> Any change in the preapproval's ContractId will require the pool's `poolReceiveContext` to be updated with the new ContractId.

Consult your token's implementation on details of the preapproval contract's lifecycle.

The pool allows for the pool owner to configure hard-coded context values that will automatically be passed to the instrument's TransferFactory.
To configure, record the token's transfer preapproval in `poolReceiveContext` so inbound transfers to the pool owner succeed atomically on send:

```shell
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-v2:CCIP.LockReleaseTokenPoolV2:LockReleaseTokenPool",
        "contractId": "<POOL_CONTRACT_ID>",
        "choice": "AddPoolReceiveContextContractValue",
        "choiceArgument": {
          "contextKey": "transfer-preapproval",
          "referredContract": "<PREAPPROVAL_CONTRACT_ID>"
        }
      }
    }
  ],
  "commandId": "set-pool-receive-preapproval",
  "actAs": ["<POOL_OWNER_PARTY>"]
}'
```

| Field              | Value                                                        |
| :----------------- | :----------------------------------------------------------- |
| `contextKey`       | Key the TransferFactory expects, e.g. `transfer-preapproval` |
| `referredContract` | ContractId 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**.

## Step 4 — Enable a lane

Lane enablement is **not** automated by the starter kit.

### 1. Deploy three rate limiters

Each lane requires [`RateLimiter`](https://github.com/smartcontractkit/chainlink-canton/blob/main/contracts/ccip/rate-limiter/daml/CCIP/RateLimiterV2.daml) 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`](https://github.com/smartcontractkit/chainlink-canton/blob/main/contracts/ccip/pools/lock-release-token-pool/daml/CCIP/LockReleaseTokenPoolV2.daml) on the pool:

| ChainUpdate field                            | Guidance                                                                        |
| :------------------------------------------- | :------------------------------------------------------------------------------ |
| `remoteChainSelector`                        | Destination chain selector — see [CCIP Directory](/ccip/directory/testnet)      |
| `remotePools`                                | Remote token pool address(es). Padded to 32 bytes in the case of EVM addresses. |
| `remoteTokenAddress`                         | Remote token address. Padded to 32 bytes in the case of EVM addresses.          |
| `inboundCCVs` / `outboundCCVs`               | Default CommitteeVerifier (testnet address above) unless custom                 |
| `finalityConfig`                             | `WaitForFinality` for default finalized source tx                               |
| `inboundRateLimiter`                         | Inbound default limiter address                                                 |
| `inboundCustomBlockConfirmationsRateLimiter` | Inbound custom limiter address                                                  |
| `outboundRateLimiter`                        | Outbound limiter address                                                        |

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](https://github.com/smartcontractkit/chainlink-canton/blob/main/eds/eds.Dockerfile) supports LockRelease, including holdings and TransferFactory context for preapprovals.

### Test transactions

See [CCIP on Canton — Overview](/ccip/concepts/canton/overview) and [CCIP Explorer](/ccip/tools-resources/ccip-explorer).

> **CAUTION: Educational Example Disclaimer**
>
> This page includes an educational example to use a Chainlink system, product, or service and is provided to
> demonstrate how to interact with Chainlink's systems, products, and services to integrate them into your own. This
> template is provided "AS IS" and "AS AVAILABLE" without warranties of any kind, it has not been audited, and it may be
> missing key checks or error handling to make the usage of the system, product or service more clear. Do not use the
> code in this example in a production environment without completing your own audits and application of best practices.
> Neither Chainlink Labs, the Chainlink Foundation, nor Chainlink node operators are responsible for unintended outputs
> that are generated due to errors in code.