Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

receivables package

Overview

Package receivables publishes a receivables aging report and the resulting allowance for credit losses over invoices held in r/invoices.

An issuer registers an aging schedule — age bands in BLOCKS and a loss rate per band — and then names the invoice ids to age. This realm reads every FACT from the invoice realm (amount, amount paid, due height, status) and prices the result with p/aging. The published report is frozen: the per-bucket unpaid totals, the per-bucket provision, the grand provision, and a snapshot of the schedule that priced it.

The caller supplies the selection, the chain supplies the facts

This is the central design constraint and it is a measured one, not a preference. r/invoices exports no per-issuer id enumerator — its internal `Iterate` is unexported and nothing above it can walk an issuer's records — so a realm here CANNOT derive "every open invoice" on its own. It therefore takes the id list from the caller.

The consequence is stated rather than hidden: **a report can under-report**. An issuer who omits an overdue invoice from the list gets a provision that is too small, and no check here can see the omission. What this realm does instead is publish a COVERAGE LINE with every report — how many ids were listed, how many were aged, how many were filtered and why, and how many open invoices and stored records the issuer held at that height — so a reader can compute the shortfall themselves. See Coverage.

Under-reporting is made VISIBLE here. It is not prevented, and a reader who treats a published provision as complete without reading the coverage line is reading it wrongly.

Ages are in BLOCKS

r/invoices stores `dueHeight` as a block height, so an age is a count of blocks and every schedule edge is a count of blocks. A "30/60/90 day" schedule is a conversion the ISSUER owns and this realm never performs: nothing here knows the block time, and compiling a guess in would make every published rate wrong by whatever the guess was off by.

This realm writes no ledger entry

A provision is an accounting entry — debit a credit-loss expense, credit an allowance — and this realm deliberately does not post it. r/books derives the owner of a book structurally from its caller, so a realm that called `PostMulti` would post into ITS OWN book and not the issuer's. There is no owner parameter to pass and no delegation path to use.

So ProposedEntry returns the entry as a string in the form r/books' `PostMulti` accepts, and the issuer posts it themselves from their own account. The realm stays keyless with respect to every ledger, which is the stronger position: it cannot write a wrong entry because it cannot write.

Custody

None. This realm holds no coins, has no withdrawal path, and refuses a user call that arrives carrying any. Nothing here moves value; a provision is a statement about value, which is not the same thing.

Limitations

  • **The report is a self-report of a self-report.** r/invoices' amounts are whatever an issuer typed; an aging of them inherits that entirely and then looks like an audited artifact. No audit happens here.
  • **It can under-report, by construction.** See above and Coverage.
  • **It cannot over-report an unlisted invoice**, because every fact comes from the chain — but it CAN over-report a *listed* one whose amount the issuer inflated in r/invoices in the first place.
  • **No write-off.** p/aging prices an expected loss and never removes an obligation; a receivable this report provisions at 100% is still a live invoice in r/invoices. Voiding is that realm's operation, not this one's.
  • **A frozen report does not track the invoices.** A payment landing after publication leaves the published figures untouched, which is what "frozen" means. There is no `Verify` here: re-deriving would need the id list, which is not stored (see Publish).
  • **Buckets are floored per bucket**, so the per-bucket column sums to the grand provision exactly and both are at or below the unrounded figure. p/aging's package doc carries the counterexample.
  • **No cross-issuer aggregate.** There is no enumerator and no total provision across issuers; such a figure would sum strangers' assertions.

r/invoices: /r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/invoices r/books: /r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/books p/aging: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/aging

Function

Publish

func Publish(cur realm, ids string) int64
Ask AI Claude ChatGPT

Publish freezes an aging report over the named invoices at the current height and returns its sequence number, which starts at 1 for each issuer.

`ids` is a comma-separated list of the caller's own invoice ids in r/invoices, at most MaxIDsPerCall of them. The caller must have registered a schedule first.

What each id is checked for

An id with no record for this issuer PANICS and fails the whole call. That is deliberate: a silently-dropped id is an under-report, and under-reporting is the one error this product cannot detect after the fact. The existence probe is a separate `HasInvoice` call rather than letting the next read panic, because r/invoices' own message is `"unknown invoice"` and does not say WHICH — unhelpful in a list of sixty-four.

An id that exists but is not ageable is filtered, counted, and reported:

  • not `open` — settled or voided, so nothing is owed. Counted `notOpen`.
  • open with nothing outstanding. Counted `zeroOut`.
  • open, owed, but `dueHeight > now`. Counted `notDue`.

The third one is not an optimisation. `aging.ReportAt` requires `DueAt <= now` for EVERY obligation and fails the whole call on the first that is not, so a report over a list containing one current invoice would produce nothing at all. Pre-filtering here is what makes "age everything I have" a usable call, and the count is what stops the filter being invisible.

The outstanding cross-check

`amount - paid` is computed here and compared against r/invoices' `Outstanding`. State its reach exactly, because the flattering reading is available: both sides are that realm's own two numbers, so this is a CONSTANT TRUE against any disagreement in ITS arithmetic and must never be quoted as cross-checking it. What it catches is a transcription error in THIS realm — reading `paid` where `amount` belongs in a six-value return, or subtracting the wrong way round — which is the one place in this path such an error could happen, and an inflated outstanding is an over-provision that still cross-foots.

The id list is not stored

A report keeps the figures and the coverage counts, not the ids. Storing 64 ids per report would roughly double the record for the sake of a `Verify` that could not mean much anyway: the invoices keep moving, so re-deriving would answer "do the figures still hold at today's height" and not "was this report right when made". r/statements documents the same distinction about its own Verify. The honest artifact is the frozen figure plus the coverage line.

Param

Command

# WARNING: This command is running in an INSECURE mode.
# It is strongly recommended to use a hardware device for signing
# and avoid trusting any computer connected to the internet,
# as your private keys could be exposed.

gnokey maketx call -pkgpath "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables" -func "Publish" -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -chainid "onyx-1" -remote "https://rpc.onyx.testnets.gno.land" ADDRESSgnokey query -remote "https://rpc.onyx.testnets.gno.land" auth/accounts/ADDRESS
gnokey maketx call -pkgpath "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables" -func "Publish" -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -broadcast=false ADDRESS > call.tx
gnokey sign -tx-path call.tx -chainid "onyx-1" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
gnokey broadcast -remote "https://rpc.onyx.testnets.gno.land" call.tx