The TON network operates on a unique message-based, asynchronous model, which can lead to common questions for developers: How do you confirm the final outcome of a transaction? What is the significance of the Bag of Cells (BOC) returned after sending a transaction, and how can you extract meaningful information from it? This guide provides clear answers and practical methods.
What Constitutes a TON Transaction?
A transaction on the TON blockchain is not a single atomic operation but a sequence of related events. Understanding its components is crucial for effective tracking.
- Inbound Message: This is the initial trigger, typically a message sent to a smart contract that initiates the entire process. Certain special methods can act as these triggers.
- Contract Execution: The core logic of the smart contract is executed based on the inbound message. This may result in updates to the contract's persistent storage state.
- Outbound Messages: As a result of execution, the contract may generate and send new messages to other contracts or addresses on the network, potentially creating a chain of transactions.
In essence, initiating a transfer is the act of sending a message to a wallet's smart contract, which then processes that message to perform the transfer.
Constructing a Valid Transaction Message
Before sending anything, you must construct a valid message. Using the JavaScript TonConnect SDK, transaction parameters are defined within a SendTransactionRequest object.
Basic Transfer Transaction
A simple transfer involves specifying the recipient's address and the amount.
const transaction = {
validUntil: Math.floor(Date.now() / 1000) + 600, // 10-minute validity window
messages: [
{
address: "0:412410771DA82CBA306A55FA9E0D43C9D245E38133CB58F1457DFB8D5CD8892F",
amount: "20000000", // Amount in nanotons (0.02 TON)
},
],
};Transfer Transaction with a Comment
Adding a comment requires a specific payload formatted as a serialized cell and encoded in Base64.
import { beginCell } from "@ton/ton";
// Build the comment payload
const body = beginCell()
.storeUint(0, 32) // opcode for a text comment
.storeStringTail("Hello, TON!") // the comment text
.endCell();
const transaction = {
validUntil: Math.floor(Date.now() / 1000) + 600,
messages: [
{
address: "0:412410771DA82CBA306A55FA9E0D43C9D245E38133CB58F1457DFB8D5CD8892F",
amount: "20000000",
payload: body.toBoc().toString("base64"), // Attach the encoded payload
},
],
};For advanced message types and detailed construction rules, consult the official TON documentation on message builders.
How to Track Transaction Results
Sending a transaction via TonConnect is just the first step. The sendTransaction method returns a promise that resolves to a SendTransactionResponse object.
const [tonConnectUi] = useTonConnectUI();
const result = await tonConnectUi.sendTransaction(transaction);
// result.boc contains the signed transaction BOCThis boc (Bag of Cells) string represents the signed, serialized transaction message that was broadcast to the network. It is your key to tracking the transaction's status.
Waiting for Transaction Confirmation
To confirm the transaction has been processed and included in a block, you need to poll the network using a TonClient instance and compare your transaction's hash to the latest ones on the recipient's account.
The following function demonstrates how to wait for confirmation by checking the target address for a transaction that matches your sent BOC's hash.
import { Address } from "@ton/ton";
const waitForTransaction = async (options, client) => {
const { hash, refetchInterval = 1000, refetchLimit, address } = options;
return new Promise((resolve) => {
let refetches = 0;
const walletAddress = Address.parse(address);
const interval = setInterval(async () => {
refetches += 1;
const state = await client.getContractState(walletAddress);
if (!state || !state.lastTransaction) {
// Handle cases where no state or transaction is found
return;
}
const { lt: lastLt, hash: lastHash } = state.lastTransaction;
const lastTx = await client.getTransaction(walletAddress, lastLt, lastHash);
if (lastTx && lastTx.inMessage) {
// Calculate the hash of the inbound message of the latest transaction
const msgCell = beginCell().store(storeMessage(lastTx.inMessage)).endCell();
const inMsgHash = msgCell.hash().toString("base64");
// Compare it to the hash of the BOC you sent
if (inMsgHash === hash) {
clearInterval(interval);
resolve(lastTx); // Transaction confirmed!
}
}
if (refetchLimit && refetches >= refetchLimit) {
clearInterval(interval);
resolve(null); // Stop after limit is reached
}
}, refetchInterval);
});
};This method uses getContractState to find the latest transaction's logical time (lt) and hash, then uses getTransaction to fetch its full details. Alternative strategies, such as using getTransactions to list multiple transactions, can also be effective for monitoring. ๐ Explore more strategies for tracking on-chain activity
Frequently Asked Questions
How long does a TON transaction typically take to confirm?
Confirmation time is usually very fast, often within seconds. The asynchronous nature means the transaction is final once it appears in the blockchain, but your dApp must actively check for its inclusion using the methods described.
What does it mean if my transaction BOC hash is never found?
This typically indicates that the transaction failed to be included in a block. This could be due to an error in the message structure, insufficient gas (TON coins) for fees, or the transaction expiring because the validUntil timestamp passed.
Can I get the transaction hash (TxHash) before it is confirmed?
No, the final transaction hash is generated by the blockchain upon successful processing and inclusion in a block. The BOC hash you get from sendTransaction is the hash of the message you sent, which is used to find the corresponding on-chain transaction later.
What's the difference between 'sendTransaction' and 'waitForTransaction'?sendTransaction is for broadcasting your signed message to the network. waitForTransaction (or your custom function) is for querying the network afterward to verify the message was accepted and processed into a transaction.
Is there a way to get the receipt of a TON transaction?
TON does not have receipts in the same way Ethereum does. You must query the transaction and its associated messages directly from the blockchain using a client like TonClient to get all relevant details and outcomes.
How can I handle errors or failed transactions?
A transaction not appearing is the primary sign of failure. You should implement a timeout logic (like refetchLimit in the example) in your waiting function to avoid infinite polling and then alert the user that the transaction may have failed.
Key Takeaways for Developers
Developing on TON requires a shift in mindset from account-based blockchains. Success hinges on understanding the message-passing paradigm. Tracking a transaction involves using the returned BOC as a reference to poll the network until you find a matching processed transaction. Mastering these concepts is fundamental to building responsive and reliable decentralized applications on TON. For a deeper dive into practical implementation, you can ๐ view real-time tools and code examples in community repositories.