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

Functions

BucketAt

func BucketAt(issuer address, seq int64, i int) (lo, hi int64, unbounded bool, rate, unpaid, provision int64)
Ask AI Claude ChatGPT

BucketAt returns one line item of a frozen report, youngest band first.

`lo` and `hi` are the band's age range in BLOCKS, half-open [lo, hi); when `unbounded` is true the band has no upper edge and `hi` is meaningless. An age equal to an edge belongs to the OLDER band. `rate` is in RateScale units, `unpaid` is the outstanding total that landed in the band, and `provision` is `floor(unpaid × rate / RateScale)`.

Params

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.BucketAt(,,)"

Result

BucketCount

func BucketCount(issuer address, seq int64) int
Ask AI Claude ChatGPT

BucketCount is how many age bands a frozen report has, which is the bucket count of the schedule that priced it and may differ from the issuer's current schedule.

Params

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.BucketCount(,)"

Result

Buckets

func Buckets(issuer address) int
Ask AI Claude ChatGPT

Buckets is how many age bands an issuer's current schedule has.

Param

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.Buckets()"

Result

Count

func Count(issuer address) int
Ask AI Claude ChatGPT

Count is how many reports an issuer has published. Zero for an address that never has, which is not an error.

Param

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.Count()"

Result

Coverage

func Coverage(issuer address, seq int64) (listed, aged, notOpen, zeroOut, notDue, dupes, openAt, recordsAt int)
Ask AI Claude ChatGPT

Coverage is the part of a report a reader must not skip.

This realm cannot enumerate an issuer's invoices (see the package doc), so a report covers exactly the ids the issuer named. These eight numbers are what let a reader judge whether that was everything:

  • `listed` — distinct ids in the call, after deduplication.
  • `aged` — ids that became obligations in the report. The provision covers these and nothing else.
  • `notOpen` — settled or voided, so nothing owed.
  • `zeroOut` — open with nothing outstanding.
  • `notDue` — open and owed, but not yet due at `PublishedAt`.
  • `dupes` — repeated ids dropped before aging.
  • `openAt` — `invoices.OpenOf(issuer)` at publication: how many open invoices the issuer actually held.
  • `recordsAt` — `invoices.RecordsOf(issuer)` at publication: records of any status, which is the figure r/invoices' per-issuer cap applies to.

The arithmetic to do: `aged + zeroOut + notDue` is how many OPEN invoices the report accounted for, so `openAt` minus that is **how many open invoices were left out**. A non-zero remainder means the provision is understated by an unknown amount, and no part of this realm can say by how much.

`listed = aged + notOpen + zeroOut + notDue` always holds; `dupes` is outside it, having been dropped before the filter.

ACCOUNTED FOR IS NOT THE SAME AS AT RISK

`notDue` counts toward "accounted for" and carries NO provision. That is right on its own terms — the invoice was examined, and p/aging deliberately refuses to bucket something that is not yet due — but it makes one reading wrong in a way the arithmetic above will not catch. A reader who takes `aged` (or the provision) as "the receivables at risk" while `notDue` is non-zero has measured a STRICT SUBSET of the open book: the omitted part is not omitted by mistake, it is merely not due yet, and it becomes due later with no further call to this realm.

So `OmittedOpen == 0` means "every open invoice was examined", never "every open invoice is provisioned". The second question is answered by reading `notDue` beside the provision, and the render pages print both for exactly this reason.

Params

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.Coverage(,)"

Result

Has

func Has(issuer address, seq int64) bool
Ask AI Claude ChatGPT

Has reports whether a report exists, so a reader can distinguish it from the panics the accessors below raise for an unknown one.

Params

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.Has(,)"

Result

HasSchedule

func HasSchedule(issuer address) bool
Ask AI Claude ChatGPT

HasSchedule reports whether an issuer has registered a schedule, so a reader can distinguish that from the panic ScheduleOf raises.

Param

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.HasSchedule()"

Result

Height

func Height() int64
Ask AI Claude ChatGPT

Height is the current chain height, which is the `now` a report published in this transaction would be aged against.

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.Height()"

Result

Latest

func Latest(issuer address) (int64, bool)
Ask AI Claude ChatGPT

Latest is an issuer's highest sequence number, and whether they have published at all. The second result matters: sequence numbers start at 1, so a zero first result means "never published" and never "report 0".

Param

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.Latest()"

Result

OmittedOpen

func OmittedOpen(issuer address, seq int64) int
Ask AI Claude ChatGPT

OmittedOpen is the coverage arithmetic as one number: how many of the issuer's open invoices the report did not account for, at the height it was published.

Zero is the answer a complete report gives. A positive answer means the provision is understated. A NEGATIVE answer is possible and is not a bug here: `openAt` is read after the loop, so an invoice settled or voided by a transaction between — impossible within one transaction, but the figure is a snapshot either way — would make the accounted set larger than the open count. It is returned signed rather than clamped, because a clamp would turn an inconsistency into a clean zero.

Params

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.OmittedOpen(,)"

Result

ProposedEntry

func ProposedEntry(issuer address, seq int64, expenseCode, allowanceCode string) string
Ask AI Claude ChatGPT

ProposedEntry returns the double entry that records a report's provision, in the `code:side:amount` form r/books' PostMulti accepts — and this realm does NOT post it.

It cannot. r/books derives a book's owner structurally from its caller, so a posting made from here would land in this realm's own book rather than the issuer's, and there is no owner parameter and no delegation path. The issuer posts this string themselves, from their own account, into their own book. That keeps this realm keyless with respect to every ledger: it cannot write a wrong entry because it cannot write.

The entry debits `expenseCode` and credits `allowanceCode` for the whole provision:

Example
1ProposedEntry(issuer, seq, "E-BADDEBT", "A-ALLOW")
2  → "E-BADDEBT:D:1250,A-ALLOW:C:1250"

Both codes go through [validCode], an ASCII whitelist, and the reason is the format itself: a code containing `:` or `,` would inject extra legs into a string this realm presents as its own arithmetic. Whether a code EXISTS, or is the right kind, in any ledger is not checked and cannot be — this realm reads no ledger.

A zero provision is refused rather than returned as a balanced pair of zeros, which would be a storage deposit spent to post nothing.

It is a PROPOSAL in the full sense: nothing here records that it was posted, posting it twice double-counts, and the allowance it credits is a contra asset whose previous balance this realm cannot see. An issuer provisioning monthly posts the DELTA, not this figure, and working that out is theirs.

Params

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.ProposedEntry(,,,)"

Result

Provision

func Provision(issuer address, seq int64) int64
Ask AI Claude ChatGPT

Provision is the allowance for credit losses a report concluded: the sum of the per-bucket column, each bucket floored.

Params

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.Provision(,)"

Result

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
  

PublishedAt

func PublishedAt(issuer address, seq int64) int64
Ask AI Claude ChatGPT

PublishedAt is the chain height a report was frozen at. It is also the `now` every age in it was measured against.

Params

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.PublishedAt(,)"

Result

Render

func Render(path string) string
Ask AI Claude ChatGPT

Render serves the index at "", an issuer's page at "<address>", and one report at "<address>/<seq>".

Every caller-supplied string reaching the output goes through the sanitizer. The surface is narrower here than in the sibling realms — this realm stores no free text, so the only untrusted bytes are the path itself — and it is still the one injection-shaped surface a realm with no custody has.

Param

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.Render()"

Result

ScheduleOf

func ScheduleOf(issuer address) (edges, rates string)
Ask AI Claude ChatGPT

ScheduleOf returns an issuer's CURRENT schedule in the comma-separated form SetSchedule accepts, so the round trip is exact.

This is the schedule that would price the issuer's NEXT report, not necessarily the one that priced their last. ScheduleUsed answers that.

Param

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.ScheduleOf()"

Result

ScheduleUsed

func ScheduleUsed(issuer address, seq int64) (edges, rates string)
Ask AI Claude ChatGPT

ScheduleUsed returns the schedule that PRICED a frozen report, in the comma-separated form SetSchedule accepts.

This exists because p/aging warns that a report carries no reference to its schedule and two schedules of the same bucket count will silently accept each other's reports. Here the reference is stored, so a reader can see the rates that produced the figure rather than inferring them from whatever the issuer has registered today.

Params

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.ScheduleUsed(,)"

Result

SetSchedule

func SetSchedule(cur realm, edges, rates string) int
Ask AI Claude ChatGPT

SetSchedule registers or replaces the caller's own aging schedule and returns its bucket count.

`edges` is a comma-separated list of block ages, positive and strictly increasing; `rates` is one more number than there are edges, each in [0, RateScale] and non-decreasing. So

Example
1SetSchedule("216000,432000,648000", "0,1000,2500,5000")

reads as: nothing provisioned under 216,000 blocks past due, 10% from there to 432,000, 25% to 648,000, 50% beyond. Every constraint is p/aging's and is validated by `aging.New` rather than re-checked here; a second copy of somebody else's validation is a second place for it to drift.

The owner is `cur.Previous().Address()` and there is no owner parameter, so authorization is structural rather than a check: there is no address to forge and no way to set a schedule for somebody else.

REPLACEMENT IS SAFE and that is a property of [report], not of this function: every published report snapshots the schedule that priced it, so a new schedule applies only to reports published after it. Nothing already frozen is re-priced.

Params

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 "SetSchedule" -args $'' -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 "SetSchedule" -args $'' -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
  

Source

func Source() string
Ask AI Claude ChatGPT

Source is the realm every figure here is read from, by value from its own exported constant.

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.Source()"

Result

Total

func Total() int
Ask AI Claude ChatGPT

Total is how many reports exist across every issuer. It is a count and nothing more; see the package doc on why there is no cross-issuer provision.

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.Total()"

Result

Totals

func Totals(issuer address, seq int64) (unpaid, provision int64, aged int)
Ask AI Claude ChatGPT

Totals returns a report's headline figures together, which is the shape a reader wants and the shape that cannot be assembled from two reads at different heights.

Params

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.Totals(,)"

Result

Unpaid

func Unpaid(issuer address, seq int64) int64
Ask AI Claude ChatGPT

Unpaid is the total outstanding the report aged. It is the denominator the provision is a fraction of, and it covers ONLY the aged invoices — not the issuer's whole receivable book. See Coverage.

Params

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/receivables.Unpaid(,)"

Result