> For the complete documentation index, see [llms.txt](https://docs.kton.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kton.io/protocol-internals/11-payout-nft-receipts.md).

# Payout NFT Receipts (Bills)

When KTON cannot settle a deposit or a withdrawal in the same transaction, it does not park your funds in an opaque internal queue. Instead it mints you an on-chain NFT, a "bill", that records exactly how much you are owed and pays out automatically when the round closes. This chapter documents the payout NFT subsystem at the contract level: how a collection is deployed per round and per side, how a bill is minted, how the burn cascade distributes assets pro-rata at round end, the unit semantics that differ between deposit and withdrawal bills, the transferability danger, and the tail-cleanup mechanics.

Source of record for this chapter: `contracts/payout_nft/nft-collection.func`, `contracts/payout_nft/nft-item.func`, `contracts/payout_nft/op-codes.func`, `contracts/payout_nft/errors.func`, plus the pool-side minting and distribution logic in `contracts/pool.func` and `contracts/pool_mint_helpers.func`.

## Where bills come from

KTON has two settlement paths. The optimistic path (live on the public pool) settles deposits and instant withdrawals inside the originating transaction and never produces a bill. The pessimistic path settles at round end and uses bills. A pessimistic deposit is minted a **deposit bill**; a pessimistic withdrawal is minted a **withdrawal bill**.

On the public KTON pool the optimistic flag is ON, so most user deposits convert to KTON immediately. The pessimistic path still matters: it is the structural fallback the contract reverts to, it is what the bill machinery is built for, and the same machinery settles every queued withdrawal that cannot take the (economically disabled) instant path. The contract code paths described below are exactly the ones that run when settlement is deferred to round end.

A bill is a single-use receipt. It is **not** transferable in any economically safe sense (see "The transferability danger"), and it must never be sent to another contract. The bill's own on-chain metadata says so literally.

## Collection identity: per-round, per-side

Each payout collection is a distinct contract. The pool deploys a **new collection per validation round and per side**: one deposit collection and one withdrawal collection per round, at most. The collection address is derived from a `StateInit` built in `calculate_payout_state_init(pool_address, round_id, distributing_jettons?)` (`contracts/address_calculations.func`).

Two properties of that derivation matter:

* The metadata `name` is built as `"Deposit Payout#<round_id>"` when `distributing_jettons?` is true, or `"Withdrawal Payout#<round_id>"` when false. So the side and the round are baked into the collection identity.
* The address is **intentionally unpredictable**. The state init folds in `random_seed`, derived from `random() >> 8` plus the hash of the contract's current storage (`randomize_lt()` is called first). This means you cannot pre-compute or front-run a collection address from the round number alone.

Because the address is unpredictable, the pool stores the live collection address in its own state. The pool's storage carries `deposit_payout` and `withdrawal_payout` (each a `MsgAddress`, nullable) plus the running tallies `requested_for_deposit` (in Gram) and `requested_for_withdrawal` (in KTON). See `pool.func` globals at lines 38 to 41.

### Collection storage layout

The collection storage (`nft-collection.func`, `load_data`) is:

```
storage#_ issued_bills:Coins
          admin:MsgAddress
          distribution:Maybe ^Cell
          collection_content:^Cell
          ^[ next_item_index:uint64
             prev:MsgAddress current:MsgAddress next:MsgAddress
             next_state_init:^Cell ]
```

* `issued_bills` is the **sum of all bill weights** issued so far (not a count). It is the denominator of the pro-rata split.
* `admin` is the pool. Only the pool may init, mint, and start distribution.
* `distribution` is a `Maybe` cell. While null, the collection is uninitialized. After init it carries `(started?, jettons?, volume, [jetton_wallet])`.
* The trailing ref keeps a singly linked list of items: `next_item_index` and the `prev`/`current`/`next` item addresses, plus the precomputed `next_state_init` so the next mint is cheap.

## Lifecycle op-codes

All op-codes below are the literal constants from `contracts/payout_nft/op-codes.func` (collection side) and `contracts/op-codes.func` (pool side). The pool-side names map to the collection-side handlers one to one.

| Step                           | Pool sends                                  | Collection handler op                    | Value                        |
| ------------------------------ | ------------------------------------------- | ---------------------------------------- | ---------------------------- |
| Deploy collection              | `payout::init` `0xf5aa8943`                 | `op::init_collection` `0xf5aa8943`       | `MINTER_DEPLOY_FEE` 0.1 Gram |
| Mint one bill                  | `payout::mint` `0x1674b0a0`                 | `op::mint_nft` `0x1674b0a0`              | carries remaining value      |
| Start distribution (Gram side) | `payouts::start_distribution` `0x1140a64f`  | `op::start_distribution` `0x1140a64f`    | the Gram volume to pay out   |
| Start distribution (KTON side) | jetton `transfer_notification` `0x7362d09c` | `op::transfer_notification` `0x7362d09c` | the KTON volume to pay out   |

Internal op-codes used between the collection and its items:

| Op                       | Code         | Direction                        | Meaning                                 |
| ------------------------ | ------------ | -------------------------------- | --------------------------------------- |
| `op::init_nft`           | `0x132f9a45` | collection to item               | deploy and initialize a bill            |
| `op::burn`               | `0xf127fe4e` | collection to item, item to item | trigger a bill burn                     |
| `op::burn_notification`  | `0xed58b0b2` | item to collection               | report weight and index, request payout |
| `op::distributed_asset`  | `0xdb3b8abd` | collection to user               | tag on the Gram or KTON payout message  |
| `op::return_unused`      | `0x4bc7c2df` | collection to pool               | sweep leftover gas after the last burn  |
| `op::ownership_assigned` | `0x05138d91` | item to owner                    | standard TEP-62 mint/transfer notice    |

## Minting a bill

Minting begins pool-side in `request_to_mint_deposit` or `request_to_mint_withdrawal` (`pool_mint_helpers.func`). On the first request of a round for that side, the pool sees its stored payout address is null, so it:

1. Computes the unpredictable collection `StateInit` and address.
2. Records the address in `deposit_payout` / `withdrawal_payout`.
3. Sends `payout::init` `0xf5aa8943` with `MINTER_DEPLOY_FEE` (0.1 Gram) and a distribution body that encodes `distribution_started = false`, `jettons? = true` for deposits or `false` for withdrawals, an initial `volume` of 0, and (deposit side only) the collection's own KTON jetton-wallet address.

On `init_collection` the collection asserts the sender is the admin (`error::unauthorized_init` 0xffff), asserts it has not already been initialized (`error::can_not_reinit_started_distribution` 87), and asserts the init body says distribution is not yet started with zero volume (`error::wrong_initial_distribution_info` 86). This guarantees a freshly deployed collection can never claim to already hold a distribution.

After init (or immediately, if the collection already exists for this round and side), the pool sends `payout::mint` `0x1674b0a0` to the collection carrying `(destination_owner, weight)`. The pool also increments its own running tally: `requested_for_deposit += amount` or `requested_for_withdrawal += amount`.

On `mint_nft` the collection (`nft-collection.func` lines 130 to 165):

* Asserts the sender is the admin (`error::unauthorized_mint_request` 73).
* Asserts distribution has not started (`error::mint_after_distribution_start` 72). You cannot add bills to a round that is already paying out.
* Adds the new bill's weight to `issued_bills`.
* Computes the next item `StateInit` and address, advances the linked-list cursor, and deploys the item with `op::init_nft` `0x132f9a45`. The init body carries `(owner, weight, next, prev)`. Note the comment in source: `next` and `prev` are stored in reverse order on purpose, which is what makes the round-end cascade walk the list correctly.

Each item, on `init_nft`, reserves `min_tons_for_storage` (0.09 Gram), stores `(owner, amount, prev, next)`, and sends the owner an `ownership_assigned` `0x05138d91` so a standard wallet shows the NFT. From this point the bill is a live, individually addressable contract owned by the user.

## Bill weight semantics: deposit vs withdrawal

The weight stored in a bill is **side-dependent**, and this is the single most important detail for reading payouts correctly.

|                                | Deposit bill                                   | Withdrawal bill                    |
| ------------------------------ | ---------------------------------------------- | ---------------------------------- |
| `weight` unit                  | Gram deposited                                 | KTON (pool jetton) being redeemed  |
| `issued_bills` unit            | total Gram queued this round                   | total KTON queued this round       |
| Asset distributed at round end | newly minted KTON                              | Gram                               |
| `jettons?` flag in collection  | true                                           | false                              |
| Distribution trigger           | jetton `transfer_notification` from the minter | `start_distribution` carrying Gram |

So a deposit bill is denominated in the asset you put **in** (Gram) and pays out the asset you want **out** (KTON). A withdrawal bill is the mirror: denominated in the KTON you burned, paying out Gram. The pro-rata formula is identical for both sides; only the units and the distributed asset differ.

The conversion rate is **not** locked when the bill is minted. The rate that actually applies is the realized end-of-round rate, because the total volume to distribute (the numerator of every payout) is fixed only when the pool fires distribution at round close. This is why a bill is a claim on a *share of a pool*, not a fixed-amount IOU.

## Round close: starting distribution

At `finalize_deposit_withdrawal_round` (`pool.func` line 665) the pool runs both sides:

**Withdrawal side, `initiate_distribution_of_tons`** (line 670): if `requested_for_withdrawal` is nonzero, the pool computes `ton_withdrawal = muldiv(requested_for_withdrawal, total_balance, supply)`, the Gram owed to the whole batch at the realized rate. If the pool cannot cover that plus `MIN_TONS_FOR_STORAGE`, it sets `halted? = true` and pays nobody until topped up (the self-halt safety described in the unstaking chapter). Otherwise it reduces `supply` and `total_balance`, clears `requested_for_withdrawal` and `withdrawal_payout`, and sends the collection `payouts::start_distribution` `0x1140a64f` with `ton_withdrawal + TRANSFER_NOTIFICATION_AMOUNT` of Gram attached.

**Deposit side, `initiate_distribution_of_pool_jettons`** (line 706): if `requested_for_deposit` is nonzero, the pool mints `jetton_mint = muldiv_extra(requested_for_deposit, supply, total_balance)` KTON (or 1:1 if `supply == 0`) to the deposit collection's jetton wallet, then `total_balance += requested_for_deposit` and clears the deposit tallies. The KTON arrives at the collection as a jetton `transfer_notification`, which is what flips the deposit collection into distribution mode.

On the collection side, `start_distribution` (Gram path) and `transfer_notification` (KTON path) both do the same thing: assert distribution has not already started (`error::distribution_already_started` 68), write the real `volume` to distribute into the `distribution` cell, mark `started = true`, and fire the first `op::burn` `0xf127fe4e` at the `current` item (the head of the list). For the Gram path, `volume = msg_value - start_distribution_gas_usage`; for the KTON path, `volume = burnt_amount` carried in the transfer.

## The burn cascade and pro-rata distribution

This is the elegant part. There is no loop in the collection over a stored list of owners. Instead the bills form a linked list and the burn propagates itself.

When an item receives `op::burn` (`nft-item.func` `burn`, line 149):

1. It checks the sender is its `prev` neighbor or the collection (`error::unauthorized` 401), so only the legitimate chain can burn it.
2. If its `next` neighbor exists, it forwards `op::burn` to that neighbor with `burn_notification_amount` (0.01 Gram). This is what makes the cascade self-propagating: every burn lights the fuse on the next bill.
3. It sends the collection `op::burn_notification` `0xed58b0b2` carrying `(amount, owner, index)`, using send mode `CARRY_ALL_BALANCE | DESTROY` (64 + 32). So the item pays out the rest of its balance and **self-destructs** in the same message.

When the collection receives `op::burn_notification` (line 225):

1. Asserts distribution started (`error::burn_before_distribution` 66). A bill cannot be usefully burned early; this is why you cannot self-burn for a refund before round end.
2. Recomputes the expected item address from the reported `burnt_index` and checks it matches the sender (`error::unauthorized_burn_notification` 74). A forged burn notification cannot steal a share.
3. Computes this bill's share:

```
share_amount = muldiv(burnt_amount, volume_to_distribute, issued_bills)
```

which is exactly `(this_bill_weight / total_weight) * volume`.

4. Decrements `volume_to_distribute` by `share_amount` and decrements `issued_bills` by `burnt_amount` (so the next bill divides the *remaining* volume by the *remaining* weight; the proportions stay exact and rounding dust does not accumulate against later bills).
5. Sends the payout to `burner_address` (the bill's owner at burn time), tagged with `op::distributed_asset` `0xdb3b8abd`:
   * Withdrawal collection (`jettons? = false`): a plain Gram message of `share_amount` to the owner.
   * Deposit collection (`jettons? = true`): a `jetton_transfer` `0xf8a7ea5` of `share_amount` KTON from the collection's jetton wallet to the owner.

Because each item forwards the burn to its `next` before the collection processes the notification, the whole list of bills burns in **one automatic cascade** seeded by a single `start_distribution`. No per-user transaction is needed. The owner simply receives Gram or KTON.

### Tail cleanup

The cascade terminates at the head of the list, index 0. In the `burn_notification` handler:

```
if(burnt_index == 0) {
    ... send op::return_unused (0x4bc7c2df) to admin with CARRY_ALL_BALANCE
}
```

When the index-0 bill reports its burn, the collection knows the list is fully drained. It sweeps its entire remaining balance back to the pool (`admin`) with `op::return_unused` `0x4bc7c2df`, mode `CARRY_ALL_BALANCE` (128). This reclaims leftover gas budget so the round's settlement nets out cleanly and no dust is stranded in the spent collection.

## The transferability danger

The item contract *is* a TEP-62 NFT: it implements `op::nft_transfer` `0x5fcc3d14` and ownership can technically change. **This is a trap, not a feature, and the docs are explicit about it.**

The payout in the burn cascade goes to `burner_address`, which is the bill's **owner at burn time**, not the original depositor. If you transfer or sell a bill before the round closes, the new owner receives your Gram or KTON when the cascade runs. There is no claw-back. The bill's own on-chain metadata, generated in `get_nft_content` (`nft-collection.func` line 321), sets the description to:

* Deposit bill: `"DO NOT SEND ON CONTRACTS: Automatically converts deposited TON to Pool Jettons when ready"`
* Withdrawal bill: `"DO NOT SEND ON CONTRACTS: Converts burned Pool Jettons to TON when ready"`

Practical rules:

* **Do not transfer a payout bill.** Keep it in the wallet that minted it until it settles.
* **Do not send a bill to a contract.** A contract destination may not be able to receive the eventual payout, and the bill is not designed to be held by arbitrary contracts.
* **Do not try to self-burn early.** The collection rejects any burn before distribution starts with `error::burn_before_distribution` 66, so an early burn cannot extract value ahead of schedule. The bill settles when, and only when, the pool finalizes the round.

There is nothing for a well-behaved user to do with a bill except hold it. It will self-destruct and pay you automatically at round end (the practical \~36 hour settlement cycle described in the unstaking chapter).

## Get methods for monitoring

The collection and item expose read methods useful for indexers and for verifying a pending claim:

* Collection `get_issued_bills()` returns `(issued_bills, next_item_index)`: total outstanding weight and the bill count.
* Collection `get_distribution_data()` returns the `distribution` cell: whether distribution has started, the side (`jettons?`), and the remaining `volume`.
* Collection `get_collection_data()` returns `(next_item_index, collection_content, admin)`.
* Collection `get_nft_address_by_index(index)` derives a bill address deterministically from its index.
* Item `get_nft_data()` returns `(init?, index, collection, owner, content)` where content carries the bill `amount` (weight).
* Item `get_bill_amount()` returns the bill weight directly.

## Summary

| Concept                 | Deposit bill                                                      | Withdrawal bill                      |
| ----------------------- | ----------------------------------------------------------------- | ------------------------------------ |
| Minted when             | a deposit cannot settle in-transaction                            | a withdrawal queues to round end     |
| `weight` denominated in | Gram deposited                                                    | KTON burned                          |
| Pays out                | newly minted KTON                                                 | Gram                                 |
| Collection name         | `Deposit Payout#<round>`                                          | `Withdrawal Payout#<round>`          |
| Distribution trigger    | jetton transfer to the collection                                 | `start_distribution` with Gram       |
| Per-bill share          | `muldiv(weight, remaining_volume, remaining_issued_bills)`        | same formula                         |
| At burn, paid to        | owner at burn time (transfer danger)                              | owner at burn time (transfer danger) |
| Lifecycle end           | self-destruct via `DESTROY`; tail sweeps gas with `return_unused` | same                                 |

The bill system gives KTON a fully on-chain, automatic, pro-rata settlement queue with no privileged off-chain keeper: one `start_distribution` triggers a self-propagating burn cascade that pays every claimant their exact share at the realized end-of-round rate, then cleans up after itself. The one rule for users is simple: never move the receipt.

Next: **Contract Reference**
