const StatusOpen, StatusSettled, StatusVoided
Invoice statuses.
Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.
Package invoices is on-chain accounts receivable: an issuer records an obligation, and the named payer discharges it ...
Package invoices is on-chain accounts receivable: an issuer records an obligation, and the named payer discharges it later, in parts.
Every payment realm in this portfolio, and every escrow and vesting package the discovery gate found on either live chain, is PREFUNDED. service_market escrows the exact price at purchase. subscriptions prepays a whole period. payroll, vesting, fee_split and revshare push money that already arrived. In all of them the coins exist before the claim on them does.
An invoice inverts that. It is UNFUNDED AT CREATION: the obligation is recorded first, and the money may never arrive at all.
That one inversion is the whole realm, and it is visible in the arithmetic rather than only in the prose. payroll's invariant carries a pool term because an employer funds first:
1payroll: Held() == TotalPooled() + Liabilities() + Surplus()
2invoices: Held() == Liabilities() + Surplus()
There is no pool here, and no reserve, because at the moment an invoice is issued this realm holds nothing on its behalf and promises nothing. Issuing costs the issuer a storage deposit and nothing else.
Two features that would be decoration on a prefunded rail are load- bearing here, and they are the reason this is a realm rather than a field on an existing one:
An invoice is billed for work already done, so there is nothing for this realm to hold back. Money paid is credited to the issuer's ledger balance in the same transaction. There is no acceptance window and no release step — those belong to service_market, whose model is the opposite one.
Consequently this realm DOES NOT arbitrate. A payer who disputes a bill simply does not pay it, and the invoice goes overdue. That is the mechanism off chain too, and inventing a dispute path here would make this a worse copy of service_market rather than a complement to it.
Pay credits the issuer's ledger balance; the issuer later calls Claim. The push alternative — paying the issuer inside the payer's own transaction — would let the issuer's address decide whether the PAYER's settlement succeeds. An issuer that is a realm which panics on receipt, or is simply unable to accept, would make its own invoices unpayable and the failure would land on the payer. Pull puts every failure on the party who owns it.
There is no permission to bill someone, because requiring one would mean a registry of who may invoice whom, which is a different application. The honest consequence is stated here rather than discovered later:
1AN UNPAID INVOICE IS AN UNVERIFIED CLAIM BY ITS ISSUER.
So this realm deliberately exposes NO global "what does this address owe" aggregate. Such a number would be the sum of every stranger's self-reported assertion, and reading it as a credit signal is the error the getter's existence would invite. What it exposes instead is ISSUER-SCOPED: OutstandingFrom and OverdueFrom answer "what does this payer owe THIS issuer", which is the question a consumer realm can actually act on, because a consumer gating its own service on its own unpaid bills is trusting only itself.
Two bounds back that up mechanically. Issuing costs a storage deposit paid by the ISSUER, so volume is not free to the party who benefits from it; and MaxRecordsPerIssuer caps one issuer's STORED records, which is an availability bound on that issuer's own namespace and no one else's. Neither bound makes an unpaid claim true, and they are not presented as doing so.
Both bounds are PER ISSUER, and the global total is not bounded. Sybil issuers multiply it: N funded addresses yield N*MaxRecordsPerIssuer records, and the per-record deposit paid by each is the only thing making that expensive. Two consequences follow, stated here rather than discovered:
The cap counts STORED records rather than OPEN ones, and the difference is load-bearing rather than a detail. OutstandingFrom, OverdueFrom and HasOverdueFrom — the getters a consumer realm calls, and therefore the ones whose gas cost is someone else's problem — iterate an issuer's whole prefix, settled and voided records included, because a settled record is not removed until Release. A cap on open invoices alone would bound the obligations and leave that iteration unbounded: an issuer could issue and void repeatedly, keeping `open` at zero while their prefix grew without limit, until a consumer's read ran out of gas. Capping what is stored bounds both, since open can never exceed stored.
Finding "every invoice naming me" is therefore an off-chain indexer's job: on chain it would be an unbounded iteration over every issuer that has ever billed anyone, and the answer would be untrustworthy anyway.
An issuer record is paid for by that issuer's first Issue; an invoice record by the Issue that creates it. Release removes a finished invoice and the chain refunds the freed deposit to the transaction that frees it, which is the standing incentive to tidy up a record that is no longer anyone's business.
Invoice statuses.
Denom is the only asset this realm holds.
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.
NOTE FOR ANY RE-NAMESPACED PORT: this is the address-as-data hazard that a namespace substitution rewrites silently while every byte-comparison still reports the tree faithful, because the rewrite IS the intended transformation. Re-state it deliberately per chain; never let it be derived.
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%.
MaxMemoLen bounds the free-text memo, in bytes. The memo is rendered, so it is sanitized at the render boundary, never at the write boundary — storing what the issuer actually wrote keeps the record honest.
MaxRecordsPerIssuer caps the invoice records one issuer may have STORED at once — open, settled and voided alike. It is what makes the issuer-scoped getters gas-bounded; see the package doc for why the cap counts stored records and not open ones.
Issue is permissionless, so this is an AVAILABILITY BOUND and it is documented rather than hidden: an issuer can exhaust their OWN capacity and no one else's, because the bound is per-issuer. The cost of filling it is gas plus a storage deposit on every record.
Hitting it is self-service to clear: Release is permissionless and the chain refunds the freed deposit to whoever calls it, so an issuer at the cap can always release their own finished records, and a stranger has a standing incentive to do it for them.
MinInvoiceAmount is the smallest invoice that may be issued. Below the fee denominator a 1% fee floors to zero, so a smaller invoice would be fee-free by accident rather than by design.
MinPayment keeps dust settlements out. It is NOT a floor on the final payment: a payment is accepted when it is at least MinPayment OR when it settles the invoice exactly. Without that second clause an invoice with 300 ugnot outstanding could never be closed, and "unpayable remainder" is a worse failure than dust.
RealmPath is this realm's own path, used to build Render links.
SettlementFeeBps is charged on each payment, on the gross, floor-rounded in the ISSUER's favour: 100 bps = 1%. It matches the most conservative live fee in this portfolio (fee_split, crowdfund, budgets, payroll).
The ISSUER bears it, not the payer: a payment of N reduces the outstanding balance by exactly N, and the issuer is credited N minus the fee. A payer's debt must be discharged by what the payer sent, or the arithmetic of "paid in full" would depend on this realm's fee schedule.
It is a compile-time constant with no setter, so an invoice's economics cannot be changed after it is issued.
The fee is floored PER PAYMENT, so splitting a payment can lower the total fee. Each payment boundary forfeits at most 1 ugnot, and MinPayment bounds how many boundaries an invoice admits. MEASURED on onyx-1, not reasoned: 10,990 paid at once charges 109, while ten payments of 1,099 charge 100. The beneficiary is the ISSUER, who is credited the difference and does not control the payment schedule; the payer gains nothing, because the outstanding balance falls by the gross either way. Gas for one extra payment costs more than the 1 ugnot it saves, so this is priced out rather than prevented, and a colluding issuer/payer pair would spend more to exploit it than they recovered.
A final settlement below the fee denominator pays zero fee for that same reason and is NOT a separate case: floor(99/100) is 0 whether or not MinPayment's settle-exactly clause admitted it, so that clause forfeits nothing on its own.
BalanceOf is an issuer's settled, unclaimed balance.
Claim pays the caller their whole settled balance. The issuer only — there is no "claim on behalf of", because that would let a third party choose the moment someone else's coins move.
FeesAccrued is the fee pot awaiting WithdrawFees.
HasInvoice reports whether a record exists, without panicking on absence — every other invoice accessor panics, which makes them unusable as an existence probe.
HasOverdueFrom is OverdueFrom as a predicate, for a consumer that only needs the boolean. It stops at the first hit rather than summing.
Height is the current chain height, exposed so a caller can compute due heights against the same clock this realm uses.
Held is the realm's actual ugnot balance, read from the chain.
IsOverdue reports whether one invoice is overdue right now.
Issue records an obligation of `amount` owed by `payer` to the caller, to be settled by `dueHeight`. It moves no coins and reserves nothing. Returns the invoice id.
dueHeight must not be in the past. A backdated due height would create an invoice that is overdue in the transaction that issued it, which is a claim about a deadline that was never offered.
IssuedOf is how many invoices an address has ever issued, and their total face value. Note the tense: both are lifetime figures and neither falls when an invoice is settled, voided or released.
Issuers is the number of addresses that have ever issued an invoice.
Liabilities is every coin this realm owes to anyone: the ledger's own total, which is issuer balances plus the fee pot.
There is no pool term, and its ABSENCE is the unfunded property this realm exists to model — see the package doc. Do not "fix" this to match payroll's shape.
Memo is an invoice's free-text memo, exactly as the issuer wrote it. It is UNSANITIZED and caller-supplied: a consumer that renders it owes it the same treatment Render gives it here.
NextID is the id the next Issue will allocate. Exposed so a caller can tell a never-issued id from a released one.
OpenOf is the number of open invoices an issuer holds.
Outstanding is what is still owed on one invoice. Zero for a settled or voided one — a voided invoice owes nothing by definition, which is the point of voiding it.
OutstandingFrom is the total a payer still owes THIS issuer across the issuer's open invoices.
The iteration is bounded by MaxRecordsPerIssuer, NOT by the number of open invoices: it walks the issuer's whole stored prefix and skips the records that are not open. That is why the cap counts stored records — see the package doc.
This is the issuer-scoped shape the package doc argues for: a consumer realm gating its own service on its own unpaid bills is trusting only itself. There is deliberately no global per-payer total — see the package doc, "Anyone may issue an invoice naming anyone".
OverdueFrom is OutstandingFrom restricted to invoices whose due height has passed. A consumer realm that wants to withhold service from a late payer reads this.
Pay settles part or all of an invoice with the attached coins. The named payer only. Direct EOA calls with -send only (the receipt-guaranteed shape); exactly one positive ugnot coin.
The attached amount must not exceed the outstanding balance. Accepting an overpayment would turn a payment into a donation the payer did not choose to make and this realm has no path to return, so it is refused and the payer keeps their coins.
Returns the outstanding balance remaining after this payment.
Records is the number of invoice records currently stored, open or not. It falls on Release, so it is a storage figure rather than a count of invoices that have existed.
RecordsOf is the number of invoice records an issuer currently has stored, of any status. This — not OpenOf — is the figure capped by MaxRecordsPerIssuer, so it is the one an issuer near the cap needs.
Release removes a finished invoice and frees its storage. Permissionless: the chain refunds the freed storage deposit to the caller, which is the standing incentive to tidy up a record that is not your own.
It refuses an open invoice, which is what makes it safe to let a stranger call: a Release can never destroy an outstanding obligation. An issuer who wants a record gone must Void it first, and that leaves an event.
Releasing frees the issuer's slot against MaxRecordsPerIssuer. That is the only way a slot is ever freed — settling and voiding do not free one, because the record is still stored and still walked by every issuer-scoped getter.
SettledTotal is the sum of every issuer's unclaimed balance.
StatusOf is an invoice's status.
Surplus is coins held beyond every obligation — what SweepSurplus would move. It is zero when every coin arrived through an entrypoint.
SweepSurplus sends coins held beyond every obligation to FeeCollector. The reserve passed to coinio is the realm's full liability, so a sweep can never reach a coin an issuer is owed.
Void cancels the unpaid remainder of an invoice. The issuer only — this is an issuer forgiving a debt, and nobody else may forgive it for them.
It does NOT reverse payments already made. Coins the payer sent are already credited to the issuer's ledger balance and may already have been claimed; there is no mechanism on chain to take them back, and pretending otherwise in the status would be worse than saying so. A voided invoice therefore keeps its `paid` figure, which is what makes the record readable afterwards.
Voiding does not remove the record. Release does that.
WithdrawFees moves the accrued fee pot to FeeCollector. Anyone may call it; the destination is a compile-time constant, so the caller cannot influence where the coins go.