The TAC SDK’s core functionality revolves around sendCrossChainTransaction(), which enables seamless execution of EVM contract calls directly from TON wallets. This guide covers everything from basic transactions to advanced batch operations and error handling.

Always test transactions on testnet first. Cross-chain transactions are irreversible once confirmed, and incorrect parameters can result in loss of funds.

Basic Cross-Chain Transaction

The fundamental pattern for sending cross-chain transactions involves three components: an EVM target message, a sender, and optional assets to bridge.

import { TacSdk, SenderFactory, Network, AssetType } from "@tonappchain/sdk";

// Initialize SDK and sender
const tacSdk = await TacSdk.create({ network: Network.TESTNET });
const sender = await SenderFactory.getSender({ tonConnect: tonConnectUI });

// Define EVM operation
const evmProxyMsg = {
  evmTargetAddress: "0x742d35Cc647332...", // Target contract address
  methodName: "transfer(address,uint256)", // Contract method signature
  encodedParameters: "0x000000000000000000...", // ABI-encoded parameters
};

// Define assets to bridge (optional)
const assets = [
  {
    type: AssetType.FT,
    amount: 1.0,
  }, // Bridge 1 TON
];

// Execute cross-chain transaction
const transactionLinker = await tacSdk.sendCrossChainTransaction(
  evmProxyMsg,
  sender,
  assets
);

console.log("Transaction ID:", transactionLinker.operationId);

EVM Proxy Message Structure

The EvmProxyMsg object defines what contract call to execute on the TAC EVM side:

Required Fields

const evmProxyMsg = {
  evmTargetAddress: "0x742d35Cc647332...", // Target contract address (required)
  methodName: "methodName(uint256,address)", // Method signature (optional)
  encodedParameters: "0x00000000000000...", // ABI-encoded params (optional)
  gasLimit: 500000n, // Gas limit (optional)
};

Method Name Formatting

The SDK accepts flexible method name formats:

const evmProxyMsg = {
  evmTargetAddress: "0x742d35Cc647332...",
  methodName: "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)",
  encodedParameters: encodedSwapParams
};

Parameter Encoding

Use ethers.js or similar libraries to encode contract parameters:

import { ethers } from "ethers";

// For ERC20 transfer
const abi = new ethers.AbiCoder();
const transferParams = abi.encode(
  ["address", "uint256"],
  ["0xRecipientAddress...", ethers.parseEther("100")]
);

// For Uniswap V2 swap
const swapParams = abi.encode(
  ["uint256", "uint256", "address[]", "address", "uint256"],
  [
    ethers.parseEther("1"), // amountIn
    ethers.parseEther("0.95"), // amountOutMin
    [tokenA, tokenB], // path
    recipientAddress, // to
    Math.floor(Date.now() / 1000) + 1200, // deadline
  ]
);

const evmProxyMsg = {
  evmTargetAddress: uniswapRouterAddress,
  methodName:
    "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)",
  encodedParameters: swapParams,
};

Asset Bridging

Assets are automatically bridged from TON to TAC EVM as part of the cross-chain transaction. The SDK supports multiple asset types and formats.

Asset Types

const assets = [
  { 
    type: AssetType.FT,
    amount: 1.5 
  } // Bridge 1.5 TON
];

Raw Asset Format

For precise control, use raw asset format:

const rawAssets = [
  {
    type: AssetType.FT, // Fungible Token
    address: "EQJettonMasterAddress...",
    rawAmount: 1000000000n, // Raw amount (with decimals)
  },
  {
    type: AssetType.NFT,
    address: "EQNFTItemAddress...", // Direct NFT item address
  },
];

Transaction Options

Customize transaction behavior with advanced options:

const options = {
  forceSend: false, // Force send even if simulation fails
  isRoundTrip: false, // Whether to return assets to TON
  protocolFee: 10000000n, // Custom protocol fee (nanotons)
  evmValidExecutors: [
    // Trusted EVM executors
    "0xExecutor1Address...",
    "0xExecutor2Address...",
  ],
  evmExecutorFee: 50000000n, // EVM executor fee (wei)
  tvmValidExecutors: [
    // Trusted TON executors
    "EQExecutor1Address...",
    "EQExecutor2Address...",
  ],
  tvmExecutorFee: 100000000n, // TON executor fee (nanotons)
};

const transactionLinker = await tacSdk.sendCrossChainTransaction(
  evmProxyMsg,
  sender,
  assets,
  options
);

Custom executor fees and validator lists are advanced features. Use default values unless you have specific requirements for execution control.

Batch Transactions

Send multiple cross-chain transactions simultaneously:

const batchTransactions = async () => {
  // Define multiple transactions
  const transactions = [
    {
      evmProxyMsg: {
        evmTargetAddress: "0xContract1...",
        methodName: "method1(uint256)",
        encodedParameters: "0x...",
      },
      assets: [{ amount: 1.0 }],
    },
    {
      evmProxyMsg: {
        evmTargetAddress: "0xContract2...",
        methodName: "method2(address)",
        encodedParameters: "0x...",
      },
      assets: [{ address: "EQJetton...", amount: 100 }],
    },
  ];

  // Convert to batch format
  const crosschainTxs = transactions.map((tx) => ({
    evmProxyMsg: tx.evmProxyMsg,
    assets: tx.assets || [],
  }));

  // Send batch
  const transactionLinkers = await tacSdk.sendCrossChainTransactions(
    sender,
    crosschainTxs
  );

  return transactionLinkers.map((linker) => linker.operationId);
};

Transaction Simulation

Simulate transactions before execution to estimate fees and validate parameters:

const simulateTransaction = async () => {
  try {
    const simulation = await tacSdk.getTransactionSimulationInfo(
      evmProxyMsg,
      sender,
      assets
    );

    console.log("Simulation Results:");
    console.log("Success:", simulation.success);
    console.log("Estimated Gas:", simulation.estimatedGas);
    console.log("Protocol Fee:", simulation.protocolFee);
    console.log("EVM Executor Fee:", simulation.evmExecutorFee);
    console.log("TON Executor Fee:", simulation.tvmExecutorFee);

    if (simulation.success) {
      // Proceed with actual transaction
      const result = await tacSdk.sendCrossChainTransaction(
        evmProxyMsg,
        sender,
        assets
      );
      return result;
    } else {
      console.error("Simulation failed:", simulation.error);
      return null;
    }
  } catch (error) {
    console.error("Simulation error:", error);
    return null;
  }
};

Error Handling

Implement comprehensive error handling for transaction failures:

const sendTransactionWithErrorHandling = async (
  evmProxyMsg,
  sender,
  assets
) => {
  try {
    // Pre-flight validation
    if (!evmProxyMsg.evmTargetAddress) {
      throw new Error("EVM target address is required");
    }

    if (!assets || assets.length === 0) {
      console.warn("No assets specified for bridging");
    }

    // Simulate first (optional but recommended)
    const simulation = await tacSdk.getTransactionSimulationInfo(
      evmProxyMsg,
      sender,
      assets
    );

    if (!simulation.success) {
      throw new Error(`Transaction simulation failed: ${simulation.error}`);
    }

    // Execute transaction
    const result = await tacSdk.sendCrossChainTransaction(
      evmProxyMsg,
      sender,
      assets
    );

    return {
      success: true,
      operationId: result.operationId,
      transactionLinker: result,
    };
  } catch (error) {
    console.error("Transaction failed:", error);

    // Handle specific error types
    if (error.message.includes("insufficient balance")) {
      return {
        success: false,
        error: "Insufficient balance for transaction and fees",
      };
    }

    if (error.message.includes("invalid address")) {
      return {
        success: false,
        error: "Invalid contract address specified",
      };
    }

    if (error.message.includes("simulation failed")) {
      return {
        success: false,
        error: "Transaction would fail on execution",
      };
    }

    return {
      success: false,
      error: "Transaction failed: " + error.message,
    };
  }
};

// Usage
const result = await sendTransactionWithErrorHandling(
  evmProxyMsg,
  sender,
  assets
);

if (result.success) {
  console.log("Transaction sent:", result.operationId);
} else {
  console.error("Transaction failed:", result.error);
}

Transaction Lifecycle

Understanding the transaction lifecycle helps with proper error handling and user experience:

Parameter Validation

The SDK validates all parameters including addresses, amounts, and method signatures before proceeding.

Simulation (Optional)

If requested, the SDK simulates the transaction to estimate gas costs and validate execution.

Asset Preparation

Assets are prepared for bridging, including balance checks and approval transactions if needed.

Message Construction

The cross-chain message is constructed with proper encoding and sequencer routing information.

TON Transaction

Transaction is signed and submitted to the TON network via the connected wallet.

Cross-Chain Routing

The sequencer network processes the message and routes it to the TAC EVM layer.

EVM Execution

The target contract method is executed on TAC EVM with bridged assets available.

Completion

Transaction completes successfully, or rollback mechanisms are triggered on failure.

What’s Next?

With transactions executing successfully, learn to monitor their progress: