Transaction lookup using external message from TON Connect
This article describes how to find a transaction associated with an external-in message in the TON Blockchain. The process includes message normalization, searching for the transaction by message hash, and waiting for transaction confirmation.
Message normalization
In TON, messages may contain fields such as init
, src
, and importFee
. These fields should be removed or zeroed out before calculating the message hash, as described in TEP-467.
export function getNormalizedExtMessageHash(message: Message) {
if (message.info.type !== 'external-in') {
throw new Error(`Message must be "external-in", got ${message.info.type}`);
}
const info = {
...message.info,
src: undefined,
importFee: 0n
};
const normalizedMessage = {
...message,
init: null,
info: info,
};
return beginCell()
.store(storeMessage(normalizedMessage, { forceRef: true }))
.endCell()
.hash();
}
Retrying API calls
API requests may fail due to rate limits or network issues.
Use the retry
function to handle such failures:
export async function retry<T>(fn: () => Promise<T>, options: { retries: number; delay: number }): Promise<T> {
let lastError: Error | undefined;
for (let i = 0; i < options.retries; i++) {
try {
return await fn();
} catch (e) {
if (e instanceof Error) {
lastError = e;
}
await new Promise((resolve) => setTimeout(resolve, options.delay));
}
}
throw lastError;
}
Finding a transaction by incoming message
The getTransactionByInMessage
function searches the account’s transaction history for a match by normalized external message hash:
async function getTransactionByInMessage(
inMessageBoc: string,
client: TonClient,
): Promise<Transaction | undefined> {
const inMessage = loadMessage(Cell.fromBase64(inMessageBoc).beginParse());
if (inMessage.info.type !== 'external-in') {
throw new Error(`Message must be "external-in", got ${inMessage.info.type}`);
}
const account = inMessage.info.dest;
const targetInMessageHash = getNormalizedExtMessageHash(inMessage);
let lt: string | undefined = undefined;
let hash: string | undefined = undefined;
while (true) {
const transactions = await retry(
() =>
client.getTransactions(account, {
hash,
lt,
limit: 10,
archival: true,
}),
{ delay: 1000, retries: 3 },
);
if (transactions.length === 0) {
return undefined;
}
for (const transaction of transactions) {
if (transaction.inMessage?.info.type !== 'external-in') {
continue;
}
const inMessageHash = getNormalizedExtMessageHash(transaction.inMessage);
if (inMessageHash.equals(targetInMessageHash)) {
return transaction;
}
}
const last = transactions.at(-1)!;
lt = last.lt.toString();
hash = last.hash().toString('base64');
}
}
If found, the function returns a Transaction
object. Otherwise, it returns undefined
.
Waiting for transaction confirmation
If a message has just been sent, it may take a few seconds before it appears on-chain.
The waitForTransaction
function polls the blockchain and waits for the corresponding transaction:
async function waitForTransaction(
inMessageBoc: string,
client: TonClient,
retries: number = 10,
timeout: number = 1000,
): Promise<Transaction | undefined> {
const inMessage = loadMessage(Cell.fromBase64(inMessageBoc).beginParse());
if (inMessage.info.type !== 'external-in') {
throw new Error(`Message must be "external-in", got ${inMessage.info.type}`);
}
const account = inMessage.info.dest;
const targetInMessageHash = getNormalizedExtMessageHash(inMessage);
let attempt = 0;
while (attempt < retries) {
const transactions = await retry(
() =>
client.getTransactions(account, {
limit: 10,
archival: true,
}),
{ delay: 1000, retries: 3 },
);
for (const transaction of transactions) {
if (transaction.inMessage?.info.type !== 'external-in') {
continue;
}
const inMessageHash = getNormalizedExtMessageHash(transaction.inMessage);
if (inMessageHash.equals(targetInMessageHash)) {
return transaction;
}
}
await new Promise((resolve) => setTimeout(resolve, timeout));
attempt++;
}
return undefined;
}
Examples
Find a transaction by incoming message
import { TonClient } from '@ton/ton';
const client = new TonClient({ endpoint: 'https://toncenter.com/api/v2/jsonRPC' });
const tx = await getTransactionByInMessage(
'te6ccgEBAQEA...your-base64-message...',
client
);
if (tx) {
console.log('Found transaction:', tx);
} else {
console.log('Transaction not found');
}
Wait for transaction confirmation
import { TonClient } from '@ton/ton';
const client = new TonClient({ endpoint: 'https://toncenter.com/api/v2/jsonRPC' });
const [tonConnectUI, setOptions] = useTonConnectUI();
// Obtain ExternalInMessage boc
const { boc } = await tonConnectUI.sendTransaction({
messages: [
{
address: "UQBSzBN6cnxDwDjn_IQXqgU8OJXUMcol9pxyL-yLkpKzYpKR",
amount: "20000000"
}
]
});
const tx = await waitForTransaction(
boc,
client,
10, // retries
1000, // timeout before each retry
);
if (tx) {
console.log('Found transaction:', tx);
} else {
console.log('Transaction not found');
}