Introduction
A TAC Proxy is a Solidity contract that receives cross-chain messages and tokens bridged from the TON blockchain. When a user on TON initiates a message (and potentially sends tokens), the TAC infrastructure delivers the tokens and data to your EVM-based contract’s function. The proxy contract then processes that data—often by calling another Dapp contract or by performing some bridging logic—and optionally sends tokens back to TON using the same TAC infrastructure. The guide below shows how to:- Install the necessary dependencies.
- Write a simple proxy contract (both non-upgradeable and upgradeable).
- Implement your custom logic in a proxy function that adheres to TAC’s required function signature.
- Encode your arguments properly on the frontend.
- Test your proxy contract locally using Hardhat and the @tonappchain/evm-ccl testing utilities.
Installation
Install the @tonappchain/evm-ccl package in your Solidity contract repository. This package includes core functionalities for cross-chain messaging and local test SDKs:Creating the Proxy Contract
In your contracts folder, create a new .sol file (e.g. MyProxy.sol). Below are two variations:Non-upgradeable Contract
For a simple, non-upgradeable contract, you can extend TacProxyV1:- We pass
_crossChainLayer
(the CrossChainLayer contract address) to TacProxyV1’s constructor. _onlyCrossChainLayer
is a security modifier inherited from TacProxyV1. It ensures that only the recognized cross-chain layer can call this function.IDappContract
is just an example interface for some external logic contract you may want to call.
Upgradeable Contract
If you need to upgrade your contract over time, use OpenZeppelin’s upgradeable libraries and the TacProxyV1Upgradeable contract:- Inherits from Initializable, OwnableUpgradeable, UUPSUpgradeable, and TacProxyV1Upgradeable.
- Has an initialize function in place of a constructor.
_authorizeUpgrade
ensures only the owner can perform contract upgrades.
Defining and Implementing Proxy Functions
Every proxy function that TAC calls must have the signature:myProxyFunction
, invokeWithCallback
, etc.), but it must accept two bytes arguments:
- The first is always the encoded TAC header.
- The second is always the encoded arguments that you define.
OutMessageV1
RoundTrip
Since TAC extends the TON ecosystem, the concept of a RoundTrip message was introduced at the smart contract level in the Cross-Chain Layer (CCL). The main interaction scenario with a DApp on TAC is as follows:- A user sends assets from TON to TAC, along with their intended action.
- After interacting with the DApp on TAC, it is possible — within the same transaction — to send the resulting assets back to the user on TON by calling the
_sendMessageV1
method. - The
_sendMessageV1
method can also be used for regular asset bridging from TAC back to TON, without necessarily involving DApp interactions.
- The message originates from TON, triggers an action on TAC, and then — after the interaction — the resulting assets are sent back to TON within the same flow.
OutMessageV1 Structure
OutMessageV1 is the main structure used for sending messages from EVM to TVM.- shardsKey: Developer ID. It is recommended to set it from the tacHeader.
- tvmTarget: The recipient address on the TON network.
- tvmPayload: A custom payload to be executed on the TON side. Currently not supported — must be empty.
- tvmProtocolFee: The protocol fee you must pay. For roundTrip messages, this fee is already covered on the TON side, so set this field to 0.
- tvmExecutorFee: The fee you offer to the executor on the TON side (in TAC tokens). For roundTrip messages, the fee is already locked on TON, so set this field to 0.
- tvmValidExecutors: A list of executors you trust to execute the message on the TON side. For roundTrip messages, this must be an empty array; the trusted executors are already defined in the initial TON message.
- toBridge: List of ERC20 tokens you want to bridge to the TON network and transfer to tvmTarget.
- toBridgeNFT: List of NFTs you want to bridge to the TON network and transfer to tvmTarget.
Example Implementation
Below is an extended example of how you might implementmyProxyFunction
in a non-upgradeable contract. The logic is the same for an upgradeable contract.
function myProxyFunction(bytes calldata, bytes calldata) external _onlyCrossChainLayer
ensures only the TAC infrastructure can call this function._decodeTacHeader(...)
is inherited from TacProxyV1 (or TacProxyV1Upgradeable); it transforms the raw bytes into TacHeaderV1 data.- You typically decode the second argument (
arguments
) usingabi.decode(...)
with a struct you define.
How the Cross-Chain Call Works
When a user on TON sends a message via your Dapp (e.g., using the tac-sdk on the frontend), the CrossChainLayer contract on EVM receives bridged tokens and data from the TON side. Then:- Tokens (if any) are automatically transferred from the CrossChainLayer contract to your proxy contract before the function call.
- The CrossChainLayer calls
myProxyFunction(tacHeader, arguments)
on your proxy contract. - tacHeader is the encoded TacHeaderV1 struct containing the following fields:
uint64 shardsKey
- ID for linking sharded messages.uint256 timestamp
- last message’s shard block timestamp from TON.bytes32 operationId
- unique ID for the operation.string tvmCaller
- TON user’s wallet address. !!! Important !!! It is always base64 mainnet bounceable format and starts with EQ.bytes extraData
- For now it’s always a zero-bytes array and not used.
- arguments is a bytes array containing data you defined in the Dapp’s frontend (encoded via tac-sdk or manually using ethers.AbiCoder).
- Your proxy function processes the tokens, calls external contracts if necessary, and optionally prepares an OutMessageV1 with tokens to be sent back to TON.
- The
_sendMessageV1(outMsg, value)
function sends everything back to the CrossChainLayer so that tokens (including native TAC token) (and an optional message) can be bridged to TON.
Fees
When sending a message, handling the fees depends on the type of the message.RoundTrip Messages
- No need to specify
tvmProtocolFee
ortvmExecutorFee
manually. - These values should be set to 0 because the fees have already been paid and locked on the TON side.
Direct TAC -> TON Messages
When sending a direct message from TAC to TON, you must manually specify the fees:-
Set tvmProtocolFee:
- Call the
getProtocolFee
method on the proxy contract to get the current protocol fee. - Set the
tvmProtocolFee
field in your OutMessageV1 accordingly.
- Call the
-
Set tvmExecutorFee:
- Estimate the appropriate executor fee you want to pay.
- If you provide an insufficient amount of TAC for the executor fee, your message will not be executed.
- Currently, there is no official library to calculate the exact executor fee, so it is recommended to:
- Overestimate slightly, or
- Use tacSDK for better fee estimation.
-
Specify Valid Executors:
- List the executors you trust to process your message.
- You can retrieve the list of default trusted executors by calling
settings.getTrustedTVMExecutors()
on the Settings contract.
msg.value
you send must be greater than or equal to tvmProtocolFee + tvmExecutorFee
. Any surplus TAC tokens will be bridged to tvmTarget
.
Encoding Arguments on the Frontend
You typically define a struct that represents the arguments your proxy function expects. For example:Basic Example
Complex Example with Nested Structures
Complex Example with Dynamic Arrays
Specifying the Function Name in tac-sdk
When using the tac-sdk to create messages for bridging, you must provide:- target: the address of your Proxy contract.
- method_name: the complete function signature, e.g.
"myProxyFunction(bytes,bytes)"
. - arguments: the ABI-encoded arguments (second parameter in your proxy function).
- gasLimit (optional): the parameter that will be passed to the TAC side. The executor must allocate at least
gasLimit
gas for executing the transaction on the TAC side. If this parameter is not specified, it will be calculated using thesimulateEVMMessage
method (preferred).
Testing the Proxy Contract
Example Minimal Proxy for Testing
In many cases, you want a stripped-down contract to test basic cross-chain behavior. Below is a minimal TestProxy that:- Inherits TacProxyV1.
- Has a single function
invokeWithCallback(...)
. - Emits an event for logging.
- Demonstrates bridging tokens back to TON.
Test Setup Example (Hardhat + @tonappchain/evm-ccl)
Create a test file such as TestProxy.spec.ts under your test directory. Below is a basic example test:-
Initialization
- Create a local cross-chain environment (
TacLocalTestSdk
). - Deploy a test token (
TestToken
). - Deploy your
TestProxy
.
- Create a local cross-chain environment (
-
Bridging Simulation
- Mint or lock tokens on the cross-chain layer.
- Create the parameters (
shardsKey
,operationId
, etc.).
-
Invoke Proxy
- Use the
testSdk.sendMessage(...)
to simulate a cross-chain call to your proxy’s function.
- Use the
-
Verification
- Confirm the transaction succeeded.
- Inspect the
deployedTokens
(if you minted new tokens). - Inspect the
outMessages
for tokens returning to TON.- Check emitted events for correct data.
NFT Proxy Contract Example
The NFT proxy contract must inherit fromIERC721Receiver
and implement the required onERC721Received
function to correctly receive ERC‑721 tokens.
NFT Proxy Implementation
Test ERC‑721 Token Contract
Test Setup Example for NFT Proxy
Below is an example Hardhat test for NFT bridging using the local test SDK:Running the Tests
Inside your project directory, simply run:@tonappchain/evm-ccl
local test SDK helps emulate bridging logic, ensuring your proxy behaves as expected in a cross-chain scenario.
Conclusion
By following these steps, you can develop, deploy, and test a TAC Proxy contract that handles cross-chain messages and tokens from TON. Key points include:- Inheritance from TacProxyV1 or TacProxyV1Upgradeable to gain built-in cross-chain functionality and security modifiers.
- Function Signatures must match
functionName(bytes, bytes) external
. - Decoding the TAC header and your custom arguments to implement your Dapp’s logic.
- Use
_sendMessageV1(...)
to return tokens and a message back to TON. - Using
_sendMessageV2(...)
to bridge both NFTs and ERC‑20 tokens back to TON. - Local Testing with
@tonappchain/evm-ccl
provides an easy way to validate your cross-chain logic without deploying a full cross-chain setup.