Cosmic Bull

budgets

gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/budgets on gno.land mainnet
gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/budgets on onyx-1 testnet

realmfactory-applicationdelegated-spendinglive on mainnetlive on onyx-1

Let delegates spend from a shared pot, within limits you set.

A delegate draws what it needs without asking again, while the owner keeps a spending cap and the power to revoke.

In detail curated

gno.land mainnet

Delegated spending pools over p/spendbook: owners fund pools and grant per-window allowances; delegates Draw against them; pull-claims via feeledger. Spendbook's first consumer on this chain -- the commit-proof for the live p/spendbook bytes. Deployed from the N1-fixed, divergence-declared tree (the grant page derives its displayed terms exactly as spendbook's at() enforces them). Carries the operator-accepted Y2/Y3 inheritance measured in-realm.

onyx-1 testnet

Delegated spending of real GNOT. An owner funds a pool and grants a named delegate a standing, rate-limited, revocable authority over it; the delegate draws within that bound with no further approval and claims the coins in a separate transaction. It is deliberately the FIRST CONSUMER of spendbook on any chain -- the whole authority model (anchored windows, lifetime caps, expiry, one-sided amendments, revocation) is delegated to the primitive and none of it is reimplemented here. A budget is AUTHORITY, not a reservation: three grants of 100 against a pool of 100 are all three valid and the first delegate to draw gets the money, so the realm never promises what it does not hold -- Draw refuses unless the amount fits THAT GRANT OWNER'S OWN pool, a per-owner bound strictly stronger than a realm-wide solvency check. Draw is credit-then-pull: it moves gross out of the pool and into the ledger and does not touch Held() at all, so a draw has no payout that can fail and a delegate may draw repeatedly and Claim once. Credit already drawn survives Revoke, expiry and Reap, because it was earned under a live authority. There is no admin: the 1% draw fee, the FeeCollector, every bound and every cap are compile-time constants and the deployer retains no privilege. The monetary invariant H == P + L + S holds at every transition.

One description per chain, because this application is deployed on more than one chain and each account was written for its own deployment. The programs differ only where the chain's namespace appears in the source — proved, not asserted, under Deployments below — so where the accounts differ they differ in emphasis and in which chain's history they describe, not in what the code does.

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

on gno.land mainnet

gnokey query vm/qeval \
  --data 'gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/budgets.FeesAccrued()' \
  --remote https://rpc.gno.land:443

on onyx-1 testnet

gnokey query vm/qeval \
  --data 'gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/budgets.FeesAccrued()' \
  --remote https://rpc.onyx.testnets.gno.land:443

FeesAccrued 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 10 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

on gno.land mainnet

import "gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/budgets"

on onyx-1 testnet

import "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/budgets"

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

Deployed on 2 chains, and these are NOT the same program. The difference is deliberate, authorized and declared in the repository (mainnet/DIVERGENCE.json): the mainnet artifact carries corrections the testnet deployment cannot receive, because a deployed realm's bytes are immutable and the testnet copy is frozen where it stands. Read each block below as its own program. The digests differ for a reason that is not the import path, the exported function set is identical on both — the build checks that and refuses to share a page otherwise — and everything below the exported signatures may differ between them.

gno.land mainnet gnoland-1

RenderedSourceCall builderState

chain-attested
Import pathgno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/budgets
Namespaceg1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39
Deployed at height1,315,542
Deploy transactionea40fd55692e529e5e8ce74a6f9ff0b37443785d7fb810df7182618311aa4b92 look it up on the RPC
Deployerg1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39
Gas used127,272,256
Storagenot attested per package on this chain — see the limitations below
Files on chainbudgets.gno budgets_test.gno gnomod.toml
Deployed bytesbudgets.gno — 49,044 bytes
sha25612061174f06e574b3baae956796d7dabaf4642fe53104e060101b1a4bd30f660
Realm addressg1eqg4vzs64n3g00q8vgfwnmtunfh3ph242yn3rh 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/budgets$download&file=budgets.gno' | shasum -a 256

Expected: 12061174f06e574b3baae956796d7dabaf4642fe53104e060101b1a4bd30f660 — 49,044 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.

onyx-1 testnet onyx-1

RenderedSourceCall builderState

chain-attested
Import pathgno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/budgets
Namespaceg1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3
Deployed at height186,167
Deploy transaction85f225b96804f4959c99a10313135261380c86b9811812c809944cc55411f377 look it up on the RPC
Deployerg1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3
Gas used63,133,563
Storagenot attested per package on this chain — see the limitations below
Files on chaingnomod.toml budgets.gno
Deployed bytesbudgets.gno — 47,454 bytes
sha25631a328297c3a26f7de8f2075d32cf14cef400f9971a493bcb1d32055a659b7b4
Realm addressg17vdd87a9s9nf3jfycugt8qxndc7knx2yd4whmm confirmed against the realm itself

Do not take the hash above on trust. $download returns the bytes onyx-1 is actually running; this command fetches them and prints their digest, which should equal the one in the table:

curl -sS 'https://onyx.testnets.gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/budgets$download&file=budgets.gno' | shasum -a 256

Expected: 31a328297c3a26f7de8f2075d32cf14cef400f9971a493bcb1d32055a659b7b4 — 47,454 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.

API

chain-derived 30 exported functions.

Read from vm/qdoc on gnoland-1. It describes the onyx-1 testnet deployment as well: they are NOT the same program — the difference is a declared, authorized divergence, not a namespace — and the only thing that carries across is the exported surface itself. The build requires the two exported symbol sets to be identical and refuses to share a page otherwise, so the signatures, types and doc comments below are the API on both. What those functions DO may differ, and on this package it does: see the note under Deployments. The API is the only thing that carries across; every provenance fact above is per-chain.

Every function below deep-links to gnoweb's call builder, which generates a ready-to-run gnokey maketx call for it — against the chain you pick. The two are separate deployments with separate state, and a call to one does nothing to the other:

gno.land mainnet gnoland-1

AmendClaimCreditOfDrawDrawableFeesAccruedFundGrantGrantLabelGrantUsageGrantsOfHeightHeldIsExpiredLiabilitiesLiveGrantsNumGrantsPendingTermsPoolOfReapRemainingRenderRevokeSurplusSweepSurplusTotalPooledUnclaimedCreditWithdrawFeesWithdrawPoolWithdrawPoolAll

onyx-1 testnet onyx-1

AmendClaimCreditOfDrawDrawableFeesAccruedFundGrantGrantLabelGrantUsageGrantsOfHeightHeldIsExpiredLiabilitiesLiveGrantsNumGrantsPendingTermsPoolOfReapRemainingRenderRevokeSurplusSweepSurplusTotalPooledUnclaimedCreditWithdrawFeesWithdrawPoolWithdrawPoolAll

Overview

Realm budgets is a delegated-budget treasury in GNOT: an owner funds a pool, grants named delegates a standing, rate-limited, revocable authority to spend from it, and each delegate then draws within that authority with no further approval from anyone.

WHY THIS EXISTS (see DISCOVERY.md): every disbursement path on either live chain is one of three shapes — a fresh group decision per spend (this portfolio's treasury_board, gno.land's r/gov/dao/treasury, the mainnet bubbletreasury), a fixed schedule (vesting), or a pull against an already-settled entitlement (fee_split, revshare, crowdfund, subscriptions). None of them lets one party spend another's money at its own discretion within a bound. That is the gap, and bubbletreasury — a real mainnet treasury that hand-rolled a sliding spend window inside the realm, with every spend still a token-holder proposal — is the evidence someone wanted it.

It is also, deliberately, the FIRST CONSUMER of spendbook on any chain. A primitive with no consumer has two properties unproven no matter how many unit tests pass: that its API is usable from a realm, and that its state survives across transactions. Only a realm can answer either, and this one is the answer.

COMPOSITION: spending authority is delegated entirely to spendbook (anchored windows, lifetime caps, expiry, one-sided amendments, revocation), coin movement to coinio, per-delegate credit and the fee pot to feeledger, ordered storage to p/nt/avl/v0, and free-text render output to p/nt/markdown/sanitize/v0. This realm owns only the pool accounting, the grant records, and the solvency rule.

What a budget is, and what it is not

A budget is AUTHORITY, not a reservation. Three grants of 100 against a pool of 100 are all three valid, and the first delegate to draw gets the money. This is not a defect to be fixed by reserving funds: a reservation would make an owner unable to grant a standing allowance without pre-committing the capital, which is the whole convenience being bought. What the realm guarantees instead is that it never PROMISES what it does not HOLD:

Draw refuses unless amount <= the owner's own unspent pool.

So a delegate is told "no" at draw time rather than credited with something unclaimable. The pool is the solvency bound and it is per-owner, which is strictly stronger than a realm-wide check: one owner's grants can never reach another owner's funds.

Credit, then pull

Draw moves nothing. It consumes the allowance, debits the owner's pool, and credits the delegate in the ledger; the delegate then signs a separate Claim to receive the coins. This is not ritual. If Draw paid out directly, a failing payout inside the same transaction would leave a consumed allowance with no money delivered, or force the allowance consumption to happen after an effect — and spendbook's own consumer contract item 3 says state first, effect second, for exactly that reason. Splitting it also means a delegate can draw repeatedly and claim once, which is cheaper than claiming per draw.

LIFECYCLE

Fund (owner, +send) : adds real coins to the caller's own pool. Anyone may fund; funding makes you an owner. Minimum MinFunding. WithdrawPool (owner) : pulls unspent pool back out, any amount, any time. See "the owner can always leave" below. WithdrawPoolAll (owner) : the whole pool. Grant (owner) : opens one spendbook allowance for one delegate and returns the grant id. The draw fee is snapshotted here. Amend (owner) : changes the grant's terms. Tighter-or- equal applies now; looser waits for the next window boundary. PeriodLength is NOT a parameter — spendbook makes it immutable and this realm makes that structural. Revoke (owner) : immediate, and the id is never reused. Draw (DELEGATE) : consumes authority, debits the pool, credits the delegate net of the fee. Claim (DELEGATE) : pays the delegate's whole credit out. Reap (ANYONE) : removes an EXPIRED grant, freeing its slot. Permissionless because an expired allowance can authorize nothing, so removing it takes nothing from anyone. WithdrawFees (ANYONE) : pays the fee pot to the compile-time FeeCollector. Fixed destination, so there is nothing to steer. SweepSurplus (ANYONE) : out-of-band coins to the FeeCollector, never touching tracked liabilities.

The owner can always leave, and that is the design

WithdrawPool has no lock, no notice period, and no check against outstanding grants. An owner can empty the pool under a live grant and the delegate's next Draw will fail on solvency. This is stated plainly rather than buried: the money is the owner's, Revoke is the clean way to end a relationship, and a realm that could trap an owner's capital behind a grant they regret would be a worse realm. A delegate who needs a guarantee of funds does not want a budget — they want escrow, which is a different artifact.

What the owner CANNOT do is undo work already authorized: credit already drawn into the ledger is the delegate's, and Revoke does not claw it back. Revoking a grant ends future authority only.

THE FEE, precisely

feeBps is snapshotted into the grant at Grant time from the compile-time DrawFeeBps, so a delegate's terms are fixed when the grant is created and visible in GrantInfo from that moment. The fee is charged ONCE per draw, on the gross amount, with feeledger's floor rounding (rounding favors the delegate). The owner's pool is debited the GROSS amount and the delegate is credited the net — the fee is paid by the owner out of the budget, not taken out of the delegate's authority, so a grant of PerPeriod means PerPeriod of the owner's money leaves the pool per window, which is the figure an owner actually cares about bounding. The ledger is constructed with the compile-time MaxFeeBps cap, so a fee above the cap is refused by the primitive, not by a check in this file. There is no fee on funding, on withdrawing an unspent pool, or on claiming.

THERE IS NO ADMIN

The fee rate, the fee collector, every bound and every cap are compile-time constants. No address can change terms, pause the realm, touch a pool it did not fund, or move a grant it did not create. The deployer has no privilege of any kind after deployment.

MONETARY INVARIANT (conservation)

Let H be the ugnot held at this realm's address, P the sum of all owner pools (tracked O(1) as totalPooled), L the ledger's liabilities (unclaimed delegate credit plus the fee pot), and S >= 0 the out-of-band surplus:

H == P + L + S

Every transition moves value between exactly two terms inside one transaction. Fund raises H and P together (coinio.Receive is the receipt-guaranteed shape). WithdrawPool debits P before the identical amount leaves H. Draw moves gross out of P and into L, split into the delegate's credit and the fee — H does not move at all, which is why Draw has no payout to fail. Claim and WithdrawFees debit L before their payout. Any panic aborts the whole transaction; this realm never issues or removes coins.

ONLY GNOT IS ACCEPTED

Fund rejects any envelope that is not exactly one positive ugnot coin (coinio.Receive). Every other entrypoint rejects attached coins outright rather than converting them into sweepable surplus.

AUTHORIZATION

Every identity is derived from the crossing entrypoint's cur.Previous().Address(). No function takes a caller identity as a parameter, so funding another owner's pool, drawing another delegate's budget, claiming another delegate's credit, or amending a grant you do not own is impossible by construction. This is spendbook consumer contract item 2, closed structurally.

The clock is runtime.ChainHeight(), never an argument. That is spendbook consumer contract item 1: a realm that let a caller choose `now` would have handed the delegate the power to roll the window at will, defeating every cap in the primitive.

STORAGE

A pool entry is paid for by the owner's own funding transaction; a grant record and its allowance by the owner's Grant. Revoke and Reap both remove the grant record and the allowance in the same call, and the chain refunds the freed storage deposit to the transaction that frees it — which is the standing incentive to call Reap on someone else's expired grant.

Imports

Constants and variables

Denom is the only asset this realm holds.

const Denom = "ugnot"

DrawFeeBps is the fee actually snapshotted into every grant: 100 bps = 1%, charged once per draw on the gross amount, floor-rounded in the delegate's favor. The most conservative live fee in this portfolio (fee_split, crowdfund) is also 1%.

const DrawFeeBps = int64(100)

FeeCollector receives withdrawn fees and swept surplus. Compile-time constant: there is no setter and no transfer path. It is the deploying namespace's address — stated plainly: the operator of this realm.

const FeeCollector = address("g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39")

MaxFeeBps is the hard ceiling on any draw fee this realm could ever charge, enforced by the feeledger the realm is constructed with — a feeBps above it is refused by the primitive's own validation, not by a check in this file. 500 bps = 5%.

const MaxFeeBps = int64(500)

MaxGrantsPerOwner bounds how much of the global cap one owner can occupy. spendbook's own package documentation names this as the gap a consuming realm must close — "maxLive is a single global figure, so any owner permitted to Open can exhaust it and lock out the rest … the consuming realm decides who may Open, and a Book shared across mutually untrusting owners needs its own per-owner cap on top of this one." This is that cap. (permission_registry finding R1 is the same lesson from this portfolio's own history.)

const MaxGrantsPerOwner = int64(16)

MaxLabelLen bounds the grant's free-text label, which is sanitized before reaching markdown.

const MaxLabelLen = 64

MaxLiveGrants caps simultaneously live grants, via spendbook's own construction bound. Grant is permissionless to anyone with a funded pool, so the cap is exhaustible in principle, and that AVAILABILITY BOUND is documented rather than hidden.

It is documented here in its CORRECTED form. An earlier version of this comment claimed the mitigation was "MaxGrantsPerOwner plus the cost of becoming an owner at all (MinFunding per pool), and Reap is permissionless so expired grants cannot accumulate". An adversarial audit (finding Y1, DISCOVERY.md § 9) showed BOTH HALVES were false. A wrong mitigation is worse than an acknowledged bound, because a reader sizes the risk from it and stops looking:

What the cost ACTUALLY is: gas for one Fund, MaxGrantsPerOwner Grants and one WithdrawPoolAll per address — about MaxLiveGrants*17/16 transactions to fill the cap — plus the storage deposit on every grant record, which the chain holds for as long as the grant does and refunds only when it is removed. That is real and unrecoverable while the grief lasts. It is not the locked pool capital the old comment promised, and it is priced in gas and storage rather than in capital.

No capital bound was added, because the obvious one does not work. Requiring an owner with live grants to retain MinFunding is defeated for the price of the draw fee: nothing forbids granting to YOURSELF, so the attacker draws its own pool to zero through its own grant and Claims the credit back, recovering all but DrawFeeBps of it while the retention rule still reads as satisfied. Closing THAT would mean bounding Draw below MinFunding — the realm refusing to let a delegate spend the budget, which is the feature. The remaining alternative, a non-refundable per-grant fee, taxes every honest owner to price a grief, and at a fee high enough to deter one it is a tenth of MinFunding per grant.

The blast radius is narrow and that is the reason this is disclosed rather than redesigned: a full book blocks NEW grants only. Every existing grant, pool, draw, credit and claim keeps working, no value is at risk, and any owner can free their own slots with Revoke.

The figure was 1024 until that audit, and 1024 was conservative rather than measured: nothing in this realm iterates the book (Render is bounded by MaxRenderRows, every write path is a single avl lookup) and a grant record's storage is paid for by whoever creates it. Quadrupling it quadruples the cost of filling the cap and costs an honest owner nothing. spendbook's own MaxLiveLimit is 10000.

const MaxLiveGrants = 4096

MaxRenderRows bounds the grant list on the index page.

const MaxRenderRows = 20

MinFunding keeps dust pools out: 1 GNOT per Fund call.

const MinFunding = int64(1_000_000)

MinPerPeriod is the smallest per-window cap a grant may carry. It exists so a grant cannot be created that no draw could ever satisfy, and it is the ONLY lower bound in the draw path — there is deliberately no minimum Draw amount, because a minimum draw above a grant's PerPeriod would make the grant silently undrawable.

const MinPerPeriod = int64(1_000)

MinPeriodBlocks is the shortest rolling window a grant may use. A window of a handful of blocks is a rate, not a budget, and it makes PerPeriod meaningless as a bound on worst-case loss. A periodLength of 0 is still accepted and means ONE LIFETIME WINDOW that never refills (spendbook's own semantics).

const MinPeriodBlocks = int64(100)

RealmPath is this realm's own path, used to build Render links.

const RealmPath = "/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/budgets"

Functions

Amend

func Amend(cur realm, id int64, perPeriod, total, expiresAt int64) bool

Crossing function. Callable from a transaction via MsgCall, and from another realm as Amend(cross(cur), ...).

Amend changes a grant's terms. Owner only. The returned bool reports whether the new terms are in force NOW (a tighter-or-equal amendment) or queued for the next window boundary (a looser one) — spendbook's one-sided rule, and the distinction worth knowing.

periodLength is NOT a parameter. spendbook makes it immutable because moving it would move every future boundary, which is an enlargement vector wearing a different hat; this realm reads the grant's own periodLength and passes it unchanged, so changing it is not merely refused, it is unexpressible.

Claim

func Claim(cur realm) int64

Crossing function. Callable from a transaction via MsgCall, and from another realm as Claim(cross(cur), ...).

Claim pays the caller's entire accrued credit out. DELEGATE ONLY: the account is the calling address. Credit from a grant that has since been revoked, expired or reaped is still claimable — it was earned under a live authority and this realm does not revisit that.

CreditOf

func CreditOf(addr address) int64

CreditOf returns a delegate's drawn-but-unclaimed credit.

Draw

func Draw(cur realm, id int64, amount int64) int64

Crossing function. Callable from a transaction via MsgCall, and from another realm as Draw(cross(cur), ...).

Draw spends amount from the grant's budget. DELEGATE ONLY: the spender is the calling address, never an argument. Nothing is paid out here — the delegate is credited net of the fee and claims separately.

Three things must all hold and they fail in this order, which matters: the caller must be the grant's delegate and the amount must be within every spendbook bound (window cap, lifetime cap, expiry, start); and the owner's pool must cover the GROSS amount. The solvency check runs BEFORE the allowance is consumed, so a draw refused for insolvency leaves the budget untouched rather than burning window capacity on a draw that did not happen.

Drawable

func Drawable(id int64) int64

Drawable is the HONEST figure, and the one a delegate should read: the lesser of what the grant authorizes and what the owner's pool holds. This distinction is the realm's own — spendbook bounds permission and says so, never solvency.

FeesAccrued

func FeesAccrued() int64

FeesAccrued returns the fee pot: charged, not yet withdrawn.

Fund

func Fund(cur realm) int64

Crossing function. Callable from a transaction via MsgCall, and from another realm as Fund(cross(cur), ...).

Fund adds the attached coins to the caller's own pool. Direct EOA calls with -send only (the receipt-guaranteed shape); exactly one positive ugnot coin, at least MinFunding. Anyone may fund; funding is what makes an address an owner.

Grant

func Grant(cur realm, spender address, label string,
	perPeriod, periodLength, total, expiresAt int64) int64

Crossing function. Callable from a transaction via MsgCall, and from another realm as Grant(cross(cur), ...).

Grant opens a standing spending authority for one delegate and returns the grant id. The caller becomes the grant's owner and must already have a funded pool — not because the grant reserves anything (it does not), but because a grant from an address that has never funded anything is a promise with nothing behind it, and this realm declines to record one.

Terms, all in the chain's own units:

perPeriod the cap per window, at least MinPerPeriod. Required. periodLength blocks per window. 0 means ONE LIFETIME WINDOW that never refills; otherwise at least MinPeriodBlocks. total lifetime cap across every window. 0 means unlimited. expiresAt absolute block height at which authority ends. 0 means no expiry.

Every bound above except the two realm minimums is validated and enforced by spendbook, and an invalid combination surfaces as that package's own sentinel error.

GrantLabel

func GrantLabel(id int64) string

GrantLabel returns a grant's raw label (unsanitized: callers rendering it into markdown must sanitize, as this realm's Render does).

GrantUsage

func GrantUsage(id int64) (spentWindow, spentTotal, windowIndex, draws int64)

GrantUsage returns a grant's consumption AT THE CURRENT HEIGHT: what the window the chain is in right now has spent, the lifetime gross, that window's index, and how many draws there have been.

The two window figures are computed rather than read straight off the record, and that is the whole point of windowAt below: a window that has rolled with no draw since leaves Allowance.WindowIndex naming a window the chain has already left, and Allowance.SpentWindow holding the previous window's spend. spentTotal and draws are cumulative and never go stale.

GrantsOf

func GrantsOf(addr address) int64

GrantsOf returns how many live grants an owner currently holds.

Height

func Height() int64

Height returns the current chain height: the clock every window and expiry is measured against, and the one no caller can choose.

Held

func Held() int64

Held returns H: the ugnot actually at this realm's address.

IsExpired

func IsExpired(id int64) bool

IsExpired reports whether a grant's authority has lapsed, which is also the condition under which anyone may Reap it.

Liabilities

func Liabilities() int64

Liabilities returns P + L — everything this realm owes.

LiveGrants

func LiveGrants() int64

LiveGrants returns how many grants are currently live, from the primitive's own count.

NumGrants

func NumGrants() int64

NumGrants returns how many grants have ever been created. Ids are never reused, so this only grows.

PendingTerms

func PendingTerms(id int64) (perPeriod, total, expiresAt int64, pending bool)

PendingTerms returns a queued loosening amendment and whether there is one. An amendment is queued only when it would widen the live window, and it takes effect at the next window boundary.

PoolOf

func PoolOf(addr address) int64

PoolOf returns an owner's unspent pool.

Reap

func Reap(cur realm, id int64)

Crossing function. Callable from a transaction via MsgCall, and from another realm as Reap(cross(cur), ...).

Reap removes a grant whose authority has EXPIRED, freeing its slot and its storage. Anyone may call it, and the reasoning is spendbook's own: an expired allowance can never authorize another draw, so removing it takes nothing from the owner and grants nothing to the caller. The chain refunds the freed storage deposit to the reaping transaction, which is the standing incentive.

A grant with no expiry can never be reaped; only its owner can end it.

Remaining

func Remaining(id int64) int64

Remaining is the AUTHORITY figure: what the grant's terms permit right now, ignoring whether the owner's pool can cover it.

Render

func Render(path string) string

Render shows the realm at "" and a grant page at "<id>". Labels are free text and pass through the ecosystem sanitizer before hitting markdown.

Revoke

func Revoke(cur realm, id int64)

Crossing function. Callable from a transaction via MsgCall, and from another realm as Revoke(cross(cur), ...).

Revoke ends a grant immediately. Owner only. The id is never reused, so a revoked grant cannot be resurrected and a later Draw against it fails as unknown.

Revoking does NOT claw back credit the delegate has already drawn — that is theirs and stays claimable. Revoke ends future authority.

Surplus

func Surplus() int64

Surplus returns H - (P + L) — out-of-band coins, sweepable.

SweepSurplus

func SweepSurplus(cur realm, denom string) int64

Crossing function. Callable from a transaction via MsgCall, and from another realm as SweepSurplus(cross(cur), ...).

SweepSurplus sends out-of-band coins to the FeeCollector: for the pool denom, everything above tracked liabilities; for any foreign denom, the full balance. Owner pools, delegate credit and the fee pot are untouchable by construction (coinio.Sweep refuses to dip below the reserve). Anyone may call it.

With nothing out of band this ABORTS ("coinio: no surplus to sweep for <denom>") rather than returning 0. That is the primitive's choice and the right one: a sweep is a transfer, and a transfer of nothing that reports success is how a caller comes to believe a balance was moved.

TotalPooled

func TotalPooled() int64

TotalPooled returns the P term: unspent owner funds.

UnclaimedCredit

func UnclaimedCredit() int64

UnclaimedCredit returns the delegates' share of the L term.

WithdrawFees

func WithdrawFees(cur realm) int64

Crossing function. Callable from a transaction via MsgCall, and from another realm as WithdrawFees(cross(cur), ...).

WithdrawFees pays the entire accrued fee pot to the compile-time FeeCollector. Anyone may call it: the destination is fixed, so there is nothing for a caller to steer.

WithdrawPool

func WithdrawPool(cur realm, amount int64) int64

Crossing function. Callable from a transaction via MsgCall, and from another realm as WithdrawPool(cross(cur), ...).

WithdrawPool returns part of the caller's unspent pool and reports the pool REMAINING afterwards. There is no lock and no check against outstanding grants: the money is the owner's, and a delegate's authority is not a claim on it (see the package doc, "the owner can always leave"). Revoke is the clean way to end a grant.

WithdrawPoolAll

func WithdrawPoolAll(cur realm) int64

Crossing function. Callable from a transaction via MsgCall, and from another realm as WithdrawPoolAll(cross(cur), ...).

WithdrawPoolAll returns the caller's entire unspent pool and reports the amount WITHDRAWN.

The return values of the two withdraw entrypoints are deliberately asymmetric, which is worth naming rather than letting a caller discover: each returns the figure its caller does not already have. WithdrawPool was told the amount and wants to know what is left; WithdrawPoolAll knows what is left (nothing) and wants to know what it got. Returning the remainder here would hand every caller a constant 0.


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

A first-party dependency is imported from the SAME chain's namespace, so each deployment above depends on that chain's copy — the paths differ even where the code does not.

chain-attested
Importsgnoland-1: chain, chain/runtime, chain/runtime/unsafe, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coinio, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledger, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/spendbook, gno.land/p/nt/avl/v0, gno.land/p/nt/markdown/sanitize/v0, strconv, strings
onyx-1: chain, chain/runtime, chain/runtime/unsafe, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coinio, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledger, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/spendbook, gno.land/p/nt/avl/v0, gno.land/p/nt/markdown/sanitize/v0, strconv, strings
First-party dependenciesgno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coinio, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledger, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/spendbook, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coinio, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledger, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/spendbook

Known limitations

Recorded by the people who built and deployed it. This list is deliberately not empty where honesty costs something.

curated

gno.land mainnet

onyx-1 testnet

Source

Source file (gnoland-1)mainnet/r/budgets/budgets.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 (gnoland-1)yes — byte-identical
Source file (onyx-1)onyx/r/budgets/budgets.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 (onyx-1)yes — byte-identical