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: 500000 n , // Gas limit (optional)
};
The SDK accepts flexible method name formats:
Full Signature Simple Name No Method Name const evmProxyMsg = {
evmTargetAddress: "0x742d35Cc647332..." ,
methodName: "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)" ,
encodedParameters: encodedSwapParams
};
const evmProxyMsg = {
evmTargetAddress: "0x742d35Cc647332..." ,
methodName: "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)" ,
encodedParameters: encodedSwapParams
};
const evmProxyMsg = {
evmTargetAddress: "0x742d35Cc647332..." ,
methodName: "transfer" , // SDK will format as "transfer()"
encodedParameters: encodedTransferParams
};
const evmProxyMsg = {
evmTargetAddress: "0x742d35Cc647332..." ,
// No methodName - calls contract directly with encoded data
encodedParameters: "0x..."
};
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
Native TON Jetton Tokens NFT Items Multiple Assets const assets = [
{
type: AssetType . FT ,
amount: 1.5
} // Bridge 1.5 TON
];
const assets = [
{
type: AssetType . FT ,
amount: 1.5
} // Bridge 1.5 TON
];
const assets = [
{
type: AssetType . FT ,
address: "EQJettonMasterAddress..." ,
amount: 1000.0 // Bridge 1000 jetton tokens
}
];
const assets = [
{
type: AssetType . NFT ,
address: "EQNFTItemAddress..." // Direct NFT item address
}
];
const assets = [
{
type: AssetType . FT ,
amount: 2.0
}, // Native TON
{
type: AssetType . FT ,
address: "EQJettonAddress..." ,
amount: 500.0
}, // Jetton
{
type: AssetType . NFT ,
address: "EQNFTItemAddress..."
} // NFT
];
For precise control, use raw asset format:
const rawAssets = [
{
type: AssetType . FT , // Fungible Token
address: "EQJettonMasterAddress..." ,
rawAmount: 1000000000 n , // 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: 10000000 n , // Custom protocol fee (nanotons)
evmValidExecutors: [
// Trusted EVM executors
"0xExecutor1Address..." ,
"0xExecutor2Address..." ,
],
evmExecutorFee: 50000000 n , // EVM executor fee (wei)
tvmValidExecutors: [
// Trusted TON executors
"EQExecutor1Address..." ,
"EQExecutor2Address..." ,
],
tvmExecutorFee: 100000000 n , // 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: