Skip to main content
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

Step 1: Decode the Header

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