netting
gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/netting on onyx-1 testnet
realmapplicationobligation-clearinglive on onyx-1
Settle who owes whom across a group by net position.
Each member settles one net amount instead of every obligation, and the realm publishes the gross and net figures rather than claiming a saving.
In detail curated
Multilateral obligation netting with REAL ugnot custody. Parties Declare bilateral obligations into a set; Close computes each member's net position and freezes the set; net debtors Settle by sending EXACTLY their net amount in coins; net creditors are credited and pull with Withdraw; a terminal set is Prune-able by anyone, which reclaims its storage and refunds the deposit to whoever calls. THE DESIGN AXIS THAT SEPARATES IT FROM EVERY OTHER CUSTODIAL REALM IN THIS PORTFOLIO IS THAT NETTING IS A COMPRESSION CLAIM AND THIS REALM REFUSES TO MAKE IT: Compression returns the gross obligation count, the net transfer count, the gross value and the net value -- FOUR NUMBERS AND NO RATIO -- because netting compresses only where obligations are MUTUAL and a star graph, where everyone owes one hub, nets to itself. Render says so in prose rather than leaving a reader to infer it from two equal numbers. netTransferCount counts every member holding a NONZERO net, so in a star it equals the member count and can EXCEED the gross obligation count -- compression of transfer count is then NEGATIVE, which is a true property of the topology and not a defect. Declare's debtor is cur.Previous().Address(), so only an address that can SIGN may owe anything and nobody can declare a debt on a stranger's behalf. Close asserts netsSumToZero BEFORE any coin is asked for, which is what makes the if debits != credits branch unreachable rather than merely untaken, and sets settleEnd = now + SettleWindowBlocks; a FULLY NETTED set (every net zero) returns early and settles with no coin movement at all. Settle is direct-EOA-with-send only through coinio's receipt-guaranteed envelope and demands the exact net amount -- not more, not less -- so an overpayment is refused rather than kept. SettleFeeBps is charged on settlement into the feeledger fee pot and swept with WithdrawFees, which is a COUNTER and not an AVL entry: its withdrawal allocates and frees nothing, measured as a zero storage delta on chain. Prune refuses a non-terminal set and PRESERVES CREDITS -- measured live, BalanceOf and Held both survive the removal of the set record that created them, which is the one thing a storage-reclaiming tidy function must never get wrong. The monetary invariant is Held() == Liabilities() + Surplus() with Liabilities() = totalPending + ledger.Liabilities(), and it was checked live AT A NONZERO BALANCE (2475 == 2475 + 0) rather than at the all-zero state every broken implementation also satisfies. NumSets() is nextID, a LIFETIME id counter and not a live set count -- it still reads 1 after the only set was pruned -- while OpenSets() is book.OpenCount(); a consumer that confuses the two publishes a wrong figure. Guards: the pinned rejectStraySend variant (!cur.IsCurrent() first) on every mutating entrypoint, checkedAdd on the lifetime totals with checkedAdd(a, MinInt64) a STATED LIMIT in the docstring rather than a silent one, and coinio's single-denomination and reserve-floor refusals inherited from the on-chain dependency.
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/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/netting.FeesAccrued()' \
--remote https://rpc.onyx.testnets.gno.land:443FeesAccrued 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.
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/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/netting"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
onyx-1 testnet onyx-1
RenderedSourceCall builderState
chain-attested| Import path | gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/netting |
|---|---|
| Namespace | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Deployed at height | 382,786 |
| Deploy transaction | 5b9699d45044809a2d20291c730acde31591272242025e4f7b141ed23a2e4546 look it up on the RPC |
| Deployer | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Gas used | 68,517,426 |
| Storage | not attested per package on this chain — see the limitations below |
| Files on chain | netting.gno gnomod.toml |
| Deployed bytes | netting.gno — 51,670 bytes |
| sha256 | b6e9efe2f844e66e2aa95259316b871d3436e163af039e2ae1eea93423f44820 |
| Realm address | g1kdewpgrrp7wfwkl8ypwr3qu9axhx5ywvupkdgz |
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/netting$download&file=netting.gno' | shasum -a 256Expected: b6e9efe2f844e66e2aa95259316b871d3436e163af039e2ae1eea93423f44820 — 51,670 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.
API
chain-derived 34 exported functions.
Every function below deep-links to gnoweb's call builder, which generates a ready-to-run gnokey maketx call for it:
AbandonAdmitBalanceOfCancelCloseCompressionDeclareFeesAccruedGrossOfHeightHeldIsMemberLapseLiabilitiesLifetimeMemberCountNetOfNumSetsObligationOpenOpenOfOpenSetsPositionOfPruneRenderSetLabelSetMemoSettleStatusSurplusSweepSurplusTotalPendingWithdrawWithdrawFees
Overview
Realm netting is a multilateral obligation clearing register in GNOT. A convenor opens a netting set and admits members; each member declares, against other members, what it owes them; closing the set computes every member's NET position — what it is owed minus what it owes — and only those net amounts move. Net debtors pay in, net creditors withdraw, and a graph of G gross obligations collapses to at most N-1 transfers.
WHY THIS EXISTS (see DISCOVERY.md): across 641 live packages on gnoland-1 and 637 on onyx-1, thirteen chains mirrored in gnolang/tx-exports, and GitHub-wide `extension:gno`, no package records BILATERAL obligations between pairs of parties, computes net positions, or compresses gross obligations into net transfers. The two closest artifacts were read in full — mainnet p/moul/x/daily/pullpayment/v0 and r/moul/x/daily/splitter/v1 — and both fail on the same structural point: their ledgers are keyed by PAYEE ALONE, so there is no payer axis, no directed graph, and nothing to net. Both are ports of Solidity's canonical primitives, and both ports kept the one-dimensional ledger.
THE DELTA IS STATED AT ITS REAL SIZE AND NO LARGER. Multilateral netting is not a new idea — it is CLS, ACH, Sardex, the Romanian Ministry of Economy's national setoff, and a literature of graph algorithms (DISCOVERY.md § out-of-language prior art). What is new here is a Gno implementation, with this portfolio's live-validated custody discipline. Nothing more is claimed.
WHAT NETTING IS WORTH, HONESTLY: compression requires MUTUALITY. A star graph — every member owing one hub and the hub owing nobody — nets to itself, and the gross and net figures are identical. This realm therefore REPORTS the gross and net terms (Compression) and never asserts a saving. Read the two pairs of numbers; the ratio is whatever the members' obligations make it.
COMPOSITION: coin movement is delegated to coinio, fee arithmetic and the fee pot to feeledger, the declaration window and exactly-once settlement authorization to duebook, ordered storage to p/nt/avl/v0, and free-text render output to the ecosystem sanitizer p/nt/markdown/sanitize/v0. This realm owns only the netting arithmetic, the set state machine, and the conservation bookkeeping.
A NETTING SET HOLDS NO COINS WHILE IT IS OPEN. An obligation is a DECLARATION, not a transfer: Declare moves no value and locks none. Coins enter only at Settle, after the set has closed and net positions are frozen. This is why Cancel and Lapse need no refund path at all — there is nothing held to refund — and it is the sharpest difference between this realm and the escrow family the discovery gate found.
NET POSITIONS ARE MAINTAINED INCREMENTALLY, NOT COMPUTED AT CLOSE. Each Declare applies -amount to the debtor and +amount to the creditor in the same transaction, so the vector of net positions sums to zero by construction at every height. Close asserts that sum is zero anyway (netsSumToZero) before a single coin can be asked for. The assertion is unreachable through the public API and is kept because it is the one invariant whose violation would mint or destroy value.
THE SETTLEMENT IS ONE DEFERRAL. Open schedules exactly one duebook deferral per set — owner: the convenor, due: the declaration deadline, expiry: the end of the closing window — and the three transitions out of [open] are the three ways that deferral can be consumed:
Close -> book.Claim (due, not expired; ANYONE) Cancel -> book.Cancel (convenor renounces, while open) Lapse -> book.Expire (anyone, once the closing window is over)
Because duebook consumes a deferral before returning and never reuses an ID, a set can leave [open] AT MOST ONCE, structurally. The book's open-deferral cap is likewise this realm's cap on open sets.
LIFECYCLE (stored states in brackets):
Open (anyone) : opens [open]; caller is the convenor; schedules the deferral. Admit (convenor) : adds a member, while [open]. Admission obligates nobody: it only makes an address a legal endpoint of a declaration. Declare (member) : "I owe this member X". Caller is ALWAYS the debtor, so a declaration can only ever increase the CALLER's own net debit. No coins move. Close (ANYONE) : height in [deadline, closeEnd). Freezes every net position. netValue == 0 (a fully circular graph) settles on the spot with no coins ever held -> [settled]. Otherwise -> [settling] with a pay-in window. Settle (net debtor,+send): exactly the debtor's own net debit, once, while [settling] and before settleEnd. The last payment that completes the set credits every net creditor -> [settled]. Abandon (ANYONE) : [settling], settleEnd reached, still short. Every debtor who paid gets exactly what it paid back, FEE-FREE -> [abandoned]. Cancel (convenor) : while [open] -> [cancelled]. No coins are held, so nothing is refunded. Lapse (ANYONE) : [open] and closeEnd reached -> [lapsed]. The permissionless valve: a set nobody closes cannot hold its duebook slot forever. Withdraw (any creditor) : the caller's whole credit balance, in coins. Pull-only; this realm never pushes. Prune (ANYONE) : removes a terminal set holding no coins. The freed storage deposit is refunded by the chain to the CALLER, which is the incentive to call it. WithdrawFees (ANYONE) : pays the accrued fee pot to the compile-time FeeCollector. SweepSurplus (ANYONE) : out-of-band coins to the FeeCollector, never touching tracked liabilities.
ALL-OR-NOTHING, AND WHAT IT COSTS: a net creditor is credited only once EVERY net debtor has paid in full. One defaulting debtor therefore denies every creditor in that set — and the compensation is that no creditor is ever credited a ugnot the realm does not already hold, and every debtor who paid gets its money back fee-free. A partial-settlement design would pay some creditors from other debtors' money and need a rule for who; there is no such rule here because there is no such case.
THE FEE, precisely: feeBps is snapshotted at Open from the compile-time SettleFeeBps, so a set's terms are fixed when it is created and visible in SetInfo from that moment. It is charged ONCE, at the moment a set becomes [settled], on each net creditor's credit, with feeledger's floor rounding (rounding favors the creditor). 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 declaring, no fee on settling, and NO FEE ON ANY ABANDON REFUND.
THERE IS NO ADMIN. The fee rate, the fee collector, every bound and every window are compile-time constants; no address can change terms, pause the realm, or touch a set it does not convene. The convenor's only powers are admitting members and cancelling its own set while it is open — neither of which can move a coin. 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 coins paid in for sets still [settling] (tracked O(1) as totalPending), L the ledger's liabilities (unclaimed creditor credits, unclaimed abandon refunds, and 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: Settle raises H and P together (coinio.Receive is the receipt-guaranteed shape); completing a set moves netValue out of P and into L, split into creditor credits and the fee; Abandon moves paidIn out of P and into L fee-free; Withdraw and WithdrawFees debit L before the identical amount leaves H (checks-effects-interactions); any panic aborts the whole transaction; this realm never issues or removes coins.
ONLY GNOT IS ACCEPTED: Settle 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 declaring a debt as another member, settling another member's net debit, or withdrawing another creditor's balance is impossible by construction.
STORAGE: a set record is paid for by the convenor's transaction deposit, a member record by the convenor's Admit, and a pair record by the declaring debtor's own transaction. Completing or abandoning a set drops nothing; Prune removes the whole terminal record, and the chain refunds the freed deposit to the transaction that frees it.
Imports
chainchain/runtimechain/runtime/unsafegno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coiniogno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/duebookgno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledgergno.land/p/nt/avl/v0gno.land/p/nt/markdown/sanitize/v0strconv
Constants and variables
const (
// MinDeclareBlocks and MaxDeclareBlocks bound the declaration
// window, in blocks, and are enforced by the duebook's own Schedule
// validation rather than by a check in this file.
MinDeclareBlocks = int64(1_200)
MaxDeclareBlocks = int64(1_300_000)
)
const (
// MaxLabelLen and MaxMemoLen bound the free text a set carries. Both
// are sanitized before they reach Render.
MaxLabelLen = 100
MaxMemoLen = 256
)
CloseWindowBlocks is how long after the deadline a set may still be closed. Past it, Close is impossible (the deferral has expired) and Lapse is the only way out — which strands no money, because an open set holds none.
const CloseWindowBlocks = int64(650_000)
Denom is the only asset this realm holds.
const Denom = "ugnot"
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("g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3")
MaxFeeBps is the hard ceiling on any 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)
MaxMembers caps one set's membership. Close iterates the member tree twice (once to assert the nets sum to zero, once to freeze positions), and completing a set iterates it once more to credit creditors, so this is the bound that keeps every O(M) pass inside a transaction's gas. 32 is deliberately modest: a clearing set is a counterparty group, not a population.
const MaxMembers = 32
MaxOpenPerConvenor bounds how much of the global cap one convenor may occupy (permission_registry R1 precedent: an uncapped per-principal share of a global cap is a griefing vector, not a feature).
const MaxOpenPerConvenor = 64
MaxOpenSets caps simultaneously open sets, via the duebook's own open-deferral cap. The AVAILABILITY BOUND this implies is stated rather than hidden: when the book is full, Open fails until a set is closed, cancelled or lapsed. Settling and terminal sets do not occupy a slot — their deferral is already consumed.
const MaxOpenSets = 1024
MaxPairs caps the DISTINCT ordered (debtor, creditor) pairs one set may hold. Declarations themselves are unbounded in count — a repeat declaration between an existing pair accumulates into that pair's record and allocates nothing — so this bounds storage, not activity. MaxMembers*(MaxMembers-1) == 992 is the complete digraph; 256 is a deliberate fraction of it.
const MaxPairs = 256
MaxRenderRows bounds the list length on the render pages.
const MaxRenderRows = 50
MinObligation keeps dust declarations out of the graph: 0.001 GNOT. Declaring costs no coins, so the only thing a dust declaration wastes is the declarer's own storage deposit — but a graph full of 1-ugnot edges makes the compression figures meaningless, which is the output this realm exists to produce.
const MinObligation = int64(1_000)
RealmPath is this realm's own path, used to build Render links.
const RealmPath = "/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/netting"
SettleFeeBps is the fee actually snapshotted into every set at Open: 100 bps = 1%, charged once, when the set becomes settled, on each net creditor's credit, floor-rounded in the creditor's favor. 1% is the most conservative live fee in this portfolio (fee_split precedent) and is what crowdfund charges.
const SettleFeeBps = int64(100)
SettleWindowBlocks is how long net debtors have to pay in after a set closes. Past it, Abandon refunds every payer fee-free.
const SettleWindowBlocks = int64(650_000)
Functions
Abandon
func Abandon(cur realm, id int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asAbandon(cross(cur), ...).
Abandon ends a set that reached its settlement deadline still short, and credits every debtor who paid exactly what it paid, FEE-FREE. Anyone may call it: the outcome is fully determined by the set's own state, so there is nothing for a caller to steer.
Net creditors receive nothing on this path, and that is the all-or-nothing guarantee working rather than failing: they were never credited, so there is no clawback, and no creditor was ever paid from another debtor's money.
Admit
func Admit(cur realm, id int64, addr address)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asAdmit(cross(cur), ...).
Admit adds an address to a set's membership while the set is open. Convenor only.
ADMISSION OBLIGATES NOBODY. Being a member only makes an address a legal endpoint of a declaration — it cannot be made a debtor by anyone but itself (see Declare). What admission does control is the griefing surface: without it, any address could fill a set to MaxPairs with declarations naming strangers, and the convenor could not form the group it meant to form.
BalanceOf
func BalanceOf(addr address) int64
BalanceOf returns an address's withdrawable credit.
Cancel
func Cancel(cur realm, id int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asCancel(cross(cur), ...).
Cancel ends an open set. Convenor only, any time while the set is open. No refund path is needed and none exists: an open set holds no coins, because a declaration is not a transfer. What members lose is the storage deposit and gas they spent declaring, which is stated plainly rather than hidden — a convenor can waste a member's gas but can never touch a member's money.
Close
func Close(cur realm, id int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asClose(cross(cur), ...).
Close freezes every net position in a set and opens the pay-in window. Anyone may call it, from the declaration deadline until the closing window ends; the duebook Claim consumes the set's deferral, so this can succeed at most once per set, ever.
A set whose net value is ZERO — every member's obligations exactly cancelled, which is what a fully circular graph produces — settles on the spot. No coins are ever asked for, held, or paid, and the gross value is extinguished in one transaction. That case is the whole point of the realm, so it is the fast path rather than a special one.
Compression
func Compression(id int64) (grossCount, netTransferCount, grossValue, netValue int64)
Compression returns the four numbers the product exists to produce: how many obligations were declared and for how much, against how many transfers the netting leaves and for how much. The RATIO IS NOT RETURNED and is not asserted anywhere — divide them if you want it, and note that netValue can legitimately be zero (a fully circular graph, the best possible case), which is exactly why no ratio is computed here.
While a set is still open the net figures are zero because they are frozen at Close, not derived on read.
Declare
func Declare(cur realm, id int64, creditor address, amount int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asDeclare(cross(cur), ...).
Declare records that the CALLER owes a fellow member `amount` ugnot, while the set is open. No coins move and none are locked: an obligation is a declaration, and the only value this realm ever holds arrives at Settle.
The caller is ALWAYS the debtor. That is the whole authorization model here and it is worth saying plainly: a declaration can only increase the caller's own net debit and some other member's net credit, so no address can be made to owe anything by anyone else. Declaring repeatedly toward the same creditor accumulates into one pair record and allocates no new storage.
FeesAccrued
func FeesAccrued() int64
FeesAccrued is the fee pot, withdrawable only to the FeeCollector.
GrossOf
func GrossOf(id int64, addr address) (out, in int64)
GrossOf returns a member's gross declared flows: what it has declared it owes, and what others have declared they owe it. These are the uncompressed figures, kept so a reader can see what netting removed.
Height
func Height() int64
Height returns the chain height this realm sees, so a caller can compare it against a deadline without a second query.
Held
func Held() int64
Held is the realm's actual ugnot balance.
IsMember
func IsMember(id int64, addr address) bool
IsMember reports whether an address is party to a set.
Lapse
func Lapse(cur realm, id int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asLapse(cross(cur), ...).
Lapse ends an open set whose closing window has passed. Anyone may call it. This is the permissionless valve: a set nobody closes cannot hold its duebook slot forever, and because an open set holds no coins there is nothing stranded by the outcome.
Liabilities
func Liabilities() int64
Liabilities is everything this realm owes anyone: pending pay-ins plus unclaimed credits plus the fee pot.
Lifetime
func Lifetime() (sets, grossValue, netValue, settledIn, credited,
refunded, withdrawn int64)
Lifetime returns the realm's cumulative counters: sets settled, gross and net value across every closed set, coins paid in, credited to creditors, refunded by Abandon, and withdrawn.
MemberCount
func MemberCount(id int64) int
MemberCount returns how many addresses are party to a set.
NetOf
func NetOf(id int64, addr address) int64
NetOf returns a member's live net position: positive when it is owed that much on balance, negative when it owes that much. It is live while the set is open and frozen thereafter. A non-member reads 0, which is also a member's value when its obligations cancel exactly — use IsMember to tell the two apart.
NumSets
func NumSets() int64
NumSets returns how many sets have ever been opened. It is the id high-water mark, so it does not decrease when a set is pruned.
Obligation
func Obligation(id int64, debtor, creditor address) int64
Obligation returns the cumulative amount debtor has declared it owes creditor in a set. Direction matters: Obligation(id, a, b) and Obligation(id, b, a) are different edges and both may be non-zero — the graph is directed, and netting them is Close's job, not storage's.
Open
func Open(cur realm, label, memo string, declareBlocks int64) int64
Crossing function. Callable from a transaction via
MsgCall, and from another realm asOpen(cross(cur), ...).
Open convenes a netting set and returns its id. Anyone may open one; the caller becomes the convenor. The fee terms (SettleFeeBps at this moment — a compile-time constant, so always SettleFeeBps) are snapshotted into the set and are fixed from here on. declareBlocks is the length of the declaration window and must be within [MinDeclareBlocks, MaxDeclareBlocks] — enforced by the duebook.
The convenor is NOT automatically a member. A convenor that wants to net its own obligations must Admit itself, which keeps "who runs the set" and "who is party to it" separate concepts.
OpenOf
func OpenOf(addr address) int64
OpenOf returns how many open sets an address convenes.
OpenSets
func OpenSets() int64
OpenSets returns how many sets are currently open, via the duebook's own open-deferral count.
PositionOf
func PositionOf(id int64, addr address) (debit, credit, paid int64)
PositionOf returns a member's frozen settlement figures: what it must pay in, what it is owed, and what it has actually paid. All three are zero until the set is closed.
Prune
func Prune(cur realm, id int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asPrune(cross(cur), ...).
Prune removes a terminal set that holds no coins, reclaiming its storage. Anyone may call it: the chain refunds the freed storage deposit to the pruning transaction, which is the incentive. Credits already in the ledger are unaffected — they are a per-address balance, not part of the set record.
Render
func Render(path string) string
Render serves the index at "" and a set page at "<id>".
SetLabel
func SetLabel(id int64) string
SetLabel returns a set's label, verbatim and unsanitized. Render sanitizes; a caller reading the raw value gets the raw value.
SetMemo
func SetMemo(id int64) string
SetMemo returns a set's memo, verbatim and unsanitized.
Settle
func Settle(cur realm, id int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSettle(cross(cur), ...).
Settle pays in the caller's own net debit, in full, once. The attached envelope must be exactly the caller's frozen net debit — not more, not less, and not in instalments. Partial payment is refused rather than accommodated: it would leave the set holding money that belongs to neither the payer nor any creditor, and a rule for who gets it first.
The payment that completes the set credits every net creditor in the same transaction and the set becomes settled.
Status
func Status(id int64) string
Status returns a set's stored state.
Surplus
func Surplus() int64
Surplus is Held minus Liabilities: out-of-band coins, sweepable to the FeeCollector. It is never negative in correct operation.
SweepSurplus
func SweepSurplus(cur realm, denom string) int64
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSweepSurplus(cross(cur), ...).
SweepSurplus sends out-of-band coins to the FeeCollector: for the pot denom, everything above tracked liabilities; for any foreign denom, the full balance. Pending pay-ins, creditor credits and the fee pot are untouchable by construction (coinio.Sweep refuses to dip below the reserve). Anyone may call it.
TotalPending
func TotalPending() int64
TotalPending is the P term of the conservation invariant: coins paid in for sets that have not yet settled or been abandoned.
Withdraw
func Withdraw(cur realm) int64
Crossing function. Callable from a transaction via
MsgCall, and from another realm asWithdraw(cross(cur), ...).
Withdraw pays the caller its entire credit balance in coins and returns the amount. Pull-only: this realm never pushes a payout, so a creditor that cannot receive coins can never block a settlement. Credits arrive from a settled set (net of that set's fee) and from an abandoned set's refund (fee-free); both land in the same balance.
WithdrawFees
func WithdrawFees(cur realm) int64
Crossing function. Callable from a transaction via
MsgCall, and from another realm asWithdrawFees(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.
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/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coinio, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/duebook, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledger, gno.land/p/nt/avl/v0, gno.land/p/nt/markdown/sanitize/v0, strconv |
|---|---|
| First-party dependencies | gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coinio, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/duebook, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledger |
Known limitations
Recorded by the people who built and deployed it. This list is deliberately not empty where honesty costs something.
curated- THE COMPRESSION CLAIM -- THIS REALM'S WHOLE REASON TO EXIST -- WAS NOT AND COULD NOT BE TESTED ON CHAIN, AND THE FIXTURE THAT REPLACED IT WAS FORCED RATHER THAN CHOSEN. Declare's debtor is cur.Previous().Address(), so only an address that can SIGN may owe anything; gno_key_list is denied by the harness permission classifier and no second funded onyx key exists. Every obligation in the live set therefore shares one debtor by construction -- a STAR GRAPH. Measured: Compression(1) returned (2, 3, 2500, 2500), so 2 gross obligations became 3 net transfer legs with gross value equal to net value; compression of VALUE was exactly 1.0x and compression of TRANSFER COUNT was NEGATIVE. That is the realm's own documented honesty condition observed live, not a disappointment. The headline case -- TestFullyCircularGraphClearsWithNoCoinsAtAll, three parties in a ring, 300,000 ugnot of obligations extinguished with ZERO transfers and no coin ever entering the realm -- remains proven by test and UNREACHABLE on chain under a one-key harness. Discharged by a second funded onyx key, which this harness has not provisioned; the same discharge r/invoices Limitation 2 names.
- NO DEPLOY-DAY §7b PREFLIGHT VERDICT IS QUOTABLE FOR THIS DEPLOY. tools/test_chain_probe.sh exits 3 PARTIAL (134 passed, 0 failed, 0 blocked by the network, 1 skipped), with PRE1b named: the committed catalog/chain_baseline.json lags live onyx-1 -- namespace_package_count live 22 against baseline 19, with invoices, netting and payroll each NEW since the baseline. Only exit 0 licenses a quote. Every GovDAO-mutable gating fact reads match, so the four findings are one count and three AUTHORIZED deploys and not drift. The blocker is no longer a permission -- operator item #16 closed and the baseline WAS re-emitted (task #99) -- it is that EVERY ONYX DEPLOY RE-STALES THE BASELINE AND RE-OPENS THE SKIP, which converts a grant owed once into a maintenance step owed per deploy. The deploy proceeded on the precedent that addpkg's exact-path collision guard is independently enforced on chain, §8's hard boundary being an unexpected deployment target collision and not an unquotable preflight.
- THE DEPLOY'S OWN STORAGE DEPOSIT IS UNMEASURED AND UNRECOVERABLE. No pre-broadcast balance snapshot was taken and the addpkg emits no storage event, so there is no second source -- see storage_measurement_note for why that reason is narrow and true while the wider version of it was measured FALSE for calls. This is the third onyx record to lose a deploy deposit for want of a one-line read before the broadcast, after Group 8 and Group 9 each observed that the read costs nothing.
- EVERY LIVE TRANSACTION WAS SIGNED BY THE ONE KEY THIS AGENT HOLDS (g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3), which is the same fact as the star-graph limitation seen from the authorization side rather than the topology side. No cross-party authorization boundary was exercised: nothing tested that party A cannot Settle party B's net, that a non-member cannot Declare into a closed set, or that one member's withdrawal cannot reach another's credit. All of those rest on the 36 committed tests and the 76-case sabotage sweep (0 uncaught).
- ONE SABOTAGE IS DELIBERATELY ABSENT AND THE REASON IS A PROOF, NOT AN EXEMPTION. A per-term mutation sweep over the committed suite found 8 surviving terms AFTER the audit had returned OK; seven were closed by adding tests, and the eighth -- Close's `if debits != credits` -- is UNREACHABLE, because netsSumToZero is asserted before any coin is asked for, so no input can enter the branch. It is on the deliberately-not-sabotaged list with that proof attached. An already-green, already-audited suite having 8 uncaught mutations is the finding worth carrying forward from this application.
- simulate=true on onyx-1 does NOT type-check (the chain's inert code-submission policy), so the dry run before this broadcast was a gas/fee/authorization probe only. The correctness substitute is `gno lint .` against the onyx release on exactly these bytes.
- THE FEE DESTINATION IS NOT ADDRESS-AS-DATA HERE, WHICH IS WORTH STATING BECAUSE THE THREE CUSTODIAL REALMS BEFORE IT WERE. netting accrues fees to its own feeledger pot and WithdrawFees pays the caller under an ownership check; there is no hard-coded g1ut6... collector constant for namespace_tree.py's blind str.replace to rewrite. A mainnet port of this realm therefore has no FeeCollector line to classify, unlike r/crowdfund, r/payroll, r/budgets and r/invoices.
- Admit validates only non-emptiness, so a non-bech32 member address breaks the documented pairKey newline-uniqueness invariant (directed-edge collision in the pairs view) and the render code-span. Custody untouched (member nets key on the full string; Close/Settle never read pairs); griefing confined to the admitting convenor's own set, plus permanently stranded credit if such an address nets positive. Fix (one IsValid line) staged for any successor and for the mainnet twin pre-deploy decision. Audit 2026-11-04, disclosed per the operator's disclose-and-queue-successor rule; details and severity in [an unpublished record].
Source
| Source file | onyx/r/netting/netting.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 |