diff --git a/docs.json b/docs.json
index 402e5072..940a1641 100644
--- a/docs.json
+++ b/docs.json
@@ -167,6 +167,7 @@
"group": "RPC providers",
"pages": [
"ecosystem/rpc/overview",
+ "ecosystem/rpc/external-normalization",
"ecosystem/rpc/toncenter"
]
},
diff --git a/ecosystem/rpc/external-normalization.mdx b/ecosystem/rpc/external-normalization.mdx
new file mode 100644
index 00000000..58b36450
--- /dev/null
+++ b/ecosystem/rpc/external-normalization.mdx
@@ -0,0 +1,274 @@
+---
+title: "External message normalisation"
+---
+
+import { Aside } from "/snippets/aside.jsx";
+
+
+
+
+Normalization is a standardization process that converts different representations into a consistent format. While messages across interfaces follow the TL-B scheme, structural differences in implementation sometimes lead to collisions.
+
+To address this, the ecosystem defines a standard that ensures consistent hash calculation. The normalization rules are specified in detail in [TEP-467](https://github.com/ton-blockchain/TEPs/pull/467).
+
+**The problem normalization solves:**
+
+Functionally identical messages may differ in how they represent the `src`, `import_fee`, and `init` fields. These variations result in different hashes for messages with equivalent content, which complicates transaction tracking and deduplication.
+
+**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
+
+This guide shows how to find the transaction associated with an `external-in` message on the TON blockchain.
+
+## Message normalization
+
+In TON, messages may contain fields like `init`, `src` and `importFee`. These fields should be removed or zeroed out before calculating the message hash, as described in [TEP-467](https://github.com/ton-blockchain/TEPs/blob/8b3beda2d8611c90ec02a18bec946f5e33a80091/text/0467-normalized-message-hash.md).
+
+Use the function below to generate a **normalized message hash**:
+
+```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
+
+Sometimes API requests may fail due to rate limits or network issues. Use `retry` function presented below to deal with api failures:
+
+```ts
+export async function retry(fn: () => Promise, options: { retries: number; delay: number }): Promise {
+ 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
+
+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 {
+ // 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 transaction history of 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
+
+```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
+
+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 {
+ 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
+
+```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
+
+* [TEP-467: Normalized Message Hash](https://github.com/ton-blockchain/TEPs/blob/8b3beda2d8611c90ec02a18bec946f5e33a80091/text/0467-normalized-message-hash.md)
+* [Messages and transactions](/ton/transaction)
+* [TON Connect: Sending messages](/ecosystem/ton-connect/index)
\ No newline at end of file
diff --git a/techniques/using-libraries.mdx b/techniques/using-libraries.mdx
index 00f4f07a..593b79e3 100644
--- a/techniques/using-libraries.mdx
+++ b/techniques/using-libraries.mdx
@@ -2,7 +2,7 @@
title: "Using libraries"
---
-import { Image, ImageControls } from '/snippets/image.jsx';
+import { Image } from '/snippets/image.jsx';
It is recommended to read [Library cells](/ton/cells/library-cells) first.
diff --git a/ton/cells/bag-of-cells.mdx b/ton/cells/bag-of-cells.mdx
new file mode 100644
index 00000000..e69de29b