diff --git a/docs.json b/docs.json
index 2ec75eb9..100a726a 100644
--- a/docs.json
+++ b/docs.json
@@ -486,7 +486,8 @@
},
"foundations/hypercube-routing",
"foundations/statuses",
- "foundations/phases-and-fees",
+ "foundations/phases",
+ "foundations/fees",
"foundations/shards",
"foundations/limits",
"foundations/config",
diff --git a/ecosystem/blueprint/benchmarks.mdx b/ecosystem/blueprint/benchmarks.mdx
index 387627f0..c1592c3c 100644
--- a/ecosystem/blueprint/benchmarks.mdx
+++ b/ecosystem/blueprint/benchmarks.mdx
@@ -13,11 +13,7 @@ Unlike many other blockchains, TON also requires you to pay for storing contract
As you develop and iterate on a contract, even small changes to its logic can affect both gas usage and data size. Monitoring these changes helps ensure that your contract remains efficient and cost-effective.
## Gas metrics reporting
@@ -162,7 +158,7 @@ storage_fee = ceil(
* time_delta / 2 ** 16)
```
-To try this in practice, use the [calculator example](/foundations/phases-and-fees).
+To try this in practice, use the [calculator example](/foundations/fees).
### Regenerate the gas report
diff --git a/foundations/fees.mdx b/foundations/fees.mdx
new file mode 100644
index 00000000..64ac33fa
--- /dev/null
+++ b/foundations/fees.mdx
@@ -0,0 +1,377 @@
+---
+title: "Transaction fees"
+---
+
+import { Aside } from '/snippets/aside.jsx';
+
+## Transaction fees
+
+Fees on TON are calculated using this formula:
+
+```cpp title="FORMULAS"
+transaction_fee = storage_fees
+ + in_fwd_fees // also called import fee
+ + computation_fees
+ + action_fees
+ + out_fwd_fees
+```
+
+All fees are denominated in nanotons (often scaled by `2^16` for precision) and come from network configuration:
+
+- `storage_fees`: param [18](https://tonviewer.com/config#18)
+
+- `in_fwd_fees`: params [24](https://tonviewer.com/config#24) and [25](https://tonviewer.com/config#25)
+
+- `computation_fees`: params [20](https://tonviewer.com/config#20) and [21](https://tonviewer.com/config#21)
+
+- `action_fees`: params [24](https://tonviewer.com/config#24) and [25](https://tonviewer.com/config#25)
+
+- `out_fwd_fees`: params [24](https://tonviewer.com/config#24) and [25](https://tonviewer.com/config#25)
+
+- `storage_fees` is the amount you pay for storing a smart contract on the blockchain. In fact, you pay for every second the smart contract is stored on the blockchain.
+ - _Example_: your TON wallet is also a smart contract, and it pays a storage fee every time you receive or send a transaction.
+
+- `in_fwd_fees` is a charge for importing messages only from outside the blockchain, for example, `external` messages. Every time you make a transaction, it must be delivered to the validators who will process it. For ordinary messages from contract to contract, this fee does not apply. Read [the TON Blockchain paper](/resources/pdfs/tblkch.pdf) to learn more about inbound messages.
+ - _Example_: each transaction you make with your wallet app (like Tonkeeper) must first be distributed among validators.
+
+- `computation_fees` is the amount you pay for executing code in the virtual machine. Computation fees depend on executed operations (gas used), not code size.
+ - _Example_: each time you send a transaction with your wallet (which is a smart contract), you execute the code of your wallet contract and pay for it.
+
+- `action_fees` is a charge for sending outgoing messages made by a smart contract, updating the smart contract code, updating libraries, etc.
+
+- `out_fwd_fees` is a charge for forwarding outgoing internal messages within TON between shardchains; it depends on message size.
+
+## Storage fee
+
+Storage fee for smart contracts is calculated using the following formula, values are defined in network config param [`18`](https://tonviewer.com/config#18):
+
+```cpp title="FORMULAS"
+storage_fee = ceil(
+ (account.bits * bit_price
+ + account.cells * cell_price)
+ * time_delta / 2^16)
+```
+
+```ts title="TypeScript example"
+import { Cell, beginCell } from '@ton/core';
+
+// Read latest storage prices (config param 18)
+const storage = getStoragePrices(configCell);
+
+// Account state as a Cell (e.g., code+data root)
+const accountRoot: Cell = /* load from blockchain */;
+const stats = collectCellStats(accountRoot, []);
+
+// Charge for one hour
+const fee = shr16ceil(
+ (stats.bits * storage.bit_price_ps + stats.cells * storage.cell_price_ps) *
+ 3600n
+);
+```
+
+> See [Helper functions appendix](/foundations/fees#helper-functions) for full implementations.
+
+
+
+## Gas fee
+
+All computation is measured in gas units; each TVM operation has a fixed gas cost.\
+The gas price is defined by network configuration and **can not be set by users**.
+
+- Basechain: 1 gas = `26214400 / 2^16` nanotons = 0.0000004 TON
+- Masterchain: 1 gas = `655360000 / 2^16` nanotons = 0.00001 TON
+
+See config parameters [`20`](https://tonviewer.com/config#20) and [`21`](https://tonviewer.com/config#21) for current gas prices.\
+The values can change through validator governance.
+
+```ts title="TypeScript example"
+const gasUsed = 50_000n;
+const prices = getGasPrices(configCell, 0); // 0 = basechain
+const gasFee =
+ gasUsed <= prices.flat_gas_limit
+ ? prices.flat_gas_price
+ : prices.flat_gas_price +
+ (prices.gas_price * (gasUsed - prices.flat_gas_limit)) / 65536n;
+```
+
+> See [Helper functions appendix](/foundations/fees#helper-functions) for full implementations.
+
+## Forward fee
+
+Forward fee for message size (`msg.bits`, `msg.cells`) per params [`24`](https://tonviewer.com/config#24)/[`25`](https://tonviewer.com/config#25):
+
+```cpp title="FORMULAS"
+// bits in the root cell of a message are not included in msg.bits (lump_price pays for them)
+msg_fwd_fees = (lump_price
+ + ceil(
+ (bit_price * msg.bits + cell_price * msg.cells) / 2^16)
+ );
+
+```
+
+```ts title="TypeScript example"
+const msgPrices = getMsgPrices(configCell, 0);
+const total =
+ msgPrices.lumpPrice +
+ shr16ceil(
+ msgPrices.bitPrice * BigInt(bits) +
+ msgPrices.cellPrice * BigInt(cells)
+ );
+const actionFee = (total * msgPrices.firstFrac) >> 16n;
+const forwardFee = total - actionFee;
+
+// From a Cell or Message:
+const fwdFromCell = computeCellForwardFees(msgPrices, messageRootCell);
+// For a full internal Message object (validates default lump case, handles init):
+const details = computeMessageForwardFees(msgPrices, internalMessage);
+```
+
+
+
+### Import fee
+
+Import fee is the same as forward fee for inbound external messages.
+
+
+
+## Action fee
+
+Action fee is charged in the Action phase and is the sender’s share of the forward fee.\
+You pay it for `SENDRAWMSG`; actions like `RAWRESERVE`, `SETCODE` do not incur the fees.
+
+```cpp title="FORMULAS"
+action_fee = floor((msg_fwd_fees * first_frac)/ 2^16); // internal
+
+action_fee = msg_fwd_fees; // external
+```
+
+`first_frac` (params [`24`](https://tonviewer.com/config#24)/[`25`](https://tonviewer.com/config#25)) divided by `2^16` ≈ 1/3 of `msg_fwd_fees`.
+
+Action fine (failed send): Starting with Global Version 4, a failed "send message" action incurs a penalty proportional to the attempted message size. It is calculated as:
+
+```cpp title="FORMULAS"
+fine_per_cell = floor((cell_price >> 16) / 4)
+max_cells = floor(remaining_balance / fine_per_cell)
+action_fine = fine_per_cell * min(max_cells, cells_in_msg);
+```
+
+## Helper functions (full code)
+
+```ts expandable
+import { Cell, Slice, beginCell, Dictionary, Message, DictionaryValue } from '@ton/core';
+
+export type GasPrices = {
+ flat_gas_limit: bigint,
+ flat_gas_price: bigint,
+ gas_price: bigint
+};
+
+export type StorageValue = {
+ utime_since: number,
+ bit_price_ps: bigint,
+ cell_price_ps: bigint,
+ mc_bit_price_ps: bigint,
+ mc_cell_price_ps: bigint
+};
+
+export class StorageStats {
+ bits: bigint;
+ cells: bigint;
+
+ constructor(bits?: number | bigint, cells?: number | bigint) {
+ this.bits = bits !== undefined ? BigInt(bits) : 0n;
+ this.cells = cells !== undefined ? BigInt(cells) : 0n;
+ }
+ add(...stats: StorageStats[]) {
+ let cells = this.cells, bits = this.bits;
+ for (let stat of stats) {
+ bits += stat.bits;
+ cells += stat.cells;
+ }
+ return new StorageStats(bits, cells);
+ }
+ addBits(bits: number | bigint) {
+ return new StorageStats(this.bits + BigInt(bits), this.cells);
+ }
+ addCells(cells: number | bigint) {
+ return new StorageStats(this.bits, this.cells + BigInt(cells));
+ }
+}
+
+function shr16ceil(src: bigint) {
+ const rem = src % 65536n;
+ let res = src / 65536n;
+ if (rem !== 0n) res += 1n;
+ return res;
+}
+
+export function collectCellStats(cell: Cell, visited: Array, skipRoot: boolean = false): StorageStats {
+ let bits = skipRoot ? 0n : BigInt(cell.bits.length);
+ let cells = skipRoot ? 0n : 1n;
+ const hash = cell.hash().toString();
+ if (visited.includes(hash)) {
+ return new StorageStats();
+ }
+ visited.push(hash);
+ for (const ref of cell.refs) {
+ const r = collectCellStats(ref, visited);
+ cells += r.cells;
+ bits += r.bits;
+ }
+ return new StorageStats(bits, cells);
+}
+
+export function getGasPrices(configRaw: Cell, workchain: 0 | -1): GasPrices {
+ const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell());
+ const ds = config.get(21 + workchain)!.beginParse();
+ if (ds.loadUint(8) !== 0xd1) throw new Error('Invalid flat gas prices tag');
+ const flat_gas_limit = ds.loadUintBig(64);
+ const flat_gas_price = ds.loadUintBig(64);
+ if (ds.loadUint(8) !== 0xde) throw new Error('Invalid gas prices tag');
+ return { flat_gas_limit, flat_gas_price, gas_price: ds.preloadUintBig(64) };
+}
+
+export function computeGasFee(prices: GasPrices, gas: bigint): bigint {
+ if (gas <= prices.flat_gas_limit) return prices.flat_gas_price;
+ return prices.flat_gas_price + (prices.gas_price * (gas - prices.flat_gas_limit)) / 65536n;
+}
+
+export const storageValue: DictionaryValue = {
+ serialize: (src, builder) => {
+ builder
+ .storeUint(0xcc, 8)
+ .storeUint(src.utime_since, 32)
+ .storeUint(src.bit_price_ps, 64)
+ .storeUint(src.cell_price_ps, 64)
+ .storeUint(src.mc_bit_price_ps, 64)
+ .storeUint(src.mc_cell_price_ps, 64);
+ },
+ parse: (src) => {
+ return {
+ utime_since: src.skip(8).loadUint(32),
+ bit_price_ps: src.loadUintBig(64),
+ cell_price_ps: src.loadUintBig(64),
+ mc_bit_price_ps: src.loadUintBig(64),
+ mc_cell_price_ps: src.loadUintBig(64)
+ };
+ }
+};
+
+export function getStoragePrices(configRaw: Cell): StorageValue {
+ const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell());
+ const storageData = Dictionary.loadDirect(Dictionary.Keys.Uint(32), storageValue, config.get(18)!);
+ const values = storageData.values();
+ return values[values.length - 1];
+}
+
+export function calcStorageFee(prices: StorageValue, stats: StorageStats, duration: bigint) {
+ return shr16ceil((stats.bits * prices.bit_price_ps + stats.cells * prices.cell_price_ps) * duration);
+}
+
+export const configParseMsgPrices = (sc: Slice) => {
+ const magic = sc.loadUint(8);
+ if (magic !== 0xea) throw new Error('Invalid message prices magic number');
+ return {
+ lumpPrice: sc.loadUintBig(64),
+ bitPrice: sc.loadUintBig(64),
+ cellPrice: sc.loadUintBig(64),
+ ihrPriceFactor: sc.loadUintBig(32),
+ firstFrac: sc.loadUintBig(16),
+ nextFrac: sc.loadUintBig(16)
+ };
+};
+
+export type MsgPrices = ReturnType;
+
+export const getMsgPrices = (configRaw: Cell, workchain: 0 | -1) => {
+ const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell());
+ const prices = config.get(25 + workchain);
+ if (prices === undefined) throw new Error('No prices defined in config');
+ return configParseMsgPrices(prices.beginParse());
+};
+
+export function computeDefaultForwardFee(msgPrices: MsgPrices) {
+ return msgPrices.lumpPrice - ((msgPrices.lumpPrice * msgPrices.firstFrac) >> 16n);
+}
+
+export function computeFwdFees(msgPrices: MsgPrices, cells: bigint, bits: bigint) {
+ return msgPrices.lumpPrice + shr16ceil(msgPrices.bitPrice * bits + msgPrices.cellPrice * cells);
+}
+
+export function computeFwdFeesVerbose(msgPrices: MsgPrices, cells: bigint | number, bits: bigint | number) {
+ const fees = computeFwdFees(msgPrices, BigInt(cells), BigInt(bits));
+ const res = (fees * msgPrices.firstFrac) >> 16n;
+ return { total: fees, res, remaining: fees - res };
+}
+
+export function computeCellForwardFees(msgPrices: MsgPrices, msg: Cell) {
+ const storageStats = collectCellStats(msg, [], true);
+ return computeFwdFees(msgPrices, storageStats.cells, storageStats.bits);
+}
+
+export function computeMessageForwardFees(msgPrices: MsgPrices, msg: Message) {
+ if (msg.info.type !== 'internal') throw new Error('Helper intended for internal messages');
+ let storageStats = new StorageStats();
+
+ const defaultFwd = computeDefaultForwardFee(msgPrices);
+ if (msg.info.forwardFee === defaultFwd) {
+ return {
+ fees: msgPrices.lumpPrice,
+ res: defaultFwd,
+ remaining: defaultFwd,
+ stats: storageStats
+ };
+ }
+
+ const visited: Array = [];
+
+ if (msg.init) {
+ let addBits = 5n;
+ let refCount = 0;
+ if (msg.init.splitDepth) addBits += 5n;
+ if (msg.init.libraries) {
+ refCount++;
+ storageStats = storageStats.add(
+ collectCellStats(beginCell().storeDictDirect(msg.init.libraries).endCell(), visited, true)
+ );
+ }
+ if (msg.init.code) {
+ refCount++;
+ storageStats = storageStats.add(collectCellStats(msg.init.code, visited));
+ }
+ if (msg.init.data) {
+ refCount++;
+ storageStats = storageStats.add(collectCellStats(msg.init.data, visited));
+ }
+ if (refCount >= 2) {
+ storageStats = storageStats.addCells(1).addBits(addBits);
+ }
+ }
+
+ const lumpBits = BigInt(msg.body.bits.length);
+ const bodyStats = collectCellStats(msg.body, visited, true);
+ storageStats = storageStats.add(bodyStats);
+
+ let feesVerbose = computeFwdFeesVerbose(msgPrices, storageStats.cells, storageStats.bits);
+ if (feesVerbose.remaining < msg.info.forwardFee) {
+ storageStats = storageStats.addCells(1).addBits(lumpBits);
+ feesVerbose = computeFwdFeesVerbose(msgPrices, storageStats.cells, storageStats.bits);
+ }
+ if (feesVerbose.remaining !== msg.info.forwardFee) {
+ throw new Error('Forward fee calculation mismatch');
+ }
+ return { fees: feesVerbose, stats: storageStats };
+}
+```
diff --git a/foundations/phases.mdx b/foundations/phases.mdx
new file mode 100644
index 00000000..15dcb3f4
--- /dev/null
+++ b/foundations/phases.mdx
@@ -0,0 +1,201 @@
+---
+title: "Execution phases"
+---
+
+import { Aside } from '/snippets/aside.jsx';
+
+When an event occurs on an account in The Open Network (TON) blockchain, it triggers a **transaction**.
+The most common event is receiving a message, but other events like `tick-tock`, `merge`, and `split` can also initiate transactions.
+
+Each transaction consists of up to five phases:
+
+1. **Storage phase**: calculates storage fees for the contract based on the space it occupies in the blockchain state.
+1. **Credit phase**: updates the contract balance by accounting for incoming message values and storage fees.
+1. **Compute phase**: executes the contract code on the TON Virtual Machine (TVM). The result includes `exit_code`, `actions`, `gas_details`, `new_storage`, and other data.
+1. **Action phase**: processes actions from the compute phase if it succeeds.\
+ Actions may include sending messages, updating contract code, or modifying libraries. If an action fails (for example, due to a lack of funds), the transaction may revert or skip the action, depending on its mode. For example, `mode = 0, flag = 2` means that any errors arising while processing this message during the action phase are ignored.
+1. **Bounce phase**: If the compute phase ends with an error and the inbound message has the bounce flag set, this phase generates a bounce message. If the send\_msg action failed and it had the +16 flag set, then the bounce phase will also be triggered.
+
+> Compute, Action and Bounce phases may be skipped
+
+
+
+## Fee deduction sequence
+
+1. Import fee (before the first phase)
+1. Storage fee (storage phase)
+1. Gas fee (compute phase)
+1. Action fee + forward fee (action phase)
+1. Additional forward fees (bounce phase)
+
+## Storage phase
+
+Cannot be skipped.
+
+In this phase, the blockchain processes fees related to the account's persistent storage. Let's start by looking at the TL-B schema:
+
+```tlb
+tr_phase_storage$_ storage_fees_collected:Grams
+ storage_fees_due:(Maybe Grams)
+ status_change:AccStatusChange
+ = TrStoragePhase;
+```
+
+The `storage_fees_due` field is of type `Maybe` because it is only present when the account has **insufficient balance** to cover the storage fees. When the account has enough funds, this field is omitted.
+
+> Note: Grams are unsigned integers, so account balances cannot be negative.
+
+The `AccStatusChange` field indicates whether the account's status changed during this phase. For example, see [account status variety](/foundations/statuses#status-variety).
+
+## Credit phase
+
+Cannot be skipped.
+
+This phase is relatively small and straightforward.
+The main logic of this phase is to **credit the contract’s balance** with the remaining value from the incoming message.
+
+The credit phase is serialized in TL-B as follows:
+
+```tlb
+tr_phase_credit$_ due_fees_collected:(Maybe Grams)
+ credit:CurrencyCollection = TrCreditPhase;
+```
+
+This phase consists of the following two fields:
+
+| Field | Type | Description |
+| -------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------- |
+| `due_fees_collected` | `Maybe Grams` | Amount of previously due storage fees collected (present only if storage fees were due and collected in this phase). |
+| `credit` | `CurrencyCollection` | The amount credited to the account as a result of receiving the incoming message. |
+
+## Compute phase
+
+The **compute phase** is one of the most complex stages of a transaction. This is where the smart contract code, stored in the account’s state, is executed.
+
+Unlike previous phases, the TL-B definition for the compute phase includes multiple variants.
+
+```tlb
+tr_phase_compute_skipped$0 reason:ComputeSkipReason
+ = TrComputePhase;
+tr_phase_compute_vm$1 success:Bool msg_state_used:Bool
+ account_activated:Bool gas_fees:Grams
+ ^[ gas_used:(VarUInteger 7)
+ gas_limit:(VarUInteger 7) gas_credit:(Maybe (VarUInteger 3))
+ mode:int8 exit_code:int32 exit_arg:(Maybe int32)
+ vm_steps:uint32
+ vm_init_state_hash:bits256 vm_final_state_hash:bits256 ]
+ = TrComputePhase;
+cskip_no_state$00 = ComputeSkipReason;
+cskip_bad_state$01 = ComputeSkipReason;
+cskip_no_gas$10 = ComputeSkipReason;
+cskip_suspended$110 = ComputeSkipReason;
+```
+
+### When the compute phase is skipped
+
+To start, note that the compute phase can be **skipped** entirely. In that case, the reason for skipping is explicitly recorded and can be one of the following:
+
+| Skip reason | Description |
+| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `cskip_no_state` | The smart contract has no [state](/foundations/statuses#status-variety) and, therefore, no code, so execution is not possible. |
+| `cskip_bad_state` | Raised in two cases: when the `fixed_prefix_length` [field has an invalid value][fixed_prefix_length] or when the `StateInit` provided in the incoming message [does not match the account’s address][account_address]. |
+| `cskip_no_gas` | The incoming message did not provide enough TON to cover the gas required to execute the smart contract. |
+| `cskip_suspended` | The address is suspended; execution is disabled (used to limit early miner accounts). |
+
+[fixed_prefix_length]: https://github.com/ton-blockchain/ton/blob/72056a2261cbb11f7cf0f20b389bcbffe018b1a8/crypto/block/transaction.cpp#L1721
+
+[account_address]: https://github.com/ton-blockchain/ton/blob/72056a2261cbb11f7cf0f20b389bcbffe018b1a8/crypto/block/transaction.cpp#L1726
+
+
+
+## Action phase
+
+Once the smart contract code has finished executing, the **Action phase** begins. If any actions were created during the compute phase, they are processed at this stage.
+
+There are precisely 4 types of actions in TON:
+
+```tlb
+action_send_msg#0ec3c86d mode:(## 8)
+ out_msg:^(MessageRelaxed Any) = OutAction;
+action_set_code#ad4de08e new_code:^Cell = OutAction;
+action_reserve_currency#36e6b809 mode:(## 8)
+ currency:CurrencyCollection = OutAction;
+libref_hash$0 lib_hash:bits256 = LibRef;
+libref_ref$1 library:^Cell = LibRef;
+action_change_library#26fa1dd4 mode:(## 7)
+ libref:LibRef = OutAction;
+```
+
+| Type | Description |
+| ------------------------- | ------------------------------------------------------------------------------------------ |
+| `action_send_msg` | Sends a message. |
+| `action_set_code` | Updates the smart contract's code. |
+| `action_reserve_currency` | Reserves a portion of the account's balance. This is especially useful for gas management. |
+| `action_change_library` | Changes the library used by the smart contract. |
+
+These actions are executed _in the order in which they were created_ during code execution.
+A total of up to [255 actions](https://github.com/ton-blockchain/ton/blob/cac968f77dfa5a14e63db40190bda549f0eaf746/crypto/block/transaction.h#L164) can be made.
+
+Here is the TL-B schema, which defines the structure of the action phase:
+
+```tlb
+tr_phase_action$_ success:Bool valid:Bool no_funds:Bool
+ status_change:AccStatusChange
+ total_fwd_fees:(Maybe Grams) total_action_fees:(Maybe Grams)
+ result_code:int32 result_arg:(Maybe int32) tot_actions:uint16
+ spec_actions:uint16 skipped_actions:uint16 msgs_created:uint16
+ action_list_hash:bits256 tot_msg_size:StorageUsed
+ = TrActionPhase;
+```
+
+## Bounce phase
+
+If the **Compute phase** or **Action phase** ends with an error, and the incoming message has the `bounce` flag set, the system triggers the **Bounce phase**.
+
+
+
+```tlb
+tr_phase_bounce_negfunds$00 = TrBouncePhase;
+tr_phase_bounce_nofunds$01 msg_size:StorageUsed
+ req_fwd_fees:Grams = TrBouncePhase;
+tr_phase_bounce_ok$1 msg_size:StorageUsed
+ msg_fees:Grams fwd_fees:Grams = TrBouncePhase;
+```
+
+The `tr_phase_bounce_negfunds` type is not used in the current version of the blockchain. The other two types function as follows:
+
+| Type | Description |
+| ------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
+| `tr_phase_bounce_nofunds` | Indicates that the account does not have enough funds to process the message that should be bounced back to the sender. |
+| `tr_phase_bounce_ok` | Indicates that the system successfully processes the bounce and sends the message back to the sender. |
+
+In this phase, `msg_fees` and `fwd_fees` are calculated from the `total_fwd_fees`:\
+approximately $\frac{1}{3}$ goes to `msg_fees` and $\frac{2}{3}$ go to `fwd_fees`.
+
+> See [Fees → Forward fee](/foundations/fees#forward-fee) for more info.
+
+### Key points
+
+- If the receiver cannot parse the message and terminates with a non-zero exit code, the message bounces back automatically.
+- The bounced message has its `bounce` flag cleared and `bounced` flag set, and contains `0xffffffff` (32-bit) op code followed by the original message body.
+- Always check the `bounced` flag before parsing `op` to avoid treating a bounce as a new query.
diff --git a/patterns/fee-management.mdx b/patterns/fee-management.mdx
new file mode 100644
index 00000000..e69de29b
diff --git a/resources/dictionaries/custom.txt b/resources/dictionaries/custom.txt
index 844488b6..14902d00 100644
--- a/resources/dictionaries/custom.txt
+++ b/resources/dictionaries/custom.txt
@@ -595,6 +595,7 @@ relatedly
relayer
resharding
resynced
+retracer
rollup
ruleset
runbooks
@@ -681,6 +682,7 @@ trie
truthy
ts
tsUSDe
+txtracer
uint
unary
unblockable
diff --git a/snippets/feePlayground.jsx b/snippets/feePlayground.jsx
new file mode 100644
index 00000000..8f59ca57
--- /dev/null
+++ b/snippets/feePlayground.jsx
@@ -0,0 +1,166 @@
+export const FeePlayground = () => {
+ const nano = 10 ** -9;
+ const bit16 = 2 ** 16;
+ const Note = ({ title, children }) => (
+