vesting
gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/vesting on gno.land mainnet
realmfactory-applicationvalue-distributionlive on mainnet
Release escrowed funds to a beneficiary on a dated schedule.
Each tranche unlocks at its own date and can be claimed once, so a vesting promise does not rely on anyone remembering it.
In detail curated
Dated-tranche GNOT disbursement: escrow released in discrete, individually dated tranches rather than by linear accrual, so a tranche is worth nothing before its due height and its full amount from that height on. Revocation can never reach a vested tranche. Composes duebook (exactly-once dated settlement) + coinio + feeledger + avl/v0 + sanitize/v0. Application-factory benchmark #5 on this chain's numbering of groups; Group 4, the fifth authorized mainnet use. This mainnet artifact is NOT a pure re-namespacing of the pearl-1 twin: it carries an authorized, declared divergence (an IsCurrent() pin on rejectStraySend, a beneficiary != self guard in Fund, and one corrected comment) recorded in mainnet/DIVERGENCE.json and verified as FAITHFUL WITH DECLARED DIVERGENCE by tools/namespace_tree.py. It ships INERT -- zero escrowed, zero claimable, zero fees, zero schedules, 0 bps fee.
Use it
A realm holds state. Reading it is a free query; changing it is a transaction you sign yourself.
Read it — no key, no account, no GNOT
gnokey query vm/qeval \
--data 'gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/vesting.EscrowTotal()' \
--remote https://rpc.gno.land:443EscrowTotal is read-only, so this is a query rather than a transaction: it costs nothing, signs nothing and needs no funded account. Every exported function is listed under the API below; swap the name and the arguments to read any of the others.
Change its state
Every state change is a transaction you sign with your own key — this site never asks for one and holds none. You do not have to hand-write the transaction: gnoweb generates the exact gnokey maketx call command for any of this realm's 12 crossing functions, with the argument names filled in.
The gno.land mainnet builder spends real GNOT. Start with the read above, and size -gas-fee from a simulation rather than from any example.
Read the known limitations further down this page first. They record what was not verified on chain, which is usually the part that matters.
Import it from another realm
import "gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/vesting"The path is the address: there is no package manager, nothing to add to a manifest and no version to pin, so the package you compile against is the one the chain runs. It differs per chain because the namespace in it is the deploying account's.
Deployments
gno.land mainnet gnoland-1
RenderedSourceCall builderState
chain-attested| Import path | gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/vesting |
|---|---|
| Namespace | g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39 |
| Deployed at height | 387,646 |
| Deploy transaction | c1c91a3892fb22a27fa99d7daec9b404a09bca522722ac6a80ed6c72c268fc31 look it up on the RPC |
| Deployer | g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39 |
| Gas used | 119,843,868 |
| Storage | not attested per package on this chain — see the limitations below |
| Files on chain | gnomod.toml vesting.gno vesting_test.gno |
| Deployed bytes | vesting.gno — 45,717 bytes |
| sha256 | 41b6e343d7e5dd6a0d663311aecd24e85009bf95e5cbdd5ba88372c3c53927da |
| Realm address | g1qzwg4z3k4ypxvzx67vyvk8f8ff3y80azv93w0h confirmed against the realm itself |
Do not take the hash above on trust. $download returns the bytes gnoland-1 is actually running; this command fetches them and prints their digest, which should equal the one in the table:
curl -sS 'https://gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/vesting$download&file=vesting.gno' | shasum -a 256Expected: 41b6e343d7e5dd6a0d663311aecd24e85009bf95e5cbdd5ba88372c3c53927da — 45,717 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.
API
chain-derived 32 exported functions.
Every function below deep-links to gnoweb's call builder, which generates a ready-to-run gnokey maketx call for it:
AcceptAdminBalanceOfEscrowTotalFeeBpsFeeOnFeesAccruedFundFundersWithOpenTranchesHeightHeldLiabilitiesMemoNumSchedulesOpenTrancheCountOpenTranchesOfFunderProposeAdminReleasableNowReleaseRenderRevokeRevokeAllSetFeeBpsSetFeeRecipientSurplusSweepDenomTrancheInfoTranchesOfUsersTotalVestedOfWithdrawWithdrawAllWithdrawFees
Overview
Realm vesting escrows native GNOT for a named beneficiary and releases it in DISCRETE, INDIVIDUALLY DATED TRANCHES — each payable exactly once, at or after its own due height.
DISCRETE, NOT LINEAR — read this first
Everywhere else in this ecosystem "vesting" means CONTINUOUS LINEAR ACCRUAL: a formula of the form amount * elapsed / duration, evaluated whenever someone asks. This realm does no such arithmetic. A funder states an explicit list of (delay, amount) pairs at funding time, and nothing accrues between them: a tranche is worth zero until its due height and its full amount from that height on.
That is a deliberate choice, not a simplification:
- It is the shape real schedules actually have — cliffs, quarterly tranches, milestone payments, a lump sum on a date.
- It has no `amount * elapsed` term, so the int64 overflow class that has already shipped a NEGATIVE vested amount in two independent ecosystem packages cannot occur here: that MULTIPLICATIVE class is designed out rather than guarded against. The additive sums that do exist are guarded, not designed out — Fund checks the tranche total and the escrow total with checkedAdd, each with its own panic, before any state changes. Both halves are true and neither replaces the other.
- It maps exactly onto duebook, whose exactly-once Claim is what makes a double release structurally impossible rather than merely checked.
There is no default curve to be surprised by: every tranche is stated explicitly at creation, or the schedule does not exist.
COMPOSITION
This realm owns only the schedule state machine. Everything else is delegated to primitives already audited and deployed:
duebook — the entire schedule: due heights, and exactly-once release coinio — coin custody: escrow-in, payout, surplus sweep feeledger — claimable balances and the fee pot sanitize — funder-supplied free text, before it reaches markdown
It is duebook's FIRST PRODUCTIVE CONSUMER. duebook_demo is the falsification experiment and holds no coins by design; this realm is the first to put value behind the exactly-once claim.
LIFECYCLE
Fund (EOA + -send) : one transaction creates the schedule, escrows the exact total, and registers every tranche in duebook. There is no draft state and no half-funded schedule. Release (ANYONE) : once a tranche is due, anyone may release it. The value is credited to the RECORDED BENEFICIARY, never to the caller. Revoke (funder only) : returns a NOT-YET-DUE tranche to the funder, and only if the schedule was created revocable. A due tranche can never be revoked. Withdraw / WithdrawAll : pays out the caller's own claimable balance.
WHY NOTHING CAN BE TRAPPED, AND WHY THERE IS NO EXPIRY
Tranches are scheduled with duebook's ttl == 0, so they NEVER expire. Vested value cannot be lost by a beneficiary who is slow, offline, or gone. The sibling realms in this portfolio use a permissionless expiry valve to guarantee escrow is never trapped; here the valve is different and stronger: Release is PERMISSIONLESS, so every tranche becomes claimable-by-anyone the moment it is due, and the schedule drains itself without depending on any particular actor staying alive. Combined with "a due tranche can never be revoked", every ugnot in escrow has exactly one reachable destination and a height after which nobody can block it.
VESTED MEANS IRREVOCABLE, WITH NO RACE
Revoke refuses any tranche whose due height has arrived, even though duebook's Cancel would happily consume it. THAT CHECK IS THIS REALM'S, not duebook's. Without it a funder could watch for the due height and front-run the beneficiary's Release to claw back value already earned. Likewise, `revocable` is fixed at funding and has no setter: an irrevocable schedule cannot be made revocable later, which is the only thing that makes the promise worth anything.
AUTHORIZATION
Every identity is derived from the crossing entrypoint's cur.Previous().Address(). No function takes a caller identity as a parameter, so acting as another account is impossible by construction. Release is permissionless *because* it pays the recorded beneficiary: the caller's identity is not used to route value, so there is nothing for a stranger to gain.
HOW IT WIRES DUEBOOK CORRECTLY (duebook's five-point consumer contract)
- THE CLOCK IS runtime.ChainHeight(), never a call argument. A realm that lets a caller choose `now` has no delay at all.
- HEIGHTS ARE THE ONLY UNIT. No timestamp appears anywhere.
- THE *Book IS NEVER EXPORTED. It is an unexported package var and is never returned across a realm boundary; handing it out would hand out the right to schedule, cancel and claim.
- THE ACTOR IS AUTHORIZED from cur.Previous().Address(); the duebook owner recorded at Schedule is the funder, and duebook checks Cancel against that recorded value, never against an argument.
- THE EFFECT HAPPENS AFTER A SUCCESSFUL Claim, IN THE SAME TRANSACTION.
The deferral PAYLOAD is a human-readable label for events and rendering. THIS REALM NEVER PARSES IT. Every authoritative fact about a tranche — its amount, its schedule, its beneficiary — comes from this realm's own `tranches` tree, keyed by the deferral ID that duebook guarantees is never reused.
MONETARY INVARIANT (conservation)
Let H be ugnot held at this realm's address, E = escrowTotal (the sum of amounts over tranches still pending), U the ledger's claimable balances, F the fee pot, and S >= 0 out-of-band surplus:
H == E + U + F + S
Fund raises H and E together (coinio.Receive is the receipt-guaranteed shape). Release moves one tranche amount from E into U+F, with feeledger guaranteeing credited + fee == amount. Revoke moves a tranche amount from E into U, fee-free. Withdrawals debit the ledger BEFORE coinio.Payout moves the identical amount out (checks-effects-interactions). Any panic aborts the whole transaction. This realm never issues or removes coins. Surplus is recoverable only via SweepDenom, which reserves Liabilities().
ONLY GNOT
Fund rejects any envelope that is not exactly one positive ugnot coin. Foreign denominations sit in surplus and are sweepable.
Imports
chainchain/runtimechain/runtime/unsafegno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coiniogno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/duebookgno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledgergno.land/p/nt/avl/v0gno.land/p/nt/markdown/sanitize/v0strconvstrings
Constants and variables
Tranche status values. A tranche is created pending and reaches exactly one terminal state; both terminals are frozen.
const (
StatusPending = "pending"
StatusReleased = "released"
StatusRevoked = "revoked"
)
Bounds. All are compile-time constants: none is admin-mutable, so a schedule's shape cannot be changed under its funder or beneficiary.
const (
// MinDelayBlocks is the floor on how far ahead the FIRST tranche may
// fall. One block is enough to prove the delay is enforced while
// keeping the realm exercisable on a live chain.
MinDelayBlocks = int64(1)
// MaxDelayBlocks caps the furthest tranche at roughly 10.8 years at
// pearl-1's ~3.42s blocks. It also bounds now+delay far inside int64.
MaxDelayBlocks = int64(100_000_000)
// MaxTranches bounds one schedule. It bounds the Fund transaction's
// work, the RevokeAll loop, and the schedule detail render.
MaxTranches = 64
// MaxOpenTranches caps pending tranches across EVERY schedule at once
// — it is a whole-realm limit, not a per-schedule one, because it is
// duebook's open-deferral cap. Fund fails when a new schedule would
// cross it. Must be <= duebook.MaxOpenLimit.
MaxOpenTranches = 5000
// MaxOpenPerFunder bounds how many tranches ONE funder may hold
// pending at once, so that MaxOpenTranches cannot be consumed by a
// single account.
//
// Without this bound the whole-realm cap is a denial-of-service
// surface that costs nothing to use: 79 Fund calls of 64 one-ugnot
// tranches fill the realm, Fund then aborts for everybody, and the
// attacker recovers 100% of the escrow fee-free with RevokeAll. Two
// deliberate choices elsewhere remove every recovery path — ttl is
// always 0, so duebook's permissionless Expire valve never opens, and
// MaxOpenTranches is a compile-time constant with no admin setter. The
// denial would therefore be total and permanent, which is out of all
// proportion to its cost.
//
// 256 leaves one funder four full MaxTranches schedules open at once —
// generous for the intended use — while exhausting the realm now takes
// at least ceil(5000/256) = 20 separately funded accounts.
//
// That is a SYBIL BOUND and nothing more, so the residual is quantified
// here rather than left to be inferred from the 20: the cap counts
// TRANCHES, never value. parseSpec accepts amount == 1, so 256 pending
// tranches cost 256ugnot and all 20 accounts together escrow about
// 5000ugnot — every bit of it recoverable fee-free, since revokeOne
// refunds at 0 bps. The real cost of the realm-wide denial is therefore
// ~80 Fund transactions' gas and 20 funded addresses, NOT capital.
// Anyone sizing the residual risk from the "20 accounts" figure alone
// will size it wrong by orders of magnitude.
//
// Bounding the attack by VALUE instead would take a per-tranche minimum
// amount. That is a change to what the application is — it would price
// small legitimate tranches out — so it is a reviewer's decision, not a
// routine fix, and it is deliberately not made here.
MaxOpenPerFunder = 256
// MaxTitleLen bounds a schedule title. Titles are charset-restricted
// so they are list-safe in Render without escaping.
MaxTitleLen = 80
// MaxMemoLen bounds the free-text memo. It is a BYTE length, not a
// rune count, because its job is to bound storage.
MaxMemoLen = 1000
// MaxSpecLen bounds the tranche specification string, so parsing cost
// is bounded before any parsing begins.
MaxSpecLen = 1400
// MaxListed caps how many entries a single view or render returns.
MaxListed = 50
)
Denom is the only asset this realm escrows.
const Denom = "ugnot"
MaxFeeBps is the hard protocol-fee cap: 1000 bps = 10%.
const MaxFeeBps = int64(1000)
Functions
AcceptAdmin
func AcceptAdmin(cur realm)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asAcceptAdmin(cross(cur), ...).
AcceptAdmin completes a handover. Only the proposed account may call it.
BalanceOf
func BalanceOf(addr address) int64
BalanceOf returns addr's claimable balance (released tranches for a beneficiary, revocations for a funder).
EscrowTotal
func EscrowTotal() int64
EscrowTotal returns the escrow held by pending tranches (the E term).
FeeBps
func FeeBps() int64
FeeBps returns the fee that will be snapshotted into newly funded schedules. Existing schedules keep their own snapshot.
FeeOn
func FeeOn(amount int64) (fee, credited int64)
FeeOn previews the fee and net credit for a tranche of amount at the CURRENT FeeBps.
FeesAccrued
func FeesAccrued() int64
FeesAccrued returns the fee pot (the F term).
Fund
func Fund(cur realm, beneficiary address, title, memo, spec string, revocable bool, maxFeeBps int64) int64
Crossing function. Callable from a transaction via
MsgCall, and from another realm asFund(cross(cur), ...).
Fund creates a vesting schedule in a single transaction: it escrows the attached GNOT, registers every tranche with duebook, and returns the new schedule id. Only direct EOA calls with -send are accepted.
spec lists the tranches as "delay:amount" pairs separated by commas, for example "100:500,200:500,300:1000". Delays are in BLOCKS FROM NOW and must be STRICTLY INCREASING — two payments at the same height are one tranche with the amounts added. Every amount must be positive, and the amounts must sum to EXACTLY the attached send: this realm will not silently absorb a surplus or create an underfunded schedule.
revocable decides, permanently, whether the funder may reclaim not-yet-due tranches. There is no setter; an irrevocable schedule stays irrevocable.
maxFeeBps is the ceiling the caller signs for. Funding is rejected if the live fee exceeds it, which closes the race where an admin sequences a fee raise ahead of this transaction. Pass MaxFeeBps to accept any legal fee.
FundersWithOpenTranches
func FundersWithOpenTranches() int
FundersWithOpenTranches returns how many distinct funders hold at least one pending tranche. It is the width of the per-funder cap: exhausting MaxOpenTranches requires this to reach ceil(MaxOpenTranches/MaxOpenPerFunder).
Height
func Height() int64
Height returns the current chain height. Delays in Fund's spec are relative to the height at which Fund executes, so this is an aid for composing a spec, not an input to one.
Held
func Held() int64
Held returns the ugnot actually held at this realm's address (H).
Liabilities
func Liabilities() int64
Liabilities returns everything this realm owes: EscrowTotal + UsersTotal + FeesAccrued.
Memo
func Memo(id int64) string
Memo returns a schedule's raw memo text.
NumSchedules
func NumSchedules() int64
NumSchedules returns how many schedules have ever been funded.
OpenTrancheCount
func OpenTrancheCount() int
OpenTrancheCount returns how many tranches are pending across the whole realm, against the MaxOpenTranches cap.
OpenTranchesOfFunder
func OpenTranchesOfFunder(funder address) int64
OpenTranchesOfFunder returns how many tranches funder currently holds pending, against the MaxOpenPerFunder cap. A funder can read this before funding to see the headroom they have left.
ProposeAdmin
func ProposeAdmin(cur realm, next address)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asProposeAdmin(cross(cur), ...).
ProposeAdmin nominates a successor admin. The handover takes effect only when that account calls AcceptAdmin, so a typo cannot strand the role. Admin only; the zero address clears a pending proposal.
ReleasableNow
func ReleasableNow() string
ReleasableNow lists tranche ids that are due and still pending, up to MaxListed, newline-separated. Anyone may Release each of them.
Release
func Release(cur realm, trancheID int64) string
Crossing function. Callable from a transaction via
MsgCall, and from another realm asRelease(cross(cur), ...).
Release pays out a due tranche. ANYONE may call it: the value is credited to the schedule's RECORDED BENEFICIARY, never to the caller, so a stranger calling this gains nothing and the beneficiary needs no liveness of their own. The protocol fee snapshotted at funding is charged here.
Aborts if the tranche does not exist, is not due yet, or has already been released or revoked — the last of those is duebook's guarantee, not a check written here.
Render
func Render(path string) string
Render shows the realm at "" and a schedule detail at "<id>". Free text (titles are charset-restricted; memos are not) passes through the ecosystem sanitizer before it reaches markdown.
Revoke
func Revoke(cur realm, trancheID int64) string
Crossing function. Callable from a transaction via
MsgCall, and from another realm asRevoke(cross(cur), ...).
Revoke returns a NOT-YET-DUE tranche to the funder's claimable balance, fee-free. Only the funder may call it, and only on a schedule created revocable.
A tranche whose due height has arrived can never be revoked, even though duebook's Cancel would consume it: that refusal is this realm's, and it is what stops a funder front-running the beneficiary's Release to claw back value already vested.
RevokeAll
func RevokeAll(cur realm, scheduleID int64) string
Crossing function. Callable from a transaction via
MsgCall, and from another realm asRevokeAll(cross(cur), ...).
RevokeAll returns every not-yet-due tranche of a schedule to the funder's claimable balance in one transaction, and returns how many were revoked. Already-due and already-settled tranches are skipped, not an error. Funder only, revocable schedules only. Aborts if nothing was revocable, so a no-op cannot be mistaken for success.
SetFeeBps
func SetFeeBps(cur realm, bps int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSetFeeBps(cross(cur), ...).
SetFeeBps sets the protocol fee snapshotted into FUTURE schedules at funding. Existing schedules keep the fee they were funded under, so an in-flight schedule's economics cannot be changed. Admin only.
SetFeeRecipient
func SetFeeRecipient(cur realm, next address)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSetFeeRecipient(cross(cur), ...).
SetFeeRecipient re-points the fee/surplus role. The pot accrued under the OUTGOING recipient is settled to that recipient's own claimable balance first, so the successor inherits the role and not the earnings. Admin only; the zero address is rejected.
Surplus
func Surplus() int64
Surplus returns Held() - Liabilities() (the S term).
SweepDenom
func SweepDenom(cur realm, denom string) string
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSweepDenom(cross(cur), ...).
SweepDenom sends the surplus of a single denomination to the fee recipient. For ugnot only the excess over Liabilities() moves — escrowed tranches and claimable balances are untouchable by construction. Other denominations move wholly. Only the fee recipient may call it.
TrancheInfo
func TrancheInfo(trancheID int64) (scheduleID, seq, amount, dueAt int64, status string, settledAt int64)
TrancheInfo returns one tranche's fields by value. It answers for settled tranches too, after duebook has consumed the deferral.
TranchesOf
func TranchesOf(id int64) string
TranchesOf lists a schedule's tranches, newline-separated, one per line as "id seq amount dueAt status".
UsersTotal
func UsersTotal() int64
UsersTotal returns the sum of all claimable balances (the U term).
VestedOf
func VestedOf(id int64) (vested, unvested int64)
VestedOf returns, for a schedule, the amount whose due height has arrived and which is therefore no longer revocable — whether or not it has been released yet — alongside the amount still revocable by the funder.
Withdraw
func Withdraw(cur realm, amount int64) string
Crossing function. Callable from a transaction via
MsgCall, and from another realm asWithdraw(cross(cur), ...).
Withdraw sends amount ugnot of the caller's claimable balance (released tranches for a beneficiary, revocations for a funder) to the caller.
WithdrawAll
func WithdrawAll(cur realm) string
Crossing function. Callable from a transaction via
MsgCall, and from another realm asWithdrawAll(cross(cur), ...).
WithdrawAll sends the caller's entire claimable balance to the caller. Aborts if there is nothing to withdraw.
WithdrawFees
func WithdrawFees(cur realm) string
Crossing function. Callable from a transaction via
MsgCall, and from another realm asWithdrawFees(cross(cur), ...).
WithdrawFees sends the accrued fee pot to the fee recipient. Only the fee recipient may call it.
Doc text is reproduced as vm/qdoc returns it. The node markdown-escapes doc comments, so a bracket or angle bracket may carry a backslash the committed source does not have. The source itself is at source and in this repository.
Dependencies
chain-attested| Imports | chain, chain/runtime, chain/runtime/unsafe, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coinio, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/duebook, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledger, gno.land/p/nt/avl/v0, gno.land/p/nt/markdown/sanitize/v0, strconv, strings |
|---|---|
| First-party dependencies | gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coinio, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/duebook, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledger |
Known limitations
Recorded by the people who built and deployed it. This list is deliberately not empty where honesty costs something.
curated- source.divergence is null and means what C6 means by it: the committed source in this repository equals the bytes this chain stores. It is NOT a statement about the pearl-1 twin. This package DOES diverge from pearl-1 deliberately, and that axis lives in mainnet/DIVERGENCE.json, not in this field -- the two uses of the word 'divergence' collide, and this is the SECOND package where the collision is load-bearing (docs/MAINNET_READINESS.md Amendment AB, MINOR-3). Reading source.divergence null as 'identical to pearl' would be exactly wrong: the two chains run materially different vesting bytes.
- The declared divergence waives two of namespace_tree.py's nine proof obligations (byte equality under reverse substitution, and byte-length preservation) for the two .gno files, and only those two. It replaces them with base_sha256/base_bytes, result_sha256/result_bytes, diff_sha256 and a pin on the exported symbol set. Those three digests are a tamper-evidence seal over a reviewed artifact, not a bound on what the artifact may contain: a declared divergence may change anything inside a function body and only reading `python3 tools/namespace_tree.py diff` will tell you it did. Measured, the delta is +1,265 source bytes of which 1,107 are comment text and the executable change is two if-guards and their panic strings.
- No state-changing call was made against this realm on gnoland-1, and it is value-bearing. The live evidence is byte custody plus a successful vm/qrender on the empty state showing conservation H == E + U + F + S exact at 0 == 0+0+0+0 and both privileged roles initialised to the deployer. Every state transition rests on the committed test suite (23/23 under the mainnet pin), the two mutation results, the audit record, and the pearl-1 twin's live evidence -- which correlates but does not transfer, because the pearl artifact lacks both mainnet guards ([an unpublished record], Live verification).
- The 23/23 test result was RECOVERED FROM THE SESSION TRANSCRIPT, not re-executed on demand: the pinned mainnet gno test invocation was denied by the harness classifier seven times and the allow-listed pearl binary was deliberately not substituted (measurably a different build). Only 11 of the 23 '--- PASS' lines survive the original tail -35, and no full 23-line log of this artifact exists on disk. The count of 23 comes from the mutation harness's tally in the same run. Custody of the tested bytes was closed by exclusion over the intervening tool calls.
- The IsCurrent() pin added by the declared divergence has NO test coverage and cannot have any without fault injection: it is unreachable because rejectStraySend is unexported and all 11 call sites forward the crossing entrypoint's own live cur. Deleting it was mutation-tested and leaves the suite green. That is declared in mainnet/DIVERGENCE.json rather than hidden behind the passing suite. The beneficiary != self guard, by contrast, was mutation-tested RED.
- Findings that ship unresolved, immutably, on this chain as well as on pearl: V1 (rotating a lost fee-recipient key settles the accrued pot into the unreachable address, so SetFeeRecipient destroys the pot instead of rescuing it); SetFeeRecipient(self), which strands every fee accrued in an admin-only window in a balance nobody can claim -- measured, BalanceOf(self) 0 -> 100ugnot on re-pointing -- and is NOT the same residual as the harmless ProposeAdmin(self); Y1/S1, the realm-wide capacity squat (~80 txs across 20 funded addresses fill MaxOpenTranches, ttl is always 0 so duebook's permissionless Expire valve never opens, MaxDelayBlocks permits a ~10.8-year hold); and V3/V4/V6/V7/V8. Each was declined as a scope change under CLAUDE.md section 8 rather than fixed implicitly, on the user's explicit narrow-scope decision. MaxOpenPerFunder counts tranches, never value.
- storage_bytes and storage_deposit are null because gnoland-1 emits no /tm.StorageDepositEvent on THIS tx -- its Events field is literally null, which is the precise and tx-scoped form of the claim; the chain does emit one on MsgCall (measured 2026-10-17, AL62), so do not read this as a chain-wide property. The deposit actually charged was 5,998,500 ugnot (59,985 stored bytes), measured by differencing auth/accounts across heights 387650 and 387651, and the gas fee of 250,000 was taken separately at 387646: total 6,248,500 ugnot. The deposit lands FIVE BLOCKS after the deploy, so a single-height balance difference would have under-reported the cost by 96%. Recorded in [an unpublished record], Cost; not chain-attested per package by this manifest. Note also that the on-chain gnomod.toml records max_deposit = 12000000ugnot, which is the CEILING requested and not the amount charged.
- The deployed file set includes vesting_test.gno. The pearl-1 deployment of the same realm carried only the source and gnomod.toml, so the mainnet package is three files where pearl's is two -- the test file is stored on chain and paid for in the deposit. It declares no crossing function and so adds no callable surface, but it is part of the immutable package.
- Closure: Gate A and Gate B both EQUIVALENT on the deploy axis over 5 compared dependencies (three first-party) with the identical digest bc0251488c70721715769e9f2b8aa93017c2373933632f6e2d7765ce094e5a32, so this is a real closure and not a vacuous one, and the tree did not drift between the gates; the --include-tests axis compared 9 and was also EQUIVALENT. Residual in every case: the 17 stdlib packages on the deploy axis (22 with tests) execute from the node binary and are covered by no closure check here. No mainnet-fixtured self-test of verify_depclosure.py exists, so these verdicts come from a tool whose own 24/0 suite is pearl-fixtured.
- compose_deploy_tx.py --cross-check-gnokey was NOT run, as on Groups 0-3: the pinned gnokey home carries no keybase entry and creating one, even pubkey-only, is declined under the standing stay-out-of-key-storage instruction. Wire format is asserted by compose_deploy_tx.py alone. Mitigation is thirteen transactions composed by this tool and accepted by gnoland-1, each byte-matched afterwards with vm/qfile -- strong evidence about the composer and no evidence about the one path a cross-check would exercise.
Source
| Source file | mainnet/r/vesting/vesting.gno at commit c2e10d17ffbf in the project repository (not public — the digest command above is the check that needs no repository) |
|---|---|
| Matches the deployed bytes | yes — byte-identical |