Skip to content

TokenTransfer onramp flow skeleton#754

Open
vicentevieytes wants to merge 13 commits into
mainfrom
vv/tt/onramp-flow-skeleton
Open

TokenTransfer onramp flow skeleton#754
vicentevieytes wants to merge 13 commits into
mainfrom
vv/tt/onramp-flow-skeleton

Conversation

@vicentevieytes

Copy link
Copy Markdown
Collaborator

No description provided.

@vicentevieytes vicentevieytes changed the title add tokenRegistry to storages TokenTransfer onramp flow skeleton Jun 8, 2026
@vicentevieytes vicentevieytes marked this pull request as ready for review June 9, 2026 02:29
@vicentevieytes vicentevieytes requested a review from a team as a code owner June 9, 2026 02:29
Copilot AI review requested due to automatic review settings June 9, 2026 02:29

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

This PR introduces the initial “token transfer onramp” flow for CCIP-on-TON: token transfers are initiated via Jetton transfer_notification, routed through Router -> OnRamp -> CCIPSendExecutor, and resolved via a new TokenRegistry that returns the token’s configured pool for lock/burn.

Changes:

  • Add token-transfer message plumbing (Common_JettonTransferNotification, Router_LockOrBurn, OnRamp_ExecutorRequestsLockOrBurn) and update CCIPSendExecutor state machine to include token-registry lookup + lock/burn confirmation.
  • Add TokenRegistry contract + wrapper, plus a MockTokenPool test contract/wrapper to simulate lock/burn.
  • Update FeeQuoter to accept token transfers (currently priced like token-less messages) and add/adjust tests including a new end-to-end token-transfer test.

Reviewed changes

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

Show a summary per file
File Description
contracts/wrappers/gen/ccip/Router.ts Regenerated wrapper: adds structs/messages for jetton transfer notification and lock/burn flow + new error IDs.
contracts/wrappers/gen/ccip/OnRamp.ts Regenerated wrapper: adds Router_LockOrBurn, OnRamp_ExecutorRequestsLockOrBurn, storage field tokenRegistry, and tokenAmounts snake typing.
contracts/wrappers/gen/ccip/FeeQuoter.ts Regenerated wrapper: updated CodeCell; error mapping adjusted after token-transfer acceptance.
contracts/wrappers/gen/ccip/CCIPSendExecutor.ts Regenerated wrapper: adds TokenRegistry and MockTokenPool message types, new executor states, and new errors.
contracts/wrappers/ccip/TokenRegistry.ts New TS wrapper for TokenRegistry deployment/config.
contracts/wrappers/ccip/Router.ts Adds lockOrBurn opcode constant for Router wrapper.
contracts/wrappers/ccip/OnRamp.ts Adds optional tokenRegistry to storage encoding/decoding and new opcode constant.
contracts/wrappers/ccip/MockTokenPool.ts New TS wrapper for the mock token pool used in tests.
contracts/wrappers/ccip/CCIPSendExecutor.ts Updates Addresses/Config to carry tokenRegistry and adds inbound opcodes for new messages.
contracts/wrappers/ccip.TokenRegistry.compile.ts New blueprint compile config for TokenRegistry.
contracts/wrappers/ccip.test.mockTokenPool.compile.ts New blueprint compile config for mock token pool.
contracts/tests/Logs.ts Decodes tokenAmounts snake cell into arrays for log matching.
contracts/tests/ccip/router/Router.Setup.ts Allows injecting TokenRegistry address into OnRamp deployment during test setup.
contracts/tests/ccip/feequoter/FeeQuoter.getValidatedFee.spec.ts Updates test expectations: token transfers now accepted and priced like token-less messages.
contracts/tests/ccip/e2e/CCIPSendWithTokenTransfer.spec.ts New E2E test for jetton-initiated CCIP send with token transfer path.
contracts/contracts/ccip/token_registry/types.tolk New TokenRegistry types.
contracts/contracts/ccip/token_registry/storage.tolk New TokenRegistry storage definition/load/store.
contracts/contracts/ccip/token_registry/messages.tolk New TokenRegistry request/response message schema.
contracts/contracts/ccip/token_registry/contract.tolk New TokenRegistry contract implementation.
contracts/contracts/ccip/test/tokenPool/messages.tolk New mock token pool message schema.
contracts/contracts/ccip/test/tokenPool/contract.tolk New mock token pool contract implementation.
contracts/contracts/ccip/router/messages.tolk Router now accepts Router_LockOrBurn and Common_JettonTransferNotification.
contracts/contracts/ccip/router/errors.tolk Adds TokenTransferNotThroughNotification router error.
contracts/contracts/ccip/router/contract.tolk Implements token-transfer notification path and lock/burn forwarding.
contracts/contracts/ccip/onramp/types.tolk Changes TVM ramp message body tokenAmounts to SnakedCell<TokenAmount>.
contracts/contracts/ccip/onramp/storage.tolk Adds tokenRegistry: address? to OnRamp storage.
contracts/contracts/ccip/onramp/messages.tolk Adds OnRamp_ExecutorRequestsLockOrBurn message.
contracts/contracts/ccip/onramp/contract.tolk Computes tokenRegistry for token sends and forwards executor lock/burn requests to Router.
contracts/contracts/ccip/fee_quoter/contract.tolk Removes “no tokens supported” asserts; token transfers now allowed (extra fee ignored for now).
contracts/contracts/ccip/ccipsend_executor/types.tolk Adds tokenRegistry address, new executor states for token flow.
contracts/contracts/ccip/ccipsend_executor/messages.tolk Adds inbound messages for TokenRegistry + MockTokenPool callbacks.
contracts/contracts/ccip/ccipsend_executor/errors.tolk Adds TokenNotEnabled executor error.
contracts/contracts/ccip/ccipsend_executor/contract.tolk Implements token registry query + lock/burn request/confirmation path.

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

Comment thread contracts/contracts/ccip/router/contract.tolk Outdated
Comment thread contracts/contracts/ccip/router/contract.tolk
Comment thread contracts/contracts/ccip/router/contract.tolk Outdated
Comment thread contracts/contracts/ccip/onramp/contract.tolk
Comment thread contracts/wrappers/ccip/CCIPSendExecutor.ts
}
// Sender must be a Jetton Wallet owned by the Router
Common_JettonTransferNotification => {
//TODO: Validate sender is owned jetton transfer

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.

This would be done by the executor/registry, right?

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.

Yeah, I don't see a way to do it directly in the Router

@patricios-space patricios-space left a comment

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 like this structure! I've left some general comments. I've only reviewed the contracts for now.

About the validation of the wallet, we should be forwarding that address from the Router through the OnRamp and to the SendExecutor so it can do the validation. We should name this fields untrustedWalletAddress or something similar, to remember that they must be verified

Comment thread contracts/contracts/ccip/router/contract.tolk Outdated
Comment thread contracts/contracts/ccip/router/contract.tolk Outdated
}

// Token transfer messages can only come in through jetton transfer notifications
assert(msg.tokenAmounts.empty()) throw Router_Error.TokenTransferNotThroughNotification; //TBD shuold this be sendMessageRejected instead of throw?

@patricios-space patricios-space Jun 10, 2026

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.

Yes, we should be returning sendMessageRejected. There are other asserts that could be changed for returning rejected message
Edit: We should be returning sendMessageRejected through a refund

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, I think this is a task on it's own. I'll create a ticket

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.

NONEVM-5291

// Fees are paid in native TON, not in the transferred token. metadata.value must be the
// native TON attached to the transfer notification (used downstream to cover fee +
// execution costs), mirroring the plain-message path which uses in.valueCoins.
val onRampSend = createMessage({

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.

Have you consider merging this handler this in onCCCIPSend function?

This lacks checks for min-value in entrypoint and cursed lanes, for example. I might be missing something else. By reducing duplicated code, we can be sure that all checks that must be done for both entry points are verified.

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.

done, take a look please, i left the min value validation outside the function because that's how it's done for all hanlders

addresses: CCIPSendExecutor_Addresses {
onramp: st.onramp,
feeQuoter: config.feeQuoter,
tokenRegistry: config.tokenRegistry

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.

SendExecutor doesn't load this config lazily, so I think this is going to be a breaking change. I think this should be good any way because old SendExecutors are not going to receive new messages from the OnRamp. It's only the new OnRamp that will receive messages from the old SendExecutors

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.

I guess the update order would be:

  1. update sendExecutorCode on the onRamp
  2. upgrade the onramp

Issue there is if the sendExecutor code is updated, and before the onRamp code is updated the onRamp tries to deploy a sendExecutor with the outdated config (that does not have the tokenRegistry)

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.

Maybe we can separate the initialization in two steps...

The new SendExecutor receives CCIPSendExecutor_Execute first, and we don't change that message type.

But, on the new handler if a TokenAmount is present then the executor blocks and waits for another message CCIPSendExecutor_InitTokenTransfer which passes the token registry address.

The Onramp sends only Execute when there are no token amounts , and Execute followed by InitTokenTransfer when there are token amounts

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.

The other option is making the executor able to handle both the old and the new config, we keep CCIPSendExecutor_Execute and add CCIPSendExecutor_ExecuteV2. The new SendExecutor is able to handle both but does not do any tokenTransfer related execution with the first one

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.

I think I lean towards the second option


match (msg) {
TokenRegistry_GetTokenInfo => {
onGetTokenInfo(msg, in.senderAddress);

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.

Missing assertion on incomming message value

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.

I'll add a TODO, this implementation of the TokenRegistry is incomplete and its just to get us an e2e test running. We don't know how much value is required here. In fact we should re-benchmark all of the contracts with the recent changes to the TON fees.


fun CCIPSendExecutor<CCIPSendExecutor_State_TokenRegistryAccess>.onTokenInfoReceived(mutate self, msg: TokenRegistry_ReturnTokenInfo) {
//TODO validate sender wallet address by querying the jettonMinter
assert(msg.tokenInfo.enabled) throw CCIPSendExecutor_Error.TokenNotEnabled;

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 can use the type system to represent this. Maybe the registry should return a different message if the token is not enabled OR it returns an optional TokenInfo. In this way, we don't even have the tokenPool address here, preventing us from trying to lock anyway

@vicentevieytes vicentevieytes Jun 12, 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.

I think I prefer having just one request and response instead of two different response types, but I can do the two messages if you think it's important to leave that tokenPool field out when it won't be used.

Maybe we should also handle bounced messages from Executor->Registry which would indicate that the token has not been enabled either.

I think handling this could be part of the scope of the task to handle the failure path on the CCIPSend TokenTransfer flow. I'll add a TODO

Comment thread contracts/contracts/ccip/ccipsend_executor/contract.tolk Outdated
Comment thread contracts/contracts/ccip/ccipsend_executor/contract.tolk Outdated
Comment thread contracts/contracts/ccip/onramp/contract.tolk Outdated
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.

3 participants