In the create-tac-app quickstart project, the TAC Proxy MessageProxy.sol contract is created automatically.
You can also review it
Your First Proxy Contract
Let’s start with the absolute minimum proxy contract that can handle TON->TAC transaction:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28 ;
import { TacProxyV1 } from "@tonappchain/evm-ccl/contracts/proxies/TacProxyV1.sol" ;
import { TacHeaderV1 } from "@tonappchain/evm-ccl/contracts/core/Structs.sol" ;
contract HelloProxy is TacProxyV1 {
event HelloFromTON ( string indexed tonUser , string message );
constructor ( address crossChainLayer ) TacProxyV1 (crossChainLayer) {}
function sayHello ( bytes calldata tacHeader , bytes calldata arguments )
external
_onlyCrossChainLayer
{
// 1. Decode who's calling from TON
TacHeaderV1 memory header = _decodeTacHeader (tacHeader);
// 2. Decode their message
string memory message = abi . decode (arguments, ( string ));
// 3. Do something with it
emit HelloFromTON (header.tvmCaller, message);
}
}
That’s it! This contract can receive messages from any TON user.
Understanding the Structure
Required Imports
import { TacProxyV1 } from "@tonappchain/evm-ccl/contracts/proxies/TacProxyV1.sol" ;
import { TacHeaderV1 } from "@tonappchain/evm-ccl/contracts/core/Structs.sol" ;
TacProxyV1: The base contract that handles all cross-chain communication
TacHeaderV1: Data structure containing information about the TON user
Inheritance
contract HelloProxy is TacProxyV1 {
Your contract must inherit from TacProxyV1 to receive cross-chain calls.
Constructor Parameter
constructor ( address crossChainLayer ) TacProxyV1 (crossChainLayer) {}
The crossChainLayer address is TAC’s infrastructure contract that will call your functions. You get this address from TAC documentation or contract addresses page.
Function Signature (Critical!)
function sayHello ( bytes calldata tacHeader , bytes calldata arguments )
external
_onlyCrossChainLayer
{
Every cross-chain function must follow this exact pattern:
bytes calldata tacHeader - Encoded TacHeaderV1 containing TON user information
bytes calldata arguments - Your custom ABI-encoded parameters
external - Must be externally callable
_onlyCrossChainLayer - Security modifier that ensures only CrossChainLayer can call this function
(required only for TON->TAC and TON->TAC->TON types)
Step-by-Step Walkthrough
TacHeaderV1 memory header = _decodeTacHeader (tacHeader);
The header tells you:
header.tvmCaller - TON user’s address. Important! It is always base64 mainnet bounceable format and starts with “EQ” (like “EQAbc123…”)
header.operationId - Unique ID for this operation
header.timestamp - When the TON transaction happened
Step 2: Decode Your Parameters
string memory message = abi . decode (arguments, ( string ));
The arguments contain whatever data the TON user sent. You decide what format this should be - it could be:
A single string: abi.decode(arguments, (string))
Multiple values: abi.decode(arguments, (uint256, address, bool))
A struct: abi.decode(arguments, (MyCustomStruct))
Step 3: Execute Your Logic
emit HelloFromTON (header.tvmCaller, message);
This is where you do whatever your contract is supposed to do. In this example, we just emit an event, but you could:
Store data in state variables
Call other contracts
Perform calculations
Transfer tokens
Sending Responses Back to TON
You may also want to send arbitrary tokens back to the TON user after an EVM execution (TON->TAC->TON transaction type).
Learn how in the more advanced example .
Deployment
Deploy your basic proxy just like any other contract. You can also use deploy from @tonappchain/evm-ccl as helper:
import { Signer } from "ethers" ;
import { HelloProxy } from "../../typechain-types" ;
import { deploy } from '@tonappchain/evm-ccl' ;
import hre from 'hardhat' ;
export async function deployHelloProxy (
deployer : Signer ,
crossChainLayer : string ,
) : Promise < HelloProxy > {
const helloProxy = await deploy < HelloProxy >(
deployer ,
hre . artifacts . readArtifactSync ( 'HelloProxy' ),
[ crossChainLayer ],
undefined ,
true // verbose
);
await helloProxy . waitForDeployment ();
return helloProxy ;
}
async function main () {
const [ deployer ] = await hre . ethers . getSigners ();
const crossChainLayer = process . env . CROSS_CHAIN_LAYER_ADDRESS ;
const helloProxy = await deployHelloProxy ( deployer , crossChainLayer );
console . log ( "HelloProxy deployed to:" , helloProxy . target );
}
main (). catch ( console . error );
What’s Next?
Start simple : Build and test basic proxy contracts before moving to
upgradeable patterns. Most use cases don’t actually need upgradeability.
Feeling ready for more complex patterns?
Upgradeable Proxy Example Learn when and how to build contracts that can be upgraded over time