This guide explains how to send cross-chain transactions from TON to TAC EVM using the TAC SDK. The core of hybrid dApp development is sending transactions through the TON Adapter to execute operations on EVM contracts.
The sendCrossChainTransaction
method is the primary function for sending data and assets from TON to TAC EVM. It creates a transaction on the TON side that initiates a cross-chain operation.
Understanding Cross-Chain Transactions
A cross-chain transaction in TAC typically involves:
- A call to an EVM contract on TAC
- Optional asset transfers (tokens) between chains
- Tracking the transaction’s progress across chains
Basic Transaction Structure
Here’s how to create and send a basic cross-chain transaction:
import { TacSdk, Network, EvmProxyMsg, AssetBridgingData } from '@tonappchain/sdk';
import { ethers } from 'ethers';
async function sendCrossChainTx() {
// Initialize SDK
const tacSdk = await TacSdk.create({
network: Network.TESTNET
});
// 1. Prepare EVM message for the target contract
const evmProxyMsg: EvmProxyMsg = {
evmTargetAddress: "0xTargetContractAddress", // Target contract on TAC
methodName: "myMethod(bytes,bytes)", // Method to call
encodedParameters: "0x...", // Encoded method parameters
};
// 2. Use a sender (from wallet integration)
const sender = /* your sender from wallet integration */;
// 3. Optional: Specify assets to bridge
const assets: AssetBridgingData[] = [
{
address: "EQTokenAddress...",
amount: 10.5, // User-friendly amount
decimals: 9 // Optional, will be detected automatically if not provided
}
];
// 4. Send the transaction
const transactionLinker = await tacSdk.sendCrossChainTransaction(
evmProxyMsg,
sender,
assets
);
// The transactionLinker can be used for tracking the transaction
return transactionLinker;
}
Parameter Encoding for EVM Methods
When calling an EVM contract method, you need to encode parameters correctly. Use the ethers.js ABI encoder:
import { ethers } from 'ethers';
// Create an ABI encoder
const abi = new ethers.AbiCoder();
// Encode parameters for a simple method
const simpleParams = abi.encode(
['uint256', 'address'],
[1000000, '0xRecipientAddress']
);
// Encode parameters for a complex method with structs
const complexParams = abi.encode(
['tuple(uint256,uint256,address[],address)'],
[
[
1000000, // amount1
500000, // amount2
['0xToken1Address', '0xToken2Address'], // token addresses
'0xRecipientAddress' // recipient
]
]
);
Always include the full method signature including parameter types in methodName
(e.g., "swap(bytes,bytes)"
) or the transaction will fail. The method must accept two bytes
parameters.
Asset Bridging
You can bridge assets (tokens) from TON to TAC as part of your transaction:
// Bridge a token
const assets: AssetBridgingData[] = [
{
address: "EQTokenAddress...", // TON token address
amount: 10.5 // User-friendly amount
}
];
// Bridge native TON
const bridgeTON: AssetBridgingData[] = [
{
// No address means native TON
amount: 1.5 // TON amount
}
];
// Bridge multiple tokens
const multipleAssets: AssetBridgingData[] = [
{
address: "EQToken1Address...",
amount: 10.5
},
{
address: "EQToken2Address...",
amount: 20.25
}
];
You can also use raw amounts if you prefer:
const assetsWithRawAmount: AssetBridgingData = {
address: "EQTokenAddress...",
rawAmount: BigInt(10500000000) // Raw amount with decimals included
};
Cross-Chain Transaction Examples
Example 1: Simple Contract Call (No Asset Transfer)
// Call a contract method without sending assets
const callOnly = await tacSdk.sendCrossChainTransaction(
{
evmTargetAddress: "0xTargetContract",
methodName: "getData(bytes,bytes)",
encodedParameters: "0x" // Empty parameters
},
sender,
[] // No assets to bridge
);
Example 2: Token Swap on DEX
// Encode parameters for a swap
const swapParams = abi.encode(
['tuple(uint256,uint256,address[],address,uint256)'],
[
[
ethers.parseUnits("10", 18), // amountIn
ethers.parseUnits("9.5", 18), // amountOutMin
[evmTokenInAddress, evmTokenOutAddress], // path
userAddress, // to
Math.floor(Date.now() / 1000) + 3600 // deadline (1 hour)
]
]
);
// Send transaction
const swapTx = await tacSdk.sendCrossChainTransaction(
{
evmTargetAddress: "0xDexProxyAddress",
methodName: "swapExactTokensForTokens(bytes,bytes)",
encodedParameters: swapParams
},
sender,
[
{
address: tvmTokenAddress,
amount: 10 // Amount to swap
}
]
);
Example 3: Adding Liquidity to a Pool
// Encode parameters for adding liquidity
const addLiquidityParams = abi.encode(
['tuple(address,address,uint256,uint256,uint256,uint256,address,uint256)'],
[
[
evmTokenAAddress,
evmTokenBAddress,
ethers.parseUnits("10", 18), // amountA
ethers.parseUnits("20", 18), // amountB
ethers.parseUnits("9.5", 18), // amountAMin
ethers.parseUnits("19", 18), // amountBMin
userAddress, // to
Math.floor(Date.now() / 1000) + 3600 // deadline
]
]
);
// Send transaction with multiple assets
const addLiquidityTx = await tacSdk.sendCrossChainTransaction(
{
evmTargetAddress: "0xLiquidityProxyAddress",
methodName: "addLiquidity(bytes,bytes)",
encodedParameters: addLiquidityParams
},
sender,
[
{
address: tvmTokenAAddress,
amount: 10
},
{
address: tvmTokenBAddress,
amount: 20
}
]
);
Transaction Simulation
Before sending a transaction, you can simulate it to estimate gas costs and check for potential issues:
// Prepare simulation request parameters
const simulationParams = {
tacCallParams: {
target: "0xTargetAddress",
methodName: "myMethod(bytes,bytes)",
arguments: encodedParameters
},
extraData: "0x", // Extra data
feeAssetAddress: "", // Empty for native TON
shardsKey: 123456, // Unique identifier
tonAssets: [
{
tokenAddress: "EQTokenAddress...",
amount: "10500000000" // Raw amount as string
}
],
tonCaller: "userTonWalletAddress"
};
// Simulate the transaction
const simulationResults = await tacSdk.simulateTACMessage(simulationParams);
// Check simulation results
if (simulationResults.simulationStatus) {
console.log("Simulation successful!");
console.log("Estimated gas:", simulationResults.estimatedGas.toString());
} else {
console.error("Simulation failed:", simulationResults.simulationError);
}
Error Handling
When sending transactions, several types of errors can occur:
try {
const tx = await tacSdk.sendCrossChainTransaction(evmProxyMsg, sender, assets);
// Transaction sent successfully
} catch (error) {
if (error.name === 'ContractError') {
console.error("Contract error:", error.message);
// Handle contract not deployed on TVM side
} else if (error.name === 'AddressError') {
console.error("Address error:", error.message);
// Handle invalid token address
} else {
console.error("Transaction error:", error);
// Handle other errors
}
}