Onchain Architecture - Components (TON)
This section provides more detail on the onchain components for TON.
Sender/Receiver
CCIP supports the following as senders and receivers on TON:
- A user-controlled wallet — A wallet account controlled by a private key.
- A smart contract — Onchain executable logic written in Tolk or FunC.
For arbitrary message passing, the supported sender/receiver combinations are:
-
Wallet → Smart Contract
- Supported Message Type: Arbitrary data.
- Reason: The receiving contract implements a handler for
CCIPReceiveinternal messages and can process the data payload.
-
Smart Contract → Smart Contract
- Supported Message Type: Arbitrary data.
- Reason: Both the sender and receiver are programmable contracts that can initiate and handle CCIP messages with arbitrary data.
A CCIP Message on TON can include:
- An arbitrary bytes data payload.
Sender Responsibilities:
- Prepare the
CCIPSendmessage, including the encoded receiver address, data payload, destination chain selector, and extra arguments (e.g., gas limit for EVM destination chains). - Retrieve a fee estimate using one of two methods:
- Onchain: Send a
Router_GetValidatedFeemessage to theRouter. TheRouterrelays the request to theOnRamp, which forwards it to theFeeQuoter. The result is returned viaRouter_MessageValidated(orRouter_MessageValidationFailed). - Offchain: Call the
validatedFeegetter directly on theFeeQuotercontract.
- Onchain: Send a
- Send
CCIPSendto theRouterwith sufficient TON attached to cover both the CCIP fee and internal message forwarding gas costs. The router checks that the attached value meets the minimum required (Router_Costs.CCIPSend()).
Receiver Responsibilities:
- Security validation: The receiver contract must verify that the sender of any
CCIPReceiveinternal message is the registered CCIPRoutercontract. - Confirmation: The receiver must reply with a
CCIPReceiveConfirm{execId}message back to theRouterafter processing the message. This confirmation must carry sufficient TON to cover the confirmation trace costs (Router_Costs.receiveConfirm()). Without this confirmation the execution is treated as failed. - Failure handling: If the receiver cannot process the message, it may allow
CCIPReceiveto bounce. TheRouterdetects the bounce and forwardsCCIPReceiveBouncedto theOffRamp, initiating the failure path.
Router
The Router contract is the single user-facing entry point for both sending and receiving CCIP messages on TON.
On the source chain (sending), the Router:
- Accepts
CCIPSendmessages from senders. Requires a minimum TON value (Router_Costs.CCIPSend()); messages with insufficient value are rejected before any further processing. - Checks the destination chain is not cursed (using its locally stored
CursedSubjects) before forwarding to theOnRamp. - Resolves the
OnRampaddress for the given destination chain selector from itsonRampsmap and forwards theOnRamp_Sendmessage, carrying all remaining value. - Receives
Router_MessageSentfrom theOnRampon success and deliversCCIPSendACKback to the original sender with unused TON. - Receives
Router_MessageRejectedfrom theOnRampon failure and deliversCCIPSendNACKto the sender. - Accepts
Router_GetValidatedFeefrom any caller, relays to theOnRamp, and returns the result (Router_MessageValidatedorRouter_MessageValidationFailed).
On the destination chain (receiving), the Router:
- Accepts
Router_RouteMessagefrom the registeredOffRampfor the source chain and forwardsCCIPReceiveto the Receiver contract. - Accepts
Router_CCIPReceiveConfirmfrom any caller (permissionless at the Router level; authorization is enforced by theReceiveExecutor) and forwardsOffRamp_CCIPReceiveConfirmto theOffRamp. - Detects a bounced
CCIPReceivefrom the Receiver and forwardsOffRamp_CCIPReceiveBouncedto theOffRamp.
As the RMN Remote, the Router:
- Stores the set of cursed subjects (
CursedSubjects) in its own storage, acting as the RMN Remote for the TON chain family. There is no separate RMN Remote contract. - Accepts
Router_RMNRemoteCurseandRouter_RMNRemoteUncursemessages exclusively from the authorized RMN admin (Ownable2Step-gated). - On each curse or un-curse operation, emits a
CursedorUncursedchain log and updates theforwardUpdatesset, then propagatesOffRamp_UpdateCursedSubjectsto every registeredOffRampaddress (one message per unique OffRamp). TheforwardUpdatesset is rebuilt automatically whenever theonRamps/offRampsmaps change. - Accepts permissionless
Router_RMNRemoteVerifyNotCursedqueries and synchronously replies with whether the given subject is cursed.
OnRamp
The OnRamp contract processes all outbound CCIP messages on the source chain. It is not called directly by users; the Router is the only authorized caller of OnRamp_Send.
When the Router forwards OnRamp_Send, the OnRamp:
- Allowlist check: If
allowlistEnabledis set for the destination chain configuration, verifies the sender address is in theallowedSendersmap for that lane. Rejects the message if not. - SendExecutor deployment: Generates a random
executorID(uint224derived from a 256-bit random value), computes a deterministic address usingautoDeployAddress(executorID), and sendsDeployable_InitializeAndSendto deploy and initialize the ephemeralSendExecutor{id}contract in a single message. All remaining value is carried to theSendExecutor. - ExecutorFinishedSuccessfully handling: When the
SendExecutorreports success, theOnRampassigns the next sequence number for the destination chain, generates themessageId(as a hash over a metadata preimage, the sender address, sequence number, nonce, and message body), emits aCCIPMessageSentchain log containing the completeTVM2AnyRampMessage, and sendsRouter_MessageSentback to theRouter. The collected CCIP fee is retained in theOnRamp's balance. - ExecutorFinishedWithError handling: Forwards a
Router_MessageRejectednotification to theRouterwith the error code.
The OnRamp also handles fee withdrawal (OnRamp_WithdrawFeeTokens, permissionless), dynamic config updates (owner-only), destination chain config updates (owner-only), and allowlist updates (allowlist admin or owner).
SendExecutor{id}
The SendExecutor{id} is an ephemeral contract deployed by the OnRamp once per outgoing message. Its address is deterministically derived from the OnRamp's address and a randomly generated id, so the OnRamp can authenticate replies without storing any state.
The SendExecutor{id}:
- Is deployed and immediately receives
CCIPSendExecutor_Execute(a self-message sent as part ofDeployable_InitializeAndSend), which carries the fullOnRamp_Sendpayload and theFeeQuoteraddress. - Sends
FeeQuoter_GetValidatedFeeto theFeeQuoterand transitions to stateOnGoingFeeValidation. - On
FeeQuoter_MessageValidated: verifies the sender attached enough value to cover the CCIP fee, then reportsOnRamp_ExecutorFinishedSuccessfullyto theOnRampcarrying the fee amount and returning any remaining balance. - On
FeeQuoter_MessageValidationFailedor a bounce ofFeeQuoter_GetValidatedFee: reportsOnRamp_ExecutorFinishedWithErrorto theOnRamp. - Is destroyed after reporting (state
Finalizedis terminal; the contract returns its remaining balance to theOnRamp).
FeeQuoter
The FeeQuoter contract is responsible for fee validation and price data storage.
On the source chain, the FeeQuoter:
- Accepts
FeeQuoter_GetValidatedFeefrom theSendExecutor{id}(routed via theOnRamp). Validates the message payload (receiver encoding, gas limit, supported destination chain, fee token), computes the total fee from execution cost, premium multiplier, and data availability cost, and replies with eitherFeeQuoter_MessageValidated(including the fee amount) orFeeQuoter_MessageValidationFailed. - Stores fee token premium multipliers, destination-chain fee configuration, and token prices with staleness enforcement.
- Accepts
FeeQuoter_UpdatePricesfrom theOffRamp(forwarded from OCRCommitreports) to update token and gas prices onchain.
The FeeQuoter is not directly accessible to end users for fee computation. The intended path is Router_GetValidatedFee → OnRamp → FeeQuoter_GetValidatedFee, or direct getter calls offchain.
OffRamp
The OffRamp contract operates on the destination chain and is the primary contract that the offchain Committing and Executing DONs interact with.
Commit Phase
-
Report submission: The Committing DON calls
OffRamp_Commitwith an OCR3 report containing a Merkle root (covering a batch of messages from a source chain) and optional token/gas price updates. -
Fee check: The
OffRampverifies the attached TON covers the minimum commit cost. Price-update-only reports require less TON than reports with Merkle roots. -
Curse and source chain check: The
OffRampchecks its locally cachedCursedSubjectsand verifies the source chain is enabled insourceChainConfigs. If either check fails, the report is rejected. -
Sequence number validation: Verifies the root's
minSeqNrmatches the next expected sequence number for the source chain and that the range covers at most 64 messages. If valid, advancesminSeqNrtomaxSeqNr + 1. -
MerkleRoot deployment: Deploys a
MerkleRoot{id}contract by sendingDeployable_Initializeto its deterministic address. TheMerkleRoot{id}is initialized with the Merkle root value, theOffRamp's address as owner, the current timestamp, and the sequence number range. -
Price updates: If the report includes price updates and their OCR sequence number is newer than
latestPriceSequenceNumber, forwardsFeeQuoter_UpdatePricesto theFeeQuoter. -
OCR3 signature verification and event emission: Calls
ocr3Base.transmit()to verify the Committing DON's signatures and emitOCR3Base_Transmitted. Then emitsCommitReportAccepted.
Execution Phase
-
Report submission: The Executing DON calls
OffRamp_Executewith an OCR3 execute report. The OCR3 transmit call for the Execute plugin uses an empty signatures cell because signature verification is disabled for the Execute plugin by configuration. -
Curse check: The
OffRampchecksCursedSubjectsimmediately. If the source chain is cursed, the report is rejected before any further processing. -
Source chain and message validation: Verifies the source chain is enabled, computes the metadata hash from the source chain selector, destination chain selector, and the known
OnRampcross-chain address. Verifies the message'sdestChainSelectorandsourceChainSelectormatch the stored configuration. Reconstructs the Merkle root from the message and the provided proof, then resolves theMerkleRoot{id}contract address from this root. -
MerkleRoot validation: Sends
MerkleRoot_Validateto theMerkleRoot{id}contract. TheMerkleRoot{id}checks the per-message execution state (must beUntouchedfor DON execution, orFailurefor retries after the permissionless threshold), marks itInProgress, and replies withOffRamp_ExecuteValidated. If this was the last message in the root, theMerkleRoot{id}destroys itself and returns its balance. -
ExecutionStateChanged emission: Emits
ExecutionStateChangedwith stateInProgress. -
ReceiveExecutor deployment or reuse: Deploys (or, on retry, reuses) a
ReceiveExecutor{id}contract at a deterministic address derived from themessageIdandsourceChainSelector. SendsDeployable_Initializefollowed immediately byReceiveExecutor_InitExecute. -
Dispatch: The
ReceiveExecutor{id}replies withOffRamp_DispatchValidated. TheOffRampsendsRouter_RouteMessageto theRouter, which forwardsCCIPReceiveto the Receiver contract. -
Confirmation: If the Receiver confirms (
Router_CCIPReceiveConfirm→OffRamp_CCIPReceiveConfirm), theOffRampsendsReceiveExecutor_Confirmto theReceiveExecutor{id}. TheReceiveExecutor{id}verifies the confirm came from theOffRampand that the confirming address matches the intended Receiver, transitions toSuccess, destroys itself, and sendsOffRamp_NotifySuccess. TheOffRampcallsMerkleRoot_MarkState(Success)and emitsExecutionStateChangedwith stateSuccess. -
Failure: If the Receiver bounces
CCIPReceive, theRoutersendsOffRamp_CCIPReceiveBounced. TheOffRampsendsReceiveExecutor_Bouncedto theReceiveExecutor{id}, which transitions toExecuteFailedand sendsOffRamp_NotifyFailure. TheOffRampcallsMerkleRoot_MarkState(Failure)and emitsExecutionStateChangedwith stateFailure.
Permissionless Manual Execution (Fallback)
The OffRamp includes an OffRamp_ManuallyExecute handler that follows the same path as OffRamp_Execute but accepts a gasOverride field. Manual execution is permissionless and becomes available after permissionlessExecutionThresholdSeconds have elapsed since the MerkleRoot{id} was deployed. It can retry messages in Failure state or, after the threshold, messages still in Untouched state. For more information, read the manual execution page.
Curse Propagation
The OffRamp accepts OffRamp_UpdateCursedSubjects exclusively from the address stored as rmnRouter in its deployables storage (which is the Router address). When received, it overwrites the local CursedSubjects cache. This is the only mechanism by which the curse state is updated in the OffRamp.
MerkleRoot{id}
The MerkleRoot{id} is an ephemeral contract deployed once per committed Merkle root by the OffRamp. Its address is deterministic, derived from the OffRamp's address and the Merkle root value itself.
- Tracks the execution state of every message in the committed root using a packed two-bit bitmap keyed by sequence number offset. Supports up to 64 messages per root.
- Per-message states are:
Untouched(0),InProgress(1),Success(2),Failure(3). - Accepts
MerkleRoot_Validatefrom theOffRamp. Verifies the sender is the owningOffRamp, checks the message's current state (must beUntouchedfor normal DON execution;Failureor staleUntouchedfor manual retry), marks the message asInProgress, and repliesOffRamp_ExecuteValidated. - Accepts
MerkleRoot_MarkStatefrom theOffRampto transition a message toSuccessorFailure. Increments a delivered count onSuccess. When delivered count equals the total expected messages, theMerkleRoot{id}freezes and returns its balance to theOffRamp. - Is never directly redeployed; if a root's
MerkleRoot{id}was destroyed (all messages succeeded) and a retry attempt arrives, the address will not exist and the message will bounce.
ReceiveExecutor{id}
The ReceiveExecutor{id} is an ephemeral contract deployed (or reused on retry) by the OffRamp per incoming message. Its address is deterministic, derived from the OffRamp's address, the messageId, and the sourceChainSelector.
- Stores the message content, the owning
MerkleRoot{id}address (root), the execution ID, the current per-message delivery state, and the last execution timestamp. - Per-message delivery states are:
Untouched,Execute,ExecuteFailed,Success. - Accepts
ReceiveExecutor_InitExecutefrom theOffRamp. Records the current timestamp, transitions toExecute, and repliesOffRamp_DispatchValidatedcarrying all remaining value. - Accepts
ReceiveExecutor_Confirmfrom theOffRamp. Verifies theOffRampis the sender and that the provided receiver address matches the intended receiver. Transitions toSuccess, destroys itself, and repliesOffRamp_NotifySuccess. - Accepts
ReceiveExecutor_Bouncedfrom theOffRamp. Verifies theOffRampis the sender and that the bounced receiver matches the intended receiver. Transitions toExecuteFailedand repliesOffRamp_NotifyFailure. - On retry (
ExecuteFailed→Execute): the sameReceiveExecutor{id}contract at the same deterministic address is reused. TheOffRampsendsReceiveExecutor_InitExecuteagain, which updates the timestamp and re-triggers the dispatch.