# Privileged credentials, and who controls the money in year three CONCEPT.md §8 says there is **no admin withdraw path on any TON balance, not timelocked, not multisig**, and `NoAdminWithdraw.spec.ts` proves it against `withdraw-surface.md`'s enumeration of every outbound send. That claim is true and it is narrow. It is a claim about **moving TON**, and a reader who takes it as "there are no privileged keys at all" has read more into it than it says. So this page is the other half: **every standing credential in the system, what it can do, what it cannot, where the chain publishes it, and which test bounds it.** There are three. Each was already honest in its own source file; what was missing was one place that lists them together, which is the difference between an accurate claim and a credible one. Enumerated from source, not from memory: - **Address credentials** — every `in.senderAddress == …` comparison across all 21 contracts, minus the ones naming a peer protocol contract (a fixed or derived address that is itself bound by these same rules, and whose own sends are in `withdraw-surface.md`). Exactly one survives: the venue timelock's proposer. - **Key credentials** — every `isSignatureValid(…)` call site. There are three, holding two distinct keys. **The other half of each row is [key-loss.md](key-loss.md)** (`GAUNTLET.md` X6): this page is the inventory — what exists and what each can do — and that one is the failure story, what stops working the day each is lost and which of them cannot be replaced without a genesis redeploy. `sender-gates.md` is the machine-generated census these were read off; it classifies every arm on every contract as authorised or deliberately permissionless, and `SenderGates.spec.ts` fails the build when an arm appears in neither. **Section 4 is a different question and it is here because the answer is next door.** The three credentials above are what exists *today*; the owner's stated succession plan for year three is the treasury's governance, and how thin its electorate may legally be is the single most important fact about who controls this money over a decade. Until now it lived only in a source comment (`GAUNTLET.md` X5). --- ## 1. The venue proposer — `primes_venue_timelock.tolk` | | | |---|---| | **What it is** | An address, `Storage.proposerAddr` (`primes_venue_timelock.tolk:82`), fixed in the contract's deploy-time (address-determining) data. | | **What it can do** | `queue` and `cancel` a change to the **seven venue addresses** `primes_ratchet.tolk` trades and stakes into: the three DeDust addresses `flush()` sells into, plus §4.3.2's Tonstakers pool, tsTON vault, tsTON pool and **harvest destination** (D-151). Nothing else. The harvest destination is the one worth reading twice — this key can re-aim where accrued yield is sent, after thirty days in public view, while still touching no term of `T`. | | **What it cannot do** | Move TON or PRIMES; touch `T`, `S`, `pending` or any `owed` balance; change treasury routing; alter any compiled-in economic parameter; or execute its own proposal early. The contract's own header calls it "the ONLY privileged credential", and that is the finding this page confirms rather than the claim it repeats. | | **What bounds it in time** | `TIMELOCK_DELAY` = 30 days (`:70`). A queued change carries an `eta` and `execute` is **permissionless** — anyone may push a matured change, and nobody can push an unmatured one. | | **How a reader sees it** | The *pending action* is published: `getQueued()` returns `(hasQueued, fired, dedustNativeVault, dedustPool, primesJettonVault, eta, staking)` — every address the pending `execute` will write, the staked reserve's block (§4.3.2 / D-151) included as the same opaque cell `update_venue` will carry, decodable with `ratchet.tlb`'s `VenueStaking`. So any proposal is visible **in full** for its 30 days before it can land. The *holder* is not on a get-method — it is a storage field, readable off-chain from the account's data cell like any other, and it is address-determining, so it is also recoverable by re-deriving the contract address from its init data. **Stated rather than glossed:** there is no `getProposer()`. | | **What bounds it in test** | `PrimesVenueTimelock.spec.ts` — "rejects queue from a non-proposer sender", "rejects cancel/queue from a non-proposer, and cancel with nothing queued", "rejects an eta below the 30-day minimum delay", "rejects execute before eta matures", and "queue → wait 30 days → permissionless execute updates the ratchet venue addresses". | **Why this credential exists at all.** A DEX venue address is not a constant: a pool can be redeployed, and a `flush()` pointed at a dead vault is a `flush()` that cannot sell. The alternative to a timelocked proposer is a hardcoded address that can never be corrected, which fails closed in the worst possible way — silently, on the one path §5's floor depends on. --- ## 2. The quest voucher signer — `primes_voucher.tolk` | | | |---|---| | **What it is** | An Ed25519 public key, `Storage.signerPubkey` (`primes_voucher.tolk:112`), fixed at deploy. The backend holds the private half. | | **What it can do** | Sign a voucher naming a wallet and a **cumulative** PRIMES total (`:236`), and sign the one-shot program burn that sets the claim deadline (`:278`). | | **What it cannot do** | Move TON. Name itself as a beneficiary in any sense that pays a bearer — a voucher is signed **for a specific wallet** and nobody else can redeem it. Exceed the program's budget: the PRIMES come from `primes_bounty_vault.tolk`'s allow-list entry, and `remaining` is genesis-fixed there, so a forged-total voucher is refused **by the vault**, not by this contract. Pay twice: totals are a high-water mark, so a replay settles to zero. Strand an earned balance: the burn cannot set a deadline inside `BURN_MIN_GRACE`. | | **How a reader sees it** | `getSignerState()` returns `(signerPubkey, burnedAt, claimDeadline, minGrace, programTag)` — the key itself, on chain, so "the key that signs is the key baked in at deploy" is checkable by inspection and a holder can verify a voucher **off-chain before spending gas on it**. `isProgramClosed()` answers the time question on its own. | | **What bounds it in test** | `PrimesVoucher.spec.ts` — "a voucher signed by the wrong key is refused", "a voucher is NOT bearer paper: bob cannot redeem a voucher signed for alice", "REPLAY PAYS ZERO", "a signed total above the genesis-fixed budget is refused BY THE VAULT, and rolls back", "a program that is NOT allow-listed cannot pay, however valid the signature", "is authenticated by the KEY, not by an owner", and "CANNOT strand an earned balance: the grace floor is an on-chain assert". | **The honest shape of the risk.** A stolen signing key could mint vouchers up to the **genesis-fixed program budget** and no further, to addresses of the thief's choosing. That is a real loss and it is a bounded one: it cannot reach the ratchet, the ledger, any TON balance, or one PRIMES beyond the allow-list entry the vault was deployed with. --- ## 3. The invite-list signer — `primes_ledger.tolk` | | | |---|---| | **What it is** | An Ed25519 public key, `ConfigS.inviteSignerPubkey` (`primes_ledger.tolk:761`), set once by the genesis script (`handleSetInviteConfig`, `:2858-2870`, one-shot behind `inviteSet`). | | **What it can do** | Sign the invitee list attached to a mint (`:3551`). The signature covers `(n, inviteCount, addresses)` together, so a list is bound to the exact mint it was issued for. | | **What it cannot do** | Spend anything the minter has not already paid for. §4.1's invite line is `count × 0.025 TON`, **the count is chosen by the minter** and capped at `INVITE_MAX_COUNT`, and unspent slots fall to β rather than anywhere else. The key authorises *who* the minter's own five slots deploy generators to; it authorises no value of its own, cannot move TON, and cannot mint a number. | | **How a reader sees it** | `getInviteConfig()` returns `(generatorsCollectionAddr, inviteSignerPubkey, inviteSet)` — where generators deploy, whose key authorises a list, and whether the one-shot bootstrap has closed. | | **What bounds it in test** | `InviteGenerators.spec.ts` — "a list signed by the wrong key is refused (220) and mints nothing", "a list signed for a DIFFERENT n is refused — the mint is inside the hash", "a list whose ADDRESSES were swapped after signing is refused", "a count above the maximum is refused BEFORE the signature is even read (221)", and "an unbootstrapped ledger refuses every invited mint with 222, **not with a zero-key forgery**". | --- ## The fourth thing a reviewer will find, and why it is not on the list `genesisScriptAddr` appears in almost every contract and is compared against the sender on a `SetLedger` / `SetRegistrar` / `SetWallet` / `SetInviteConfig` / `SetRatchet` arm. It is a **bootstrap credential, and it is spent.** Each of those arms is one-shot behind its own flag (`walletSet`, `inviteSet`, `vaultSet`, `ratchetSet`, …) and refuses a second call with `already_processed` (104). The flags are published — `getVaultConfig()` returns `walletSet`, `getInviteConfig()` returns `inviteSet`, `getVault()` returns `vaultSet` — so "the bootstrap is closed" is a read, not a promise. One-shot-ness is tested per contract (for example `InviteGenerators.spec.ts`'s "SetLedger is one-shot" and "SetRegistrar is one-shot", and `PrimesBountyVault.spec.ts`'s `SetWallet` replay returning 104). It is named here rather than omitted precisely because a reviewer grepping for sender gates *will* find it, and an undisclosed credential looks worse than a spent one. --- --- ## 4. Governance — the succession plan, and how thin its electorate may legally be `GAUNTLET.md` X5. The three credentials above are what exists *today*. This section is about who controls the money in **year three**, because the answer is not a credential at all: it is `primes_treasury.tolk`'s governance (D-100), and the owner's stated succession plan is that holders propose and vote TON out of it. That mechanism is built, tested and surfaced at `/treasury`. **What was not published anywhere a holder reads is how thin the electorate may be.** The contract states it plainly in its own header and this section exists so that a reader does not have to find it there. **None of it is a defect** — it is D-100 point 8 as signed, and point 10 accepts the consequences in writing. It is also the single most important fact about who controls this money over a decade. ### The rule, verbatim from the contract | | | |---|---| | **Vote** | One day, **simple majority of votes cast, NO QUORUM** (`primes_treasury.tolk:162`). | | **The electorate** | Positions in the tiers at **30 days and above** — `VOTE_MULT` is `0` for instant/1-day/7-day, then `1` (30d), `3` (3mo), `10` (6mo). Weight is `amount × voteMultiplier`, a pure function of the position: no snapshot, no oracle, no `p_f` read (TR-7). A position in withdrawal cooldown does not vote. | | **So a lone voter can pass a proposal** | Stated in the contract's own words: *"with no quorum a single 30-day locker passes a proposal unless someone with more weight votes no"* (`:179`). | | **On day one the electorate is the operator alone** | Also the contract's own words (`:178`). The operator's LP both votes and mines — D-100 point 9 reversed an earlier exclusion — and **the operator's 6-month position at 10× is the stated defence** against a lone 30-day locker. | | **The other two defences** | A **25% cap** per proposal (`PROPOSAL_CAP_BPS = 2500`, of that asset's balance **at execute time**, not at open) and the **one-day visibility** window. A proposer must also hold at least 1% of the electorate's weight (`PROPOSE_MIN_BPS`), and may have one open proposal at a time. | | **A tie does not pass** | Simple majority means strictly more yes than no; `Treasury.spec.ts` — *"refuses an execute on a tie — the incumbent state keeps the money"*. | | **Execute is permissionless** | After `closes`, anyone may push it: the destination and amount were fixed when the proposal opened and no field on `execute` names either, so who calls it cannot matter. A bounced execute resets `executed` and is re-executable. | | **An EMPTY electorate** | No eligible weight means no proposal can open at all — `Treasury.spec.ts`'s *"refuses a proposal from a wallet with no eligible weight"* and *"refuses a proposal from below 1% of the electorate"*. The failure mode of an abandoned project is **paralysis, not capture**: the money stays where it is. | | **Published, not just written** | `getGov()` returns `(proposalSeq, eligibleWeight, PROPOSAL_CAP_BPS, PROPOSE_MIN_BPS, VOTE_DURATION)` — the live electorate weight and all three thresholds from the contract itself, which is what `/treasury` renders (§9.1). | ### What the treasury can and cannot reach This is the half that bounds everything above, and it is why a thin electorate is a bounded risk rather than an unbounded one. From the contract's own header (`primes_treasury.tolk:17-24`): > **Nothing in this file can move `T`, `S`, `pending`, `owed`, `k`, `K_CEIL` or the five-way > split, and nothing in it mints.** `pending` is the ratchet's and `owed` is the prime owners' — §3.4 puts each protected pool in its own account precisely so that a contract which *can* spend is distinguishable at a glance from one that cannot. §11 Q1 said the economic core is immutable; D-100 point 7 reopens it **narrowly and only here**, on the grounds that the treasury was never part of that core. Depositors' LP is not the treasury's to propose either: for LP, "the asset's balance" is `ownLp` and not the wallet, which is invariant **TR-5** (`Σ positions[].amount == LP wallet balance − ownLp`), enforced as an access rule rather than only asserted in a test. **There is no admin inbound at all** (D-100 point 7): no `update_config`, no owner field, no pause, no upgrade, no beneficiary change. The only one-shot message is `set_wallets`, the same circular-address bootstrap every jetton-custody contract here needs, and it is spent. ### What bounds it in test `contracts/tests/Treasury.spec.ts` — *"publishes the vote parameters it enforces"*, *"refuses a proposal from below 1% of the electorate"*, *"refuses a proposal from a wallet with no eligible weight at all"*, *"allows one open proposal per proposer, and a second once the first closes"*, *"tallies one wallet one vote at its full weight, and refuses a second"*, *"refuses a vote after the window closes"*, *"refuses an execute on a tie"*, and *"reports the six tiers from the contract, not from a config file"*. **This section is disclosure only.** It adds no quorum, changes no tier and re-opens no part of D-100. `DECISIONS.md` D-130 is where the calendar-vs-fraction question about the proposal cap is briefed, and it is a separate, open question. --- ## What this page does not do It does not add, remove or re-scope a credential, add a quorum, change a tier or re-open any part of D-100 — it is disclosure only. It also does not claim the three keys are **operationally** well held: where the private halves live, how they are rotated and what happens if one leaks are operations questions, and `threat-model.md` is where they belong. Kept honest by `PrivilegedCredentials.spec.ts`, which re-derives the credential set from the contract sources — every `isSignatureValid` call site and every non-peer sender comparison — and fails if one appears that this page does not name.