# Cross-Chain Token Standard - Token Pools (EVM)
Source: https://docs.chain.link/ccip/concepts/cross-chain-token/evm/token-pools
Last Updated: 2025-05-19

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

Learn about standard and custom token pool contracts for facilitating CCT transfers on EVM chains. This page covers common requirements like mandatory functions, gas limit considerations, and token decimal handling.

## Common Requirements

All token pools, whether standard or custom, must adhere to the following guidelines:

### Function Requirements

When CCIP interacts with your token pools, it expects the presence of the following functions:

1. **Sending Tokens (Source)**:
   - This must include the following function: `lockOrBurn(Pool.LockOrBurnInV1 calldata lockOrBurnIn) external returns (Pool.LockOrBurnOutV1 memory)`.
   - This function locks or burns tokens depending on the implementation of the token pool.
   - See implementation details in [`TokenPool.lockOrBurn`](/ccip/api-reference/evm/v1.6.1/token-pool#lockorburn).

2. **Receiving Tokens (Destination)**:
   - This must include the following function: `releaseOrMint(Pool.ReleaseOrMintInV1 calldata releaseOrMintIn) external returns (Pool.ReleaseOrMintOutV1 memory)`.
   - This function releases or mints tokens depending on the implementation of the token pool.
   - See implementation details in [`TokenPool.releaseOrMint`](/ccip/api-reference/evm/v1.6.1/token-pool#releaseormint).

### Gas Requirements

On the destination blockchain, the CCIP OffRamp contract performs three key calls:

1. **`balanceOf` before minting/releasing tokens**: To check the token balance of the receiver before the minting or releasing operation.

2. **`releaseOrMint` to mint or release tokens**: To execute the minting or releasing of tokens on the destination blockchain.

   > \*\*NOTE\*\*
   >
   >
   >
   > The total execution of the `releaseOrMint` function includes both the *token pool's logic* and the *token's
   > execution logic*. For example, if minting tokens is part of the process, the gas consumed by the minting operation
   > is included in the total gas consumed by the `releaseOrMint` function.

3. **`balanceOf` after minting/releasing tokens**: To verify the token balance of the receiver after the operation is complete.

#### Default Gas Limits

- The combined execution of these three calls should not exceed the default gas limit of 90,000 gas.
- To verify this cost, it is recommended to perform a token transfer on testnet.

#### Handling Execution Failures

- If the default gas limit of **90,000 gas** is exceeded and a custom limit has not been configured, the CCIP execution on the destination blockchain will fail.
- In such cases, manual intervention by the user will be required to execute the transaction. For more information, see the [manual execution page](/ccip/concepts/manual-execution). - The resulting transaction can be inspected for the amount of gas that was used.

#### Requesting Gas Limit Adjustments

- If the combined execution requires consistently more than **90,000 gas** on the destination blockchain, you should [contact Chainlink Labs](https://chain.link/ccip-contact?v=Tokens:%20Gas%20limit%20update) to update an internal CCIP parameter to avoid execution failure.
- It is highly recommended to design your token pool to stay within the 90,000 gas limit whenever possible to ensure you enabled your tokens in CCIP without Chainlink Labs intervention.

### Token Decimal Handling

When you deploy the same token on multiple blockchains, you set decimals independently on each chain. Those settings determine how CCIP converts amounts during transfers. Use matching decimals on every chain when you can. If decimals differ between chains, read the examples and notes below and account for effects on token supply and on Token Pool Rate Limits; enforcement and scaling depend on your TokenPool contract version.

#### Precision Loss Examples

> **CAUTION: Impact on Token Supply**
>
> When you move tokens from a higher-decimal chain to a lower-decimal chain, amounts round to the destination's
> precision and a small remainder is lost on each transfer. With lock/release pools that remainder can remain on the
> source chain; with burn/mint pools it is typically not included in what is minted on the destination.

For example:

- On Ethereum: you configure 18 decimals (0.123456789123456789)
- On Polygon: you configure 9 decimals (0.123456789)

When tokens move between chains with different decimals, CCIP converts values automatically and rounds to match the destination chain’s precision.

**Impact Scenario**

Consider a token deployed across three blockchains:

- Blockchain A: 18 decimals
- Blockchain B: 9 decimals
- Blockchain C: 18 decimals

| Scenario         | Transfer Path | Example                                                           | Impact                                                                                                                                                                                                                                                        |
| ---------------- | ------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **High → Low ❌** | A → B         | • Send: 1.123456789123456789<br />• Receive: 1.123456789          | Precision is lost when converting to the destination chain’s decimals. The difference (0.000000000123456789) is not represented on the destination chain.<br />• Burn/mint: not included in the minted amount<br />• Lock/release: remains in the source pool |
| **Low → High ✅** | B → A         | • Send: 1.123456789<br />• Receive: 1.123456789000000000          | No precision loss                                                                                                                                                                                                                                             |
| **Equal ✅**      | A → C         | • Send: 1.123456789123456789<br />• Receive: 1.123456789123456789 | No precision loss                                                                                                                                                                                                                                             |

#### Token Pool Version Considerations

> **CAUTION: Token amounts and TPRLs vary by TokenPool version**
>
> Decimal differences affect both token amounts and how rate limits are interpreted depending on the version of the
> TokenPool contract you are using.

##### v1.6.0+ (Recommended)

TokenPool contracts in v1.6.0 and higher correctly handle mixed-decimal tokens and enforce Token Pool Rate Limits (TPRLs) across chains without additional configuration.

[Use the latest TokenPool (v1.6.1)](https://www.npmjs.com/package/@chainlink/contracts-ccip/v/1.6.1)

##### v1.5.1

Token pools in **v1.5.1** introduce support for tokens with different decimal places across blockchains. However, **INBOUND Token Pool Rate Limits (TPRLs) are not properly enforced when decimals differ between chains** (OUTBOUND TPRLs are properly enforced). If you use mixed-decimal tokens in this version, you must manually account for how decimals affect TPRL configuration—and even then, enforcement may be significantly higher or lower than intended. This includes both capacity and refill rate.

**Token Pool Rate Limits (TPRL)**

When configuring TPRLs for mixed-decimal tokens, you must base calculations on the **remote token decimals** (the source chain). Using local decimals when they differ will incorrectly scale the configured limits.

Definitions:

- Remote decimals: token decimals on the destination chain (where tokens are being transferred from)
- Local decimals: token decimals on the source chain (where tokens are being transferred to)

**Inbound capacity rule**

- If remote decimals ≠ local decimals\
  → `capacity = whole_tokens × 10^remote_decimals`

- If remote decimals = local decimals\
  → `capacity = whole_tokens × 10^local_decimals`

**Example (Solana → Ethereum)**

- Solana: 9 decimals
- Ethereum: 18 decimals
- Desired inbound capacity: 50,000 tokens

`50_000 × 10^9 = 50_000_000_000_000`

If you instead use local decimals (`10^18`), you will misconfigure the limit. This can cause rate limits to be higher or lower than intended.

> **CAUTION: v1.5.1 limitation**
>
> Even if you configure TPRLs using the correct model, enforcement may not match your configured values when token
> decimals differ between chains. Upgrade to the latest TokenPool (v1.6.0+) to avoid misconfiguration and drift.
> <a href="https://www.npmjs.com/package/@chainlink/contracts-ccip/v/1.6.1">View package</a>.

##### v1.5.0

Token pools on **v1.5.0** do **not** support tokens with different decimal places across blockchains. If you deploy tokens with different decimals across chains in this version, transfers will produce inconsistent token amounts.

| Transfer Path | Example                                          | Impact              |
| ------------- | ------------------------------------------------ | ------------------- |
| 12 → 6        | Send 1.0 (= 10¹² base units) → Receive 1,000,000 | Artificial increase |
| 6 → 12        | Send 1.0 (= 10⁶ base units) → Receive 0.000001   | Significant loss    |

Upgrade to **v1.6.0 or higher** for correct handling of mixed-decimal tokens.

#### Best Practices

- Use the same token decimals across chains unless you have a clear reason not to
- If you choose different decimals, account for rounding in both directions
- Test transfers between all chain pairs before production
- Monitor lock/release pools for accumulation over time
- Add UI warnings for transfers that may lose precision

## Standard Token Pools

Depending on your use case (token handling mechanism), you need to deploy the appropriate token pool type for each blockchain you want to support. Chainlink provides a set of token pool contracts that you can use to deploy your token pools in minutes. These token pools are fully audited and ready for deployment on your blockchains. You can find the token pool contracts in the [Chainlink GitHub repository](https://github.com/smartcontractkit/chainlink-ccip/tree/release/contracts-ccip-1.6.2/chains/evm/contracts/pools). For most use cases, you should use either:

- **[BurnMintTokenPool](https://github.com/smartcontractkit/chainlink-ccip/blob/release/contracts-ccip-1.6.2/chains/evm/contracts/pools/BurnMintTokenPool.sol)**: This token pool is used to burn or mint tokens. You can read the API reference here.
- **[BurnFromMintTokenPool](https://github.com/smartcontractkit/chainlink-ccip/blob/release/contracts-ccip-1.6.2/chains/evm/contracts/pools/BurnFromMintTokenPool.sol)**: This is a variant of the BurnMintTokenPool that uses the `burnFrom(from, amount)` function to burn tokens from a specified account. You can read the API reference here. **Note**: If your token supports the standard `burn` function, you should typically use the BurnMintTokenPool instead of BurnFromMintTokenPool.
- **[LockReleaseTokenPool](https://github.com/smartcontractkit/chainlink-ccip/blob/release/contracts-ccip-1.6.2/chains/evm/contracts/pools/LockReleaseTokenPool.sol)**: This token pool is used to lock or release tokens. You can read the API reference here.

**Note**: Both token pools inherit from the same base [TokenPool](https://github.com/smartcontractkit/chainlink-ccip/blob/release/contracts-ccip-1.6.2/chains/evm/contracts/pools/TokenPool.sol) contract, which provides all the common functions necessary for a token pool. For example, it includes the `applyChainUpdates` function, which is used to configure the token pool. You can read the API reference here.

## Custom Token Pools

### Guidelines for Custom Token Pools

If the standard token pools do not meet your requirements, you have the option to build a custom TokenPool. However, it is essential to adhere to the following guidelines:

- Your custom token pool must inherit from the appropriate base token pool contract depending on your token handling mechanism:
  - **Burn and Mint**: Your custom token pool should inherit from `BurnMintTokenPoolAbstract`. Use this base contract if your custom pool involves burning tokens on the source chain and minting them on the destination chain.
  - **Lock and Release**: Your custom token pool can either inherit from `TokenPool` and implement the `ILiquidityContainer` interface, or directly inherit from `LockReleaseTokenPool` and reimplement `lockOrBurn` and `releaseOrMint` functions as needed. This setup is appropriate when your pool involves locking tokens on the source blockchain and releasing them on the destination blockchain.
- Your custom TokenPool must implement the mandatory functions for both the source and destination blockchains. (Refer to the [Common Requirements](#common-requirements) section for more details.)

### Use Cases for Custom Token Pools

Here are some examples of use cases that may require custom token pools:

#### Tokens with rebasing or fee-on-transfer mechanisms

##### Use Case

Rebasing tokens are a unique type of token that adjusts its supply in response to specific parameters or events (e.g., price or transfer tax). These tokens require custom logic to handle rebasing events during cross-chain transfers.

##### Solution

- **Source Blockchain**: When initiating a cross-chain transfer of rebasing tokens, the TokenPool on the source blockchain should lock or burn the underlying shares rather than a fixed amount of tokens. This ensures that the value is consistently represented, regardless of changes in supply. The number of shares being locked or burned is recorded in the `destPoolData`and passed to the destination blockchain via CCIP.
- **Destination Blockchain**: On the destination blockchain, the TokenPool should accurately convert the number of underlying shares to tokens using the current share-to-token ratio. The calculated token amount should then be minted on the destination blockchain and returned in the `destinationAmount` field of the `ReleaseOrMintOutV1` struct, which is returned by the `releaseOrMint` function.

#### Tokens with different decimals across blockchains

> **NOTE: Note**
>
> This custom implementation is only needed for v1.5.0 pools. Starting from v1.6.0+, token decimal handling is natively
> supported by all token pools. See [Token Decimal Handling](#token-decimal-handling) for more information.

For **v1.5.0** pools:

##### Use Case

Some tokens have different decimal values across various blockchains.

##### Solution

- **Source Blockchain**: During the lock or burn process on the source blockchain, the TokenPool should include a shared denomination in the `destPoolData` field of the `LockOrBurnOutV1` struct (returned by the `lockOrBurn` function) to represent the value in a standard format. This data is then passed to the destination blockchain via CCIP.
- **Destination Blockchain**: On the destination blockchain, the TokenPool should use the information contained in the `sourcePoolData` of the `ReleaseOrMintInV1` struct (used by the `releaseOrMint` function) to convert the value into the local denomination. The correct number of tokens should then be minted based on the destination blockchain's decimal format. The minted amount is returned in the `destinationAmount` field of the `ReleaseOrMintOutV1` struct, which is returned by the `releaseOrMint` function.