Cross-Chain Token Standard - Token Pools (EVM)
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:
-
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
LockReleaseTokenPool.lockOrBurn
orBurnMintTokenPoolAbstract.lockOrBurn
.
- This must include the following function:
-
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
LockReleaseTokenPool.releaseOrMint
orBurnMintTokenPoolAbstract.releaseOrMint
.
- This must include the following function:
Gas Requirements
On the destination blockchain, the CCIP OffRamp contract performs three key calls:
-
balanceOf
before minting/releasing tokens: To check the token balance of the receiver before the minting or releasing operation. -
releaseOrMint
to mint or release tokens: To execute the minting or releasing of tokens on the destination blockchain. -
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. - 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 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
v1.5.0
Token pools on v1.5.0 do not support tokens with different decimal places across blockchains. When transferring a token between blockchains with differing decimal places, the token loses precision resulting in different amounts of tokens between the source and destination blockchain.
Consider a token developer who deployed their token across two blockchains with different decimal configurations:
- Blockchain A: Token with 12 decimals
- Blockchain B: Token with 6 decimals
Transfer Path | Example | Explanation | Impact |
---|---|---|---|
A → B (12 → 6) | • Send 1.0 from A (= 10^12 base units) • Receive on B: 1,000,000 | 10^12/10^6= 1,000,000 | Gain of 999,999 (1,000,000 -1) |
B → A (6 → 12) | • Send 1.0 from B (= 10^6 base units) • Receive on A: 0.000001 | 10^6/10^12= 0.000001 | Loss of 0.999999 (1-0.000001) |
We highly recommend upgrading to v1.5.1 to leverage the native support for token decimal handling. If using v1.5.0, please make sure to configure a custom v1.5.0 token pool.
V1.5.1 and Higher
Starting from v1.5.1, token pools support tokens with different decimal places across blockchains. This feature can impact the total number of tokens in circulation because tokens locked/burned on the source chain might result in a smaller number of tokens minted/released on the destination chain due to decimal rounding.
Understanding Token Decimals
When deploying their token, token developers can configure different decimal places for each blockchain. For example:
- On Ethereum: The developer sets 18 decimals (0.123456789123456789)
- On Polygon: The developer sets 9 decimals (0.123456789)
When transferring tokens between these blockchains, CCIP handles decimal conversion automatically but must round numbers to match the destination's configured precision.
Impact Scenario
Consider a token developer who deployed their token across three blockchains with different decimal configurations:
- Blockchain A: High precision (18 decimals)
- Blockchain B: Low precision (9 decimals)
- Blockchain C: High precision (18 decimals)
Scenario | Transfer Path | Example | Impact |
---|---|---|---|
High to Low Precision ❌ | A → B | • Send from A: 1.123456789123456789 • Receive on B: 1.123456789 | Lost: 0.000000000123456789 • Burn/mint: Tokens permanently burned on Blockchain A • Lock/release: Tokens locked in pool on Blockchain A |
Low to High Precision ✅ | B → A | • Send from B: 1.123456789 • Receive on A: 1.123456789000000000 | • No precision loss |
Equal Precision ✅ | A → C | • Send from A: 1.123456789123456789 • Receive on C: 1.123456789123456789 | • No precision loss |
Best Practices
- Deploy tokens with the same number of decimals across all blockchains whenever possible
- This prevents any loss of precision during cross-chain transfers
- Different decimals should only be used when required by blockchain limitations (e.g., non-EVM chains with decimal constraints)
- Verify decimal configurations on both source and destination blockchains before transfers
- Consider implementing UI warnings for transfers that might be affected by rounding
- When using high-to-low precision transfers, be aware that:
- In burn/mint pools: Lost precision results in permanently burned tokens
- In lock/release pools: Lost precision results in tokens accumulating in the source pool
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. For most use cases, you should use either:
- BurnMintTokenPool: This token pool is used to burn or mint tokens. You can read the API reference here.
- BurnFromMintTokenPool: 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 standardburn
function, you should typically use the BurnMintTokenPool instead of BurnFromMintTokenPool. - LockReleaseTokenPool: 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 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 theILiquidityContainer
interface, or directly inherit fromLockReleaseTokenPool
and reimplementlockOrBurn
andreleaseOrMint
functions as needed. This setup is appropriate when your pool involves locking tokens on the source blockchain and releasing them on the destination blockchain.
- Burn and Mint: Your custom token pool should inherit from
- Your custom TokenPool must implement the mandatory functions for both the source and destination blockchains. (Refer to the 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 theReleaseOrMintOutV1
struct, which is returned by thereleaseOrMint
function.
Tokens with different decimals across blockchains
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 theLockOrBurnOutV1
struct (returned by thelockOrBurn
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 theReleaseOrMintInV1
struct (used by thereleaseOrMint
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 thedestinationAmount
field of theReleaseOrMintOutV1
struct, which is returned by thereleaseOrMint
function.