# Message lookup (https://docs-fpm2731fy-ton-core-docs.vercel.app/llms/ecosystem/ton-connect/message-lookup/content.md)



<Callout type="danger">
  You should never use external message tracking for payment processing purposes. Check out [payment processing](/llms/payments/overview/content.md) for more details.
</Callout>

## Introduction [#introduction]

The process of seeking a transaction associated with an `external-in` message is called **message lookup**. It should only be used for better UX to display, for example, the progress of the operation and its result.

### Message normalization [#message-normalization]

Normalization is a standardization process that converts different external-in message representations into a consistent format. This needs to be done because the structure of external-in messages allows the same message to be constructed in different forms, which results in different possible hashes for the same external message.

To address this, the ecosystem defines a standard that ensures consistent hash calculation. The normalization rules are specified in detail in the [TEP-467 proposal](https://github.com/ton-blockchain/TEPs/pull/467). Message lookup by its normalized hash is already implemented in most TON [RPC providers](/llms/ecosystem/api/overview/content.md).

**How normalization works:**

The normalized hash is computed by applying the following standardization rules to an external-in message:

1. **Source Address (`src`)**: set to `addr_none$00`
2. **Import Fee (`import_fee`)**: set to `0`
3. **InitState (`init`)**: set to an empty value
4. **Body**: always stored as a reference

## Transaction lookup using external message from TON Connect [#transaction-lookup-using-external-message-from-ton-connect]

```ts
/**
 * Generates a normalized hash of an "external-in" message for comparison.
 *
 * This function ensures consistent hashing of external-in messages by following [TEP-467](https://github.com/ton-blockchain/TEPs/blob/8b3beda2d8611c90ec02a18bec946f5e33a80091/text/0467-normalized-message-hash.md):
 *
 * @param {Message} message - The message to be normalized and hashed. Must be of type `"external-in"`.
 * @returns {Buffer} The hash of the normalized message.
 * @throws {Error} if the message type is not `"external-in"`.
 */
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 [#retrying-api-calls]

Sometimes API requests may fail due to rate limits or network issues. Use the `retry` function presented below to deal with api failures:

```ts
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;
}
```

## Find the transaction by incoming message [#find-the-transaction-by-incoming-message]

The `getTransactionByInMessage` function searches the account’s transaction history for a match by normalized external message hash:

```ts
/**
 * Tries to find transaction by ExternalInMessage
 */
async function getTransactionByInMessage(
    inMessageBoc: string,
    client: TonClient,
): Promise<Transaction | undefined> {
    // Step 1. Convert Base64 BoC to Message if input is a string
    const inMessage = loadMessage(Cell.fromBase64(inMessageBoc).beginParse());

    // Step 2. Ensure the message is an external-in message
    if (inMessage.info.type !== 'external-in') {
        throw new Error(`Message must be "external-in", got ${inMessage.info.type}`);
    }
    const account = inMessage.info.dest;

    // Step 3. Compute the normalized hash of the input message
    const targetInMessageHash = getNormalizedExtMessageHash(inMessage);

    let lt: string | undefined = undefined;
    let hash: string | undefined = undefined;

    // Step 4. Paginate through the transaction history of the account
    while (true) {
        const transactions = await retry(
            () =>
                client.getTransactions(account, {
                    hash,
                    lt,
                    limit: 10,
                    archival: true,
                }),
            { delay: 1000, retries: 3 },
        );

        if (transactions.length === 0) {
            // No more transactions found - message may not be processed yet
            return undefined;
        }

        // Step 5. Search for a transaction whose input message matches the normalized hash
        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, it returns a `Transaction` object. Otherwise, it returns `undefined`.

### Example [#example]

```ts
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');
}
```

## Waiting for transaction confirmation [#waiting-for-transaction-confirmation]

If you’ve just sent a message, it may take a few seconds before it appears on-chain.
The function `waitForTransaction` to poll the blockchain and wait for the corresponding transaction should be used in this case:

```ts
/**
 * Waits for a transaction to appear on-chain by incoming external message.
 *
 * Useful when the message has just been sent.
 */
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) {
        console.log(`Waiting for transaction to appear in network. Attempt: ${attempt}`);

        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));
    }

    // Transaction was not found - message may not be processed
    return undefined;
}
```

### Example [#example-1]

```typescript
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');
}
```

## See also [#see-also]

* [TEP-467: Normalized Message Hash](https://github.com/ton-blockchain/TEPs/blob/8b3beda2d8611c90ec02a18bec946f5e33a80091/text/0467-normalized-message-hash.md)
* [Messages and transactions](/llms/foundations/messages/ordinary-tx/content.md)
* [TON Connect: Sending messages](/llms/ecosystem/ton-connect/overview/content.md)
