Skip to content

Token pools#756

Open
krebernisak wants to merge 46 commits into
mainfrom
feat/token-pool
Open

Token pools#756
krebernisak wants to merge 46 commits into
mainfrom
feat/token-pool

Conversation

@krebernisak

Copy link
Copy Markdown
Collaborator

No description provided.

@krebernisak krebernisak requested a review from a team as a code owner June 9, 2026 10:47
Copilot AI review requested due to automatic review settings June 9, 2026 10:47
@krebernisak krebernisak marked this pull request as draft June 9, 2026 10:47
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

👋 krebernisak, thanks for creating this pull request!

To help reviewers, please consider creating future PRs as drafts first. This allows you to self-review and make any final changes before notifying the team.

Once you're ready, you can mark it as "Ready for review" to request feedback. Thanks!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds CCIP “token pool” support by introducing a shared TokenPool core (rate limiting, chain/ramp config, cursed subjects) plus two concrete pool contracts (Lock/Release and Burn/Mint), along with TypeScript wrappers and sandbox tests.

Changes:

  • Introduces new Tolk contracts for TokenPool, LockReleaseTokenPool, and BurnMintTokenPool, including messages/events/types/rate-limiter logic.
  • Adds CCIP CCT Jetton contracts (minter/wallet) and TS wrappers for deployment and interactions.
  • Adds TS wrapper utilities and new sandbox test suites for both pool variants.

Reviewed changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
contracts/wrappers/gen/ccip/pools/TokenPool.ts Auto-generated TypeScript wrapper for TokenPool contract types/messages.
contracts/wrappers/ccip/TokenPool.ts Handwritten TS codec/opcodes and helper senders for pool messages.
contracts/wrappers/ccip/LockReleaseTokenPool.ts TS wrapper for LockReleaseTokenPool deployment, sends, and get-methods.
contracts/wrappers/ccip/BurnMintTokenPool.ts TS wrapper for BurnMintTokenPool deployment, sends, and get-methods.
contracts/wrappers/ccip/CCTJettonMinter.ts TS wrapper for CCIP CCT Jetton minter (deploy/mint/admin/wallet addr).
contracts/wrappers/ccip/CCTJettonCode.ts Helper functions to load compiled CCT Jetton minter/wallet code.
contracts/wrappers/ccip.pools.LockReleaseTokenPool.compile.ts Blueprint compile config for LockReleaseTokenPool Tolk entrypoint.
contracts/wrappers/ccip.pools.BurnMintTokenPool.compile.ts Blueprint compile config for BurnMintTokenPool Tolk entrypoint.
contracts/wrappers/ccip.cct.JettonWallet.compile.ts Blueprint compile config for CCT JettonWallet Tolk entrypoint.
contracts/wrappers/ccip.cct.JettonMinter.compile.ts Blueprint compile config for CCT JettonMinter Tolk entrypoint.
contracts/tests/ccip/pools/TokenPool.behavior.ts Shared behavioral test suite for token-pool ramp/curse/chain behaviors.
contracts/tests/ccip/pools/LockReleaseTokenPool.spec.ts LockReleaseTokenPool integration tests using Jettons and behavior suite.
contracts/tests/ccip/pools/BurnMintTokenPool.spec.ts BurnMintTokenPool integration tests using CCT jettons and behavior suite.
contracts/contracts/ccip/pools/types.tolk TokenPool-related structs/constants (configs, fee config, v1 IO types).
contracts/contracts/ccip/pools/messages.tolk Opcode-tagged internal message types for TokenPool operations.
contracts/contracts/ccip/pools/events.tolk Shared TokenPool event structs/topics.
contracts/contracts/ccip/pools/errors.tolk TokenPool error codes/enum.
contracts/contracts/ccip/pools/rate_limiter.tolk Rate limiter config/bucket structs and refill/consume logic.
contracts/contracts/ccip/pools/token_pool.tolk Core TokenPool logic: chain config, ramps, cursed subjects, rate limiting, fees.
contracts/contracts/ccip/pools/token_pool_contract.tolk TokenPool contract template entrypoint for bindings (non-deployable stub).
contracts/contracts/ccip/pools/lock_release_token_pool/types.tolk LockReleaseTokenPool-specific constants and pending-release structs.
contracts/contracts/ccip/pools/lock_release_token_pool/storage.tolk LockReleaseTokenPool storage layout and initialization.
contracts/contracts/ccip/pools/lock_release_token_pool/messages.tolk LockReleaseTokenPool custom inbound/bounced message types.
contracts/contracts/ccip/pools/lock_release_token_pool/events.tolk LockReleaseTokenPool-specific event structs/topics.
contracts/contracts/ccip/pools/lock_release_token_pool/errors.tolk LockReleaseTokenPool-specific error enum/facility identifiers.
contracts/contracts/ccip/pools/lock_release_token_pool/contract.tolk LockReleaseTokenPool contract implementation (lock via transfer notify, release via off-ramp).
contracts/contracts/ccip/pools/burn_mint_token_pool/types.tolk BurnMintTokenPool-specific constants and pending burn/mint structs.
contracts/contracts/ccip/pools/burn_mint_token_pool/storage.tolk BurnMintTokenPool storage layout and initialization.
contracts/contracts/ccip/pools/burn_mint_token_pool/messages.tolk BurnMintTokenPool custom inbound message types (incl. claim-minter-admin).
contracts/contracts/ccip/pools/burn_mint_token_pool/errors.tolk BurnMintTokenPool-specific error enum/facility identifiers.
contracts/contracts/ccip/pools/burn_mint_token_pool/contract.tolk BurnMintTokenPool contract implementation (burn on lock, mint on release).
contracts/contracts/ccip/fee_quoter/types.tolk Removes duplicated constant now defined under pools types.
contracts/contracts/ccip/cct/JettonWallet.tolk CCT jetton wallet contract implementation (receive/transfer/burn + bounce handling).
contracts/contracts/ccip/cct/JettonMinter.tolk CCT jetton minter contract implementation (mint/admin lifecycle + wallet address).
contracts/contracts/ccip/cct/fees-management.tolk Fee/gas/storage estimation utilities for CCT jetton contracts.
contracts/Acton.toml Registers new pool contracts (and TokenPool template) in Acton config.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread contracts/wrappers/ccip/TokenPool.ts Outdated
Comment on lines +280 to +292
export async function sendUpdateCursedSubjects(
provider: ContractProvider,
via: Sender,
value: bigint,
cursedSubjects: bigint[],
) {
const dict = Dictionary.empty(Dictionary.Keys.BigInt(128), Dictionary.Values.Bool())
cursedSubjects.forEach((subject) => dict.set(subject, true))
await provider.internal(via, {
value,
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell().storeUint(opcodes.in.updateCursedSubjects, 32).storeDict(dict).endCell(),
})
Comment on lines +31 to +36

RateLimiter_refillBucket(mutate bucket);
// TODO: should be RateLimiter error
// assert(bucket.tokens >= amount, TokenPool_Error.RateLimitExceeded);
bucket.tokens -= amount;
}
Comment on lines +92 to +96
remoteChainSelector: request.remoteChainSelector,
details: TokenPool_LockedOrBurnedDetails {
token: request.localToken,
sender: transferInitiator,
amount: prepared.destTokenAmount,
Comment on lines +145 to +160
runTokenPoolBehaviorTests('LockReleaseTokenPool', async () => ({
pool: lockReleasePool,
deployer,
offRamp,
altOffRamp: deployer,
unauthorized: recipient,
recipient,
remoteChainSelector,
unsupportedChainSelector: remoteChainSelector + 1n,
unknownSourcePoolAddress: poolCodec.crossChainAddressFromBuffer(Buffer.from('unknown-source-pool')),
remoteTokenAddress: destTokenAddress,
onRampAddress: jettonSender.address,
destTokenAddress,
sourcePoolAddress,
localToken: jettonMinter.address,
}))
Comment on lines +162 to +177
runTokenPoolBehaviorTests('BurnMintTokenPool', async () => ({
pool: burnMintPool,
deployer,
offRamp,
altOffRamp: deployer,
unauthorized,
recipient,
remoteChainSelector,
unsupportedChainSelector: remoteChainSelector + 1n,
unknownSourcePoolAddress: poolCodec.crossChainAddressFromBuffer(Buffer.from('unknown-source-pool')),
remoteTokenAddress: destTokenAddress,
onRampAddress: deployer.address,
destTokenAddress,
sourcePoolAddress,
localToken: cctMinter.address,
}))
const GAS_CONSUMPTION_BurnNotification = 3855


fun calculateJettonWalletMinStorageFee() {

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.

You can use @stdlib/reflection here:

Suggested change
fun calculateJettonWalletMinStorageFee() {
val (minBits, maxBits, minRefs, maxRefs) =reflect.estimateSerializationOf<WalletStorage>();

See docs

Comment thread contracts/contracts/ccip/cct/JettonWallet.tolk Outdated
@nicolasgnr nicolasgnr marked this pull request as ready for review June 23, 2026 13:32
Comment on lines +814 to +820
// We either get this transfer from the Router, in which case we trust the transfer is valid;
// Or for a different sender (e.g., per-user deposit account), we need to validate the transfer,
// which we can only do with context -> recover from ContextExecutor.
//
// The context contains info about the sender, the amount, etc. that we can use to validate the transfer.
// If we can't validate the transfer, we try to return the funds.

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.

So we have these two options:

Transfer comes from Router Wallet, and it's address is configured in the Pool:

  • SendExecutor initializes LockOrBurn: SendExecutor -> OnRamp -> Router --[LockOrBurn]-> TokenPool
  • TokenPool initiates withdraw: TokenPool -> SendExecutor -> OnRamp -> Router
  • Router withdraws from user deposit account and sends it to the pool: Router -> UserAccount -> UserAccountWallet -> RouterWallet -> Router -> RouterWallet -> TokenPool Wallet -> TokenPool

Transfer comes from user account:

  • SendExecutor initializes LockOrBurn: SendExecutor -> OnRamp -> Router --[LockOrBurn]-> TokenPool
  • TokenPool initiates withdraw: TokenPool -> SendExecutor -> OnRamp -> Router
  • Router indicates the user account to transfer to the pool directly: Router -> UserAccount -> UserAccountWallet -> TokenPool
  • Token pool verifies the transfer comes from the user wallet

The second option is simpler when it comes to how many messages and handlers we need, but we need the context_executor or some similar way to store the account wallet address. How far away are we from having the executor deployments/access wired in here?

@krebernisak krebernisak Jun 25, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, that is basically it.

How far away are we from having the executor deployments/access wired in here?

The contract is roughly implemented, just need to start deploying/using it. Probably ready by end of next week, focusing on getting Lockbox + LockReleaseLockboxPool ready, then connect the executor.

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.

Sounds good 👍
Probably a good idea to leave that integration for a follow up PR


CCIP per-message sharded contracts which manage the context and state of the on-ramp and off-ramp flows.

## TODO

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.

We should move this documentation to the docs directory

Comment on lines +42 to +50
val handled = executor.onInternalMessage(ContextExecutor_InMessageForward {
senderAddress: in.senderAddress,
valueCoins: in.valueCoins,
valueExtra: in.valueExtra,
originalForwardFee: in.originalForwardFee,
createdLt: in.createdLt,
createdAt: in.createdAt,
body: in.body.toCell(),
});

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.

Why not just pass InMessage?

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.

I see, InMessage cannot be serialized:

contracts/ccip/executors/context_executor/contract.tolk:16:23: error: `outgoingMessages` can not be serialized: type `ContextExecutor_OutMessage<cell>`
           because alias `ContextExecutor_OutMessage<cell>` expands to `ContextExecutor_Reply<cell> | ContextExecutor_ForwardNotification<cell>`
           because variant #2 of type `ContextExecutor_ForwardNotification<cell>` can't be serialized
           because field `ContextExecutor_ForwardNotification<cell>.message` of type `Cell<InMessage>` can't be serialized
           because field `InMessage.body` of type `slice` can't be serialized
           because type `slice` can not be used for reading, it doesn't define binary width
           hint: replace `slice` with `address` if it's an address, actually
           hint: replace `slice` with `bits128` and similar if it represents fixed-width data without refs
  16 |     outgoingMessages: ContextExecutor_OutMessage<cell>
     |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants