This is the first version of generating automatic proxies. Please let us know if you have any feedback or suggestions.

Building a proxy is a crucial part of enabling cross-chain communication in the TAC ecosystem. Let’s walk through the process step by step.

Getting Started

First, install the core package in your Solidity project:

npm add --save tac-l2-ccl@latest

or if you use yarn:

yarn add tac-l2-ccl@latest

Creating Your Proxy Contract

Start by creating a new file for your proxy (e.g., MyProxy.sol) in your contracts folder. Here are the essential imports needed:

import { TacProxyV1 } from "tac-l2-ccl/contracts/proxies/TacProxyV1.sol";
import { ICrossChainLayer } from "tac-l2-ccl/contracts/interfaces/ICrossChainLayer.sol";
import { TokenAmount, OutMessage, TacHeaderV1 } from "tac-l2-ccl/contracts/L2/Structs.sol";

Here’s the basic structure of a proxy contract:

contract MyProxy is TacProxyV1 {
    IDappContract dappContract;
    ICrossChainLayer crossChainLayer;

    constructor(IDappContract _dappContract, ICrossChainLayer _crossChainLayer) {
        dappContract = _dappContract;
        crossChainLayer = _crossChainLayer;
    }

    modifier onlyTacCCL() {
        require(msg.sender == address(crossChainLayer), "Only TAC CCL");
        _;
    }
}

Building Proxy Functions

Please make sure that all proxy functions must follow this signature pattern:

function myProxyFunction(bytes calldata tacHeader, bytes calldata arguments) external onlyTacCCL {
    // Implementation goes here
}

Structuring Arguments

When using tac-console for development, it’s best to structure arguments like this:

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

Complete Function Implementation

Here’s a full example of a proxy function implementation:

function myProxyFunction(bytes calldata tacHeader, bytes calldata arguments) external onlyTacCCL {
    // Decode arguments structure
    MyProxyFunctionArguments memory args = abi.decode(arguments, (MyProxyFunctionArguments));

    // Handle token approvals and contract calls
    IERC20(args.tokenFrom).approve(address(dappContract), args.amount);
    uint256 tokenToAmount = dappContract.doSomething(args.tokenFrom, args.tokenTo, args.amount);

    // Set up token bridging back to TON
    TokenAmount[] memory tokensToBridge = new TokenAmount[](1);
    tokensToBridge[0] = TokenAmount(args.tokenTo, tokenToAmount);
    IERC20(tokensToBridge[0].l2Address).approve(address(crossChainLayer), tokensToBridge[0].amount);

    // Handle TAC header and create outgoing message
    TacHeaderV1 memory header = _decodeTacHeader(tacHeader);
    OutMessage memory outMsg = OutMessage({
        queryId: header.queryId,
        tvmTarget: header.tvmCaller,
        tvmPayload: "",
        toBridge: tokensToBridge
    });

    crossChainLayer.sendMessage(outMsg);
}

Before myProxyFunction is called, the CrossChainLayer contract transfers all user tokens from TON to your contract. The function receives two arguments:

TacHeader Structure

  • uint64 shardedId: Your specified ID for TVM contract messages
  • uint256 timestamp: TON block timestamp of message creation
  • string operationId: Unique TAC infrastructure message identifier
  • string tvmCaller: TON user’s wallet address
  • bytes extraData: Optional executor-provided data (empty if unused)

Basic Encoding Example

Here is a simple example of encoding arguments:

import { ethers } from "ethers";
const abiCoder = ethers.AbiCoder.defaultAbiCoder();

const myProxyFunctionArguments = abiCoder.encode(
  ["address", "address", "uint256"],
  [tokenFromAddress, tokenToAddress, tokenFromAmount]
);

Handling Complex Structures

For nested structures like this:

struct AnyExtraInfo {
    address feeCollector;
    uint256 feeRate;
}

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

The encoding looks like this:

const extraInfo = [feeCollectorAddress, feeRate];
const myProxyFunctionArguments = abiCoder.encode(
  ["tuple(address,uint256)", "address", "address", "uint256"],
  [extraInfo, tokenFromAddress, tokenToAddress, tokenFromAmount]
);

For dynamic arrays in your structures:

struct MyProxyFunctionArguments {
    address[] path;
    uint256 amount;
}

The encoding should look like this, as a tuple:

const path = [tokenFrom, tokenTo];
const myProxyFunctionArguments = abiCoder.encode(
  ["tuple(address[],uint256)"],
  [[path, tokenFromAmount]]
);

SDK Integration

When using the TAC SDK, first define the function signature:

const myProxyFunctionName = "myProxyFunction(bytes,bytes)";

then prepare your message like this:

{
    target: MyProxyContractAddress,
    method_name: "myProxyFunction(bytes,bytes)",
    arguments: myProxyFunctionArguments
}

That covers everything needed to create a working TAC proxy. Remember to thoroughly test before deploying to production!