> ## Documentation Index
> Fetch the complete documentation index at: https://companyname-a7d5b98e-ton-storage.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Transaction fees

export const Aside = ({type = "note", title = "", icon = "", iconType = "regular", children}) => {
  const asideVariants = ["note", "tip", "caution", "danger"];
  const asideComponents = {
    note: {
      outerStyle: "border-sky-500/20 bg-sky-50/50 dark:border-sky-500/30 dark:bg-sky-500/10",
      innerStyle: "text-sky-900 dark:text-sky-200",
      calloutType: "note",
      icon: <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-sky-500" aria-label="Note">
          <path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.3C10.14 1.3 12.7 3.86 12.7 7C12.7 10.14 10.14 12.7 7 12.7C5.48908 12.6974 4.0408 12.096 2.97241 11.0276C1.90403 9.9592 1.30264 8.51092 1.3 7C1.3 3.86 3.86 1.3 7 1.3ZM7 0C3.14 0 0 3.14 0 7C0 10.86 3.14 14 7 14C10.86 14 14 10.86 14 7C14 3.14 10.86 0 7 0ZM8 3H6V8H8V3ZM8 9H6V11H8V9Z"></path>
        </svg>
    },
    tip: {
      outerStyle: "border-emerald-500/20 bg-emerald-50/50 dark:border-emerald-500/30 dark:bg-emerald-500/10",
      innerStyle: "text-emerald-900 dark:text-emerald-200",
      calloutType: "tip",
      icon: <svg width="11" height="14" viewBox="0 0 11 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="text-emerald-600 dark:text-emerald-400/80 w-3.5 h-auto" aria-label="Tip">
          <path d="M3.12794 12.4232C3.12794 12.5954 3.1776 12.7634 3.27244 12.907L3.74114 13.6095C3.88471 13.8248 4.21067 14 4.46964 14H6.15606C6.41415 14 6.74017 13.825 6.88373 13.6095L7.3508 12.9073C7.43114 12.7859 7.49705 12.569 7.49705 12.4232L7.50055 11.3513H3.12521L3.12794 12.4232ZM5.31288 0C2.52414 0.00875889 0.5 2.26889 0.5 4.78826C0.5 6.00188 0.949566 7.10829 1.69119 7.95492C2.14321 8.47011 2.84901 9.54727 3.11919 10.4557C3.12005 10.4625 3.12175 10.4698 3.12261 10.4771H7.50342C7.50427 10.4698 7.50598 10.463 7.50684 10.4557C7.77688 9.54727 8.48281 8.47011 8.93484 7.95492C9.67728 7.13181 10.1258 6.02703 10.1258 4.78826C10.1258 2.15486 7.9709 0.000106649 5.31288 0ZM7.94902 7.11267C7.52078 7.60079 6.99082 8.37878 6.6077 9.18794H4.02051C3.63739 8.37878 3.10743 7.60079 2.67947 7.11294C2.11997 6.47551 1.8126 5.63599 1.8126 4.78826C1.8126 3.09829 3.12794 1.31944 5.28827 1.3126C7.2435 1.3126 8.81315 2.88226 8.81315 4.78826C8.81315 5.63599 8.50688 6.47551 7.94902 7.11267ZM4.87534 2.18767C3.66939 2.18767 2.68767 3.16939 2.68767 4.37534C2.68767 4.61719 2.88336 4.81288 3.12521 4.81288C3.36705 4.81288 3.56274 4.61599 3.56274 4.37534C3.56274 3.6515 4.1515 3.06274 4.87534 3.06274C5.11719 3.06274 5.31288 2.86727 5.31288 2.62548C5.31288 2.38369 5.11599 2.18767 4.87534 2.18767Z"></path>
        </svg>
    },
    caution: {
      outerStyle: "border-amber-500/20 bg-amber-50/50 dark:border-amber-500/30 dark:bg-amber-500/10",
      innerStyle: "text-amber-900 dark:text-amber-200",
      calloutType: "warning",
      icon: <svg className="flex-none w-5 h-5 text-amber-400 dark:text-amber-300/80" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-label="Warning">
          <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
        </svg>
    },
    danger: {
      outerStyle: "border-red-500/20 bg-red-50/50 dark:border-red-500/30 dark:bg-red-500/10",
      innerStyle: "text-red-900 dark:text-red-200",
      calloutType: "danger",
      icon: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" className="text-red-600 dark:text-red-400/80 w-4 h-4" aria-label="Danger">
          <path d="M17.1 292c-12.9-22.3-12.9-49.7 0-72L105.4 67.1c12.9-22.3 36.6-36 62.4-36l176.6 0c25.7 0 49.5 13.7 62.4 36L494.9 220c12.9 22.3 12.9 49.7 0 72L406.6 444.9c-12.9 22.3-36.6 36-62.4 36l-176.6 0c-25.7 0-49.5-13.7-62.4-36L17.1 292zm41.6-48c-4.3 7.4-4.3 16.6 0 24l88.3 152.9c4.3 7.4 12.2 12 20.8 12l176.6 0c8.6 0 16.5-4.6 20.8-12L453.4 268c4.3-7.4 4.3-16.6 0-24L365.1 91.1c-4.3-7.4-12.2-12-20.8-12l-176.6 0c-8.6 0-16.5 4.6-20.8 12L58.6 244zM256 128c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"></path>
        </svg>
    }
  };
  let variant = type;
  let gotInvalidVariant = false;
  if (!asideVariants.includes(type)) {
    gotInvalidVariant = true;
    variant = "danger";
  }
  const iconVariants = ["regular", "solid", "light", "thin", "sharp-solid", "duotone", "brands"];
  if (!iconVariants.includes(iconType)) {
    iconType = "regular";
  }
  return <>
      <div className={`callout my-4 px-5 py-4 overflow-hidden rounded-2xl flex gap-3 border ${asideComponents[variant].outerStyle}`} data-callout-type={asideComponents[variant].calloutType}>
        <div className="mt-0.5 w-4" data-component-part="callout-icon">
          {}
          {icon === "" ? asideComponents[variant].icon : <Icon icon={icon} iconType={iconType} size={14} />}
        </div>
        <div className={`text-sm prose min-w-0 w-full ${asideComponents[variant].innerStyle}`} data-component-part="callout-content">
          {gotInvalidVariant ? <p>
              <span className="font-bold">
                Invalid <code>type</code> passed!
              </span>
              <br />
              <span className="font-bold">Received: </span>
              {type}
              <br />
              <span className="font-bold">Expected one of: </span>
              {asideVariants.join(", ")}
            </p> : <>
              {title && <p className="font-bold">{title}</p>}
              {children}
            </>}
        </div>
      </div>
    </>;
};

## Transaction fees

Fees on TON are calculated using this formula:

```cpp title="FORMULAS" theme={null}
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" theme={null}
storage_fee = ceil(
                   (account.bits * bit_price
                   + account.cells * cell_price)
                * time_delta / 2^16)
```

```ts title="TypeScript example" theme={null}
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.

<Aside type="note" title="Deduplication">
  The system counts only unique hash cells for storage and forward fees. For example, it counts three identical hash cells as one. This mechanism deduplicates data by storing the content of multiple equivalent sub-cells only once, even if they are referenced across different branches. [Read more about deduplication](/foundations/serialization/library).
</Aside>

## 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" theme={null}
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" theme={null}
// 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" theme={null}
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);
```

<Aside type="note" title="Action fee relation">
  `msg_fwd_fees` already includes the action fee for the corresponding `SENDRAWMSG`. For a basic internal message with `lump_price = 400000` and `first_frac = 21845`, `action_fee ≈ 133331` nanotons and `fwd_fee ≈ 266669` nanotons.
</Aside>

### 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" theme={null}
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" theme={null}
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 theme={null}
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<string>, 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<StorageValue> = {
    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<typeof configParseMsgPrices>;

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<string> = [];

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