Proxy functions are the heart of your TAC contracts - they receive cross-chain calls from TON users and execute your application logic. This guide covers how to implement robust proxy functions that handle different use cases and error conditions.

Function Signature Requirements

Every proxy function that handles cross-chain calls must follow this exact pattern:
function <function_name>(bytes calldata, bytes calldata) external;
You can name the function as you wish (e.g. myProxyFunction, invokeWithCallback, swap, etc.), but it must accept two bytes arguments:
function yourFunctionName(bytes calldata tacHeader, bytes calldata arguments)
    external
    _onlyCrossChainLayer
{
    // Your implementation
}
Required Components:
  • bytes calldata tacHeader - Contains verified TON user information
  • bytes calldata arguments - Your custom encoded parameters
  • external visibility - Functions must be externally callable
  • _onlyCrossChainLayer modifier - Security requirement
Important: Only the Cross-Chain Layer (CCL) contract can call these functions. When a user on TON sends a message, the CCL automatically transfers any bridged tokens to your proxy contract before calling your function.

Parameter Encoding and Decoding

Simple Parameters

For basic data types, use straightforward encoding:
struct SimpleParams {
    address token;
    uint256 amount;
    address recipient;
}

function handleSimpleParams(bytes calldata tacHeader, bytes calldata arguments)
    external
    _onlyCrossChainLayer
{
    // Decode parameters
    SimpleParams memory params = abi.decode(arguments, (SimpleParams));

    // Use the parameters
    IERC20(params.token).transfer(params.recipient, params.amount);
}

Complex Parameters

For complex data with arrays or nested structures:
struct ComplexParams {
    address[] tokens;
    uint256[] amounts;
    bytes swapData;
    uint256 deadline;
}

function handleComplexParams(bytes calldata tacHeader, bytes calldata arguments)
    external
    _onlyCrossChainLayer
{
    ComplexParams memory params = abi.decode(arguments, (ComplexParams));

    // Validate array lengths match
    require(params.tokens.length == params.amounts.length, "Array length mismatch");

    // Process each token
    for (uint i = 0; i < params.tokens.length; i++) {
        // Your custom logic here
        IERC20(params.tokens[i]).transfer(msg.sender, params.amounts[i]);
    }

    // Use additional data
    if (params.swapData.length > 0) {
        // Your custom swap logic here
    }
}

Complete Implementation Walkthrough

Let’s build a complete proxy function step-by-step, showing external Dapp integration - the most common real-world pattern:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { TacProxyV1 } from "@tonappchain/evm-ccl/contracts/proxies/TacProxyV1.sol";
import { TokenAmount, OutMessageV1, TacHeaderV1, NFTAmount } from "@tonappchain/evm-ccl/contracts/core/Structs.sol";

interface IDappContract {
    function doSomething(address tokenFrom, address tokenTo, uint256 amount)
        external returns (uint256);
}

contract MyProxy is TacProxyV1 {
    IDappContract public dappContract;

    struct MyProxyFunctionArguments {
        address tokenFrom;
        address tokenTo;
        uint256 amount;
    }

    constructor(address _dappContract, address _crossChainLayer)
        TacProxyV1(_crossChainLayer)
    {
        dappContract = IDappContract(_dappContract);
    }

    function myProxyFunction(bytes calldata tacHeader, bytes calldata arguments)
        external
        _onlyCrossChainLayer
    {
        // 1. Decode the custom arguments
        MyProxyFunctionArguments memory args = abi.decode(arguments, (MyProxyFunctionArguments));

        // 2. Approve tokens to your Dapp contract for some action
        IERC20(args.tokenFrom).approve(address(dappContract), args.amount);

        // 3. Call the Dapp contract
        uint256 tokenToAmount = dappContract.doSomething(
            args.tokenFrom,
            args.tokenTo,
            args.amount
        );

        // 4. Prepare tokens to send back to TON
        TokenAmount[] memory tokensToBridge = new TokenAmount[](1);
        tokensToBridge[0] = TokenAmount(args.tokenTo, tokenToAmount);

        // 5. Approve the CrossChainLayer to pull them
        IERC20(tokensToBridge[0].evmAddress).approve(
            _getCrossChainLayerAddress(),
            tokensToBridge[0].amount
        );

        // 6. Decode the TAC header
        TacHeaderV1 memory header = _decodeTacHeader(tacHeader);

        // 7. Form an OutMessage
        OutMessageV1 memory outMsg = OutMessageV1({
            shardsKey: header.shardsKey,           // Use same key for RoundTrip
            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: tokensToBridge,              // Result tokens
            toBridgeNFT: new NFTAmount[](0)        // No NFTs
        });

        // 8. Send message back through CrossChainLayer with zero native
        _sendMessageV1(outMsg, 0);
    }
}
This example shows the complete flow:
  • External Dapp contract integration (lines 11-13, 19-21, 32-38)
  • Token approval for external contracts (line 33)
  • Processing and getting results (lines 35-38)
  • RoundTrip response pattern (lines 48-58)
  • Proper fee handling for RoundTrip messages (lines 52-53)
Important Notes:
  • Use _sendMessageV1 for ERC20 token bridging only
  • Use _sendMessageV2 if you need to bridge both NFTs and ERC20 tokens back to TON
  • The CCL automatically handles token transfers to your proxy before calling your function
  • Always include the NFTAmount import when using OutMessageV1

What’s Next?

Now that you understand how to implement proxy functions, learn about managing fees: