TAC Proxy contracts are Solidity contracts that can receive function calls from TON users. This guide walks you through creating your first basic proxy contract, explaining each concept as we build.

Why Proxy Contracts?

Regular smart contracts can only be called by users on the same blockchain. TAC Proxy contracts solve a different problem: they let TON users call your EVM functions directly. Without proxies:
  1. TON user bridges tokens to TAC
  2. User switches to MetaMask
  3. User interacts with your dApp
  4. User bridges tokens back to TON
With proxies:
  1. TON user calls your function directly from TON wallet
  2. ✨ Everything else happens automatically
Think of proxy contracts as API endpoints that TON users can call with blockchain transactions instead of HTTP requests.

Your First Proxy Contract

Let’s start with the absolute minimum proxy contract:
// 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

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

Sometimes you want to send tokens back to the TON user. Use the OutMessageV1 structure:
import { TokenAmount, OutMessageV1 } from "@tonappchain/evm-ccl/contracts/core/Structs.sol";

// Send back to TON user (no fees for RoundTrip messages)
OutMessageV1 memory outMsg = OutMessageV1({
    shardsKey: header.shardsKey,        // Link to original operation
    tvmTarget: header.tvmCaller,        // Send back to caller
    tvmPayload: "",                     // Must be empty - not supported
    tvmProtocolFee: 0,                  // 0 for RoundTrip - already paid on TON
    tvmExecutorFee: 0,                  // 0 for RoundTrip - already paid on TON
    tvmValidExecutors: new string[](0), // Empty for RoundTrip - already defined on TON
    toBridge: tokensToSend,             // Tokens to send
    toBridgeNFT: new NFTAmount[](0)     // No NFTs
});

_sendMessageV1(outMsg, 0);

Deployment

Deploy your basic proxy like any other contract:
// scripts/deploy-basic-proxy.js
const { ethers } = require("hardhat");

async function main() {
  // Get CrossChainLayer address from TAC documentation or contract addresses page
  const crossChainLayerAddress = process.env.CROSS_CHAIN_LAYER_ADDRESS;

  const HelloProxy = await ethers.getContractFactory("HelloProxy");
  const proxy = await HelloProxy.deploy(crossChainLayerAddress);
  await proxy.deployed();

  console.log("HelloProxy deployed to:", proxy.address);
}

main().catch(console.error);

What’s Next?

You now understand the basics of TAC Proxy contracts. Ready for more complex patterns?
Start simple: Build and test basic proxy contracts before moving to upgradeable patterns. Most use cases don’t actually need upgradeability.