> 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/10-governance-roles-upgrades.md).

# Governance, Roles and Upgrades

KTON governance: the role slots, what each can do, the 3-of-4 multisig that holds them, upgrades and halts.

KTON's pool is not controlled by a single owner key, and it is not controlled by token-holder voting at the pool level. Instead, authority is split across eight named role slots, each holding a specific, limited power. Some powers carry an on-chain quarantine (a waiting period that counts from the moment the role is set, not from the moment it acts), and the most sensitive power of all, replacing the contract's code, sits in a separate role slot from the one that governs day-to-day parameters. Today every admin slot is held by the same 3-of-4 multisig. This chapter walks through every role, how powers are split between the slots, the code-upgrade mechanism, the governor-migration handshake, and the halt/unhalt safety controls. All op-codes, fields, and constants below are cited from the deployed FunC sources.

## The eight role slots

The pool packs all of its authority into one storage tuple. The shape is defined in `roles_helper.func` and loaded by `~load_roles` in `pool_storage.func`:

```
roles = [sudoer, sudoer_set_at, governor, governor_update_after,
         interest_manager, halter, approver, treasury]
```

The accessors in `roles_helper.func` index this tuple by position (`0 INDEX` for sudoer, `1 INDEX` for `sudoer_set_at`, and so on through `7 INDEX` for treasury). Two of the eight slots are not addresses at all: `sudoer_set_at` (index 1) and `governor_update_after` (index 3) are 48-bit timestamps that drive the quarantines described later. The other six slots are contract addresses (slices). Any documentation that lists only six roles, and omits the two timestamp slots, is describing an older layout.

`~load_roles` parses the addresses across two cells: `sudoer`, `sudoer_set_at`, `governor`, `governor_update_after`, and `interest_manager` sit in the first cell, then a referenced cell holds `halter`, `approver`, and `treasury`. There is a backward-compatibility detail worth noting: if the inner cell has run out of bits when the parser reaches the treasury slot (`ds.slice_bits() ? ... : interest_manager`), the treasury defaults to the interest-manager address. `save_roles` writes the same two-cell structure back.

| Slot                    | Index | Type               | Held by                                                       |
| ----------------------- | ----- | ------------------ | ------------------------------------------------------------- |
| `sudoer`                | 0     | address            | Superuser key (code/data upgrades, arbitrary messages)        |
| `sudoer_set_at`         | 1     | timestamp (48-bit) | When the current sudoer was installed; starts the 2-day clock |
| `governor`              | 2     | address            | Central day-to-day authority                                  |
| `governor_update_after` | 3     | timestamp (48-bit) | Earliest time a prepared governor migration may execute       |
| `interest_manager`      | 4     | address            | Sets interest rate and loan operational params                |
| `halter`                | 5     | address            | Emergency stop (halt / partial-halt)                          |
| `approver`              | 6     | address            | Approves controllers to borrow (used on the controller side)  |
| `treasury`              | 7     | address            | Passive fee sink                                              |

## Each role's powers

### Governor

The governor is the central operating authority. In `pool.func` it is the only sender accepted for the following operations (each guarded by `assert_sender!(sender_address, get_governor(roles))`):

* `governor::set_sudoer` (`0x79e7c016`): installs a new sudoer address and stamps `sudoer_set_at` to `now()`, which restarts the sudoer's quarantine.
* `governor::unhalt` (`0x7247e7a5`): clears `halted?` back to false.
* `governor::prepare_governance_migration` (`0x9971881c`): arms a future governor change (the first step of the two-step migration).
* `governor::set_roles` (`0x5e517f36`): updates `governor` (only after migration matures), `interest_manager`, `halter`, `approver`, and `treasury`.
* `governor::set_deposit_settings` (`0x9bf5561c`): sets `optimistic_deposit_withdrawals`, `deposits_open?`, and `instant_withdrawal_fee`.
* `governor::set_governance_fee` (`0x2aaa96a0`): sets `governance_fee_share`.

Crucially, the governor cannot upgrade the contract's code and cannot set the interest rate. Those powers belong to the sudoer and the interest-manager respectively. This is the core of the split between the slots: the role that tunes economics day to day is structurally unable to rewrite the contract or move the validator interest rate.

### Sudoer

The sudoer is the superuser, but it is the most constrained in time. It can do two things, both handled in `sudoer_requests.func` and both gated by the 2-day quarantine:

* `sudo::send_message` (`0x270695fb`): `process_sudo_request` loads an 8-bit send mode and a message cell, then issues `send_raw_message(message, mode)`. This lets the sudoer emit an arbitrary message with an arbitrary send-mode on behalf of the pool.
* `sudo::upgrade` (`0x96e7f528`): `process_sudo_upgrade_request` loads up to three maybe-refs, `data`, `code`, and `after_upgrade`. If `data` is present it calls `set_data(data)`; if `code` is present it calls `set_code(code)`; if `after_upgrade` is present it `execute`s that continuation. It then `throw(1)` to unwind.

Both sudo handlers begin with `assert_sender!(sender, sudoer)` and `throw_unless(sudoer::quarantine, now() > sudoer_set_at + SUDOER_QUARANTINE)`. In the contract, the only guardrail on the sudoer is this quarantine; once it has elapsed (as it has for the current sudoer, whose `sudoer_set_at` is 0), the sudoer can replace code and storage outright. In practice the sudoer is the 3-of-4 multisig, so an upgrade needs three of its four signers. No upgrade has ever been sent to the public pool. The sudoer cannot install itself: only the governor can call `set_sudoer`, and doing so resets the clock.

### Interest-manager

The interest-manager governs the loan economics, not the user-facing pool flags. In `pool.func`, guarded by `assert_sender!(sender_address, get_interest_manager(roles))` and `assert_state!(state::NORMAL)`:

* `interest_manager::set_interest` (`0xc9f04485`): sets `interest_rate` (a 24-bit share value).
* `interest_manager::set_operational_params` (`0x4485c9f0`): sets `min_loan_per_validator`, `max_loan_per_validator` (with `max >= min` enforced via `error::contradicting_operational_params`), `disbalance_tolerance` (an 8-bit value, `DISBALANCE_BIT_SIZE`), and `credit_start_prior_elections_end`.

The interest-manager also receives per-round statistics. It cannot touch the governance fee, the sudoer, the deposit settings, or the halt state.

### Halter

The halter is the emergency-stop role. It can pause but never resume:

* `halter::halt` (`0x139a1b4e`): sets `halted? = true`. The handler intentionally does not `end_parse` the message, so any trailing data can carry an off-chain reason.
* `halter::partial_halt` (`0x77778888`): reads two booleans, `stop_optimistic` and `close_deposits`. Setting the first flips `optimistic_deposit_withdrawals` to false; setting the second flips `deposits_open?` to false.

The halter cannot call `unhalt`. Only the governor can clear `halted?`. This split means an emergency key can freeze the pool quickly but cannot quietly un-freeze it.

### Approver

The approver lives on the controller side of the protocol. It approves controllers to request loans and is part of the profit-share setup (`approver_set_profit_share`). It has no authority over pool state, fees, halting, or upgrades.

### Treasury

The treasury is a passive fee sink. It receives the governance fee and other protocol fees routed out of the pool. It holds no operational authority of any kind: it cannot send pool messages, change parameters, halt, or upgrade.

## Roles and their powers, at a glance

| Action                                               | Required role     | Quarantine                     |
| ---------------------------------------------------- | ----------------- | ------------------------------ |
| Replace code / data (`sudo::upgrade`)                | sudoer            | 2 days (`SUDOER_QUARANTINE`)   |
| Send arbitrary pool message (`sudo::send_message`)   | sudoer            | 2 days (`SUDOER_QUARANTINE`)   |
| Install a new sudoer (`set_sudoer`)                  | governor          | none (but resets sudoer clock) |
| Prepare governor migration                           | governor          | none to arm                    |
| Execute governor migration (via `set_roles`)         | governor          | 1 day (`GOVERNOR_QUARANTINE`)  |
| Set interest\_manager / halter / approver / treasury | governor          | none (instant)                 |
| Set interest rate / loan params                      | interest\_manager | none                           |
| Set deposit settings / governance fee                | governor          | none                           |
| Halt / partial-halt                                  | halter            | none                           |
| Unhalt                                               | governor          | none                           |

No single role can both rewrite the contract and operate it day to day; the upgrade key (sudoer) and the operating key (governor) are deliberately different roles, and the only role that can mint a new sudoer (governor) cannot itself perform the upgrade.

## The upgrade mechanism

Code upgrades are sudoer-only, executed through `sudo::upgrade` (`0x96e7f528`), and they are not possible until the sudoer has been in place for at least `SUDOER_QUARANTINE`. From `sudoer_requests.func`:

```
const int SUDOER_QUARANTINE = 2 * 24 * 3600 ;   ;; 172800 seconds, 2 days
```

The flow is:

1. The governor installs the intended sudoer with `governor::set_sudoer`, which stamps `sudoer_set_at = now()`.
2. Two days must pass. Until then, every `sudo::upgrade` (and every `sudo::send_message`) reverts with `sudoer::quarantine` (`0xa000`).
3. After the quarantine, the sudoer sends `sudo::upgrade` with optional `data`, `code`, and `after_upgrade` refs. The handler applies `set_data` and/or `set_code`, optionally runs the `after_upgrade` continuation, then unwinds.

Re-installing the sudoer (calling `set_sudoer` again) restarts the two-day clock, because `sudoer_set_at` is reset to the new `now()`. There is no governor-driven code upgrade, and there is no on-chain version-bump operation. The pool exposes `get_code_version()` in `versioning.func`, which simply returns a compile-time `git_hash` from `auto/git-hash.func`. That value is informational only; it is not a control surface and does not change as part of any upgrade transaction.

## Governor migration (two-step, 1-day quarantine)

Replacing the governor itself is a deliberate two-step handshake with its own 1-day quarantine, defined in `pool.func`:

```
const int GOVERNOR_QUARANTINE = 86400; ;; 1 day
```

Step one, `governor::prepare_governance_migration` (`0x9971881c`): the current governor submits a `governor_update_after` timestamp. The handler stores it via `set_governor_update_after`, then enforces `throw_unless(error::governor_update_too_soon, governor_update_after - now() > GOVERNOR_QUARANTINE)`, that is, the proposed effective time must be more than one day in the future. The error code for proposing too soon is `error::governor_update_too_soon` (`0xa001`).

Step two, `governor::set_roles` (`0x5e517f36`): the governor change is applied only inside `set_roles`, and only when its first boolean flag is set. At that point the handler enforces `throw_unless(error::governor_update_not_matured, get_governor_update_after(roles) < now())`, so the prepared timestamp must already have passed. The new governor address is then installed and `governor_update_after` is set to a sentinel far-future value (`0xffffffffffff`) so the slot cannot be re-used without a fresh prepare step. The error code for executing before maturity is `error::governor_update_not_matured` (`0xa003`).

The same `set_roles` message can also update `interest_manager`, `halter`, `approver`, and `treasury` in the same transaction, each behind its own boolean flag. Those four role changes are instant: only the governor slot itself carries the 1-day maturity check. This is the practical asymmetry of the model: rotating the emergency, interest, approval, and fee-sink keys is immediate, but rotating the central governor key requires a public, day-long waiting window.

## Halt, partial-halt, unhalt and self-halt

Halting is the pool's circuit breaker. There are three ways it can engage and exactly one way it can be cleared.

**Full halt.** `halter::halt` (`0x139a1b4e`) sets `halted? = true`. Once halted, `assert_not_halted!()` (defined in `asserts.func`) makes the gated operations revert. Halting blocks deposits, loan requests, loan repayments, round upkeep, controller deployment, and similar pool operations.

**Partial halt.** `halter::partial_halt` (`0x77778888`) is a softer brake. It can flip `optimistic_deposit_withdrawals` off (forcing the queued, pessimistic path) and/or flip `deposits_open?` off (closing new deposits), without setting the global `halted?` flag.

**Self-halt.** The pool can halt itself. If, at round finalization, it cannot cover the queued withdrawals plus its storage reserve (`MIN_TONS_FOR_STORAGE`, 10 Gram), it sets `halted? = true` on its own and pays nobody until it is topped up. No human key is involved in this path; it is a safety latch.

**Withdrawals stay open.** This is the most important property of the halt design. The withdraw handler runs `assert_not_halted!()` inside a `try` block, so a halt during withdrawal does not destroy value: the burned KTON is re-minted back to the user (the `catch` branch calls `request_to_mint_pool_jettons(from_address, jetton_amount, query_id, true)`). The burn-notification op (`pool::withdraw`) is the path a user's KTON burn lands on, and it is structured so that any revert refunds the user's KTON rather than losing it. In other words, halting can stop new deposits and loan activity, but it is built so the protocol can always either pay out or hand back the user's tokens.

**Only the governor unhalts.** Clearing the halt requires `governor::unhalt` (`0x7247e7a5`), guarded by `assert_sender!(sender_address, get_governor(roles))`. The halter that pressed the button cannot release it; resumption is a separate, higher-trust role. This guarantees that an emergency stop is reviewed by the governing key before normal operation resumes.

## Why this model matters

In the contract, the slot that can rewrite the contract (sudoer) is separate from the slot that runs it (governor), from the slot that can freeze it (halter), and the slot that receives fees (treasury) has no power at all. Today the sudoer slot is empty (switched off on 2026-10-23) and the other admin slots are held by the same 3-of-4 multisig, so the layers are in the code rather than between different people. The two most dangerous transitions have on-chain quarantines (2 days for a newly set sudoer, 1 day for a governor migration); both count from when the role is set. None of these controls are token-voted at the pool level; they are address-keyed with mandatory waiting periods, which is the protocol's chosen form of trust minimization.

Next: **Payout NFT Receipts**
