Cosmic Bull

aging

gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/aging on onyx-1 testnet

pure packageprimitiveaccountinglive on onyx-1

Track overdue balances and outstanding obligations.

Grouping what is overdue by how late it is turns a list of unpaid balances into an expected loss you can book.

In detail curated

A pure aging/credit-loss primitive: it partitions a caller's unpaid obligations by how long each has been past due and prices the resulting schedule into a single expected loss (a Provision, never an 'allowance' -- that word is taken by p/spendbook and GRC20). Nothing reads the chain, holds state, or knows what an invoice is. The provision is the SUM OF PER-BUCKET FLOORS, not one floor at the end, deliberately: ProvisionByBucket is the line-item column and Provision is DEFINED as its sum, so the two can never disagree -- the same discipline p/depreciation uses for Charge/CarryingAt. The cost is stated and pinned: the integer provision is NOT monotone in `now` (a counterexample is in the doc and TestFloorIsPerBucketNotAtTheEnd), it can fall by up to one unit per bucket, and a realm gating on provision>=last will eventually be wrong by one; only the EXACT rational provision is monotone, which is why New requires non-decreasing rates. Edges must be POSITIVE and strictly increasing (the first-edge-0 off-by-one that overstated the degenerate overdue case by every invoice-due-today was found by the pre-deploy audit, AUDIT.md Y2). A not-yet-due obligation is refused (ErrNotYetDue), not bucketed. Overflow is guarded in both reachable places -- ReportAt bounds the running total to the pricing precondition so a report it returns always prices, closing the Y1 gap where ReportAt accepted what Provision then refused by a factor of RateScale. MaxBuckets 16, MaxObligations 1024 (chunking loses up to one unit per bucket per chunk). Imports stdlib errors only.

Use it

A pure package has no state and no transactions: you use it by importing it.

Import it

import "gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/aging"

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.

Called from real code

Not an example written for this page. These lines are quoted from receivables, a deployed package in this portfolio that imports this one as aging, out of the committed file whose sha256 onyx-1 testnet attested. They compile because they are compiled.

s, err := aging.New(e, r)
onyx/r/receivables/receivables.gno line 398, onyx-1 testnet

Deployments

onyx-1 testnet onyx-1

RenderedSourceCall builder

chain-attested
Import pathgno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/aging
Namespaceg1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3
Deployed at height362,562
Deploy transaction3f1695ea7fa2354c15ac7e7950902872277de183ffe60e57835d6a452e13c462 look it up on the RPC
Deployerg1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3
Gas used44,637,364
Storagenot attested per package on this chain — see the limitations below
Files on chainaging.gno gnomod.toml
Deployed bytesaging.gno — 32,972 bytes
sha25677510a46697806a6191665c1afacb5ca5500a9f71a53c2dc07a8298f331ae588
Realm addressnone — A /p/ package is not a realm: it holds no state, custodies no coins and is never a transaction sender. The pkgPath derivation would still produce a value; recording one would name nothing.
Used byr/receivables

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/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/aging$download&file=aging.gno' | shasum -a 256

Expected: 77510a46697806a6191665c1afacb5ca5500a9f71a53c2dc07a8298f331ae588 — 32,972 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.

API

chain-derived 2 exported functions, 3 types, 10 methods.

This is a /p/ package: you import it, you do not call it in a transaction. gnoweb's $help shows only exported top-level functions, so the types and methods below do not appear there at all — which is why this reference exists.

Overview

Package aging partitions a set of unpaid obligations by how long each has been past due, and prices the resulting schedule into a single expected loss.

A realm holds the obligations — invoices, loans, dues — and asks this package two questions: how is what I am owed distributed across age bands, and what should I expect not to collect. Nothing here reads the chain, holds state, or knows what an invoice is, so a realm can test a year of its own receivables without a node.

s, _ := New([]int64{30, 60, 90}, []int64{0, 1000, 2500, 5000}) rep, err := s.ReportAt(obs, now) // four bucket totals and their sum loss, err := s.Provision(rep) // the expected credit loss

The two steps are separable on purpose: the report is the line items a reader sees, and the provision is the one number that goes in a ledger. Schedule.ProvisionFor does both in one call for a caller that only wants the number.

The output is a provision, and never an allowance

The textbook name for this number is "allowance for doubtful accounts", and that word is not available here. `allowance` already means a *spending* allowance across this portfolio — including p/spendbook's `Allowance` type — and it means an approve/transferFrom quota across the wider ecosystem's GRC20 realms. A type named `Allowance` in a package imported beside either of those would read as the wrong thing at every call site, so the loss number is `Provision` throughout. See DISCOVERY.md § 4.

Why the floor is taken per bucket, and exactly what that costs

The provision is the sum of per-bucket floors:

Provision = Σ floor(bucketᵢ × rateᵢ / RateScale)

not one floor taken at the end of an exact rational total. Those are different numbers, and which one a package shipped is invisible in any test that happens to use round rates.

Per-bucket is chosen because Schedule.ProvisionByBucket is the line-item column a reader sees next to the bucket totals, and line items that do not add up to the stated total are a bookkeeping defect regardless of which total is "more accurate". Schedule.Provision is *defined* as the sum of that column, so the two cannot disagree — the same discipline p/depreciation uses to define `Charge` in terms of `CarryingAt`.

The cost of that choice is a real and measurable weakening of the obvious invariant, and it is stated here because the discovery record for this package asserted the strong form and the strong form is false:

FALSE: for a fixed unpaid set, Provision is non-decreasing in now.

The counterexample is small enough to state in full. Take edges `[30]` and rates `[5000, 5000]` — one boundary, the same 50% either side — and two obligations of amount 1. While both sit in bucket 0 the buckets are `[2, 0]` and the provision is `floor(2×5000/10000) = 1`. Let one age past 30: the buckets become `[1, 1]`, each floors to `0`, and the provision is `0`. **It fell as the receivables got older**, with no payment and no change of rate. One floor at the end would have returned 1 in both states.

What is true, and what [TestProvisionNearlyMonotone] asserts:

The random sweep in [TestProvisionNearlyMonotone] reports a far smaller largest drop than that, and the difference is a property of the sweep rather than of the package: with randomly drawn rates the shape that loses the most to the floors essentially never comes up. Both run, and the constructed one is the one the bound is quoted from.

So the provision is safe to re-run and safe to re-run often — it cannot wander upward, and it cannot fall by more than a rounding unit per bucket. It is not a monotone function, and a realm that gates on `provision >= lastProvision` will eventually be wrong by one. [TestFloorIsPerBucketNotAtTheEnd] pins the counterexample itself, so a later "fix" that quietly moves to a single floor at the end fails the suite rather than silently changing every published number.

Why the rates must be non-decreasing

New refuses a rate schedule that falls anywhere. Two reasons, and the second is the load-bearing one:

Why a not-yet-due obligation is refused

An obligation with `DueAt > now` has no age, and the choices are to refuse it or to put it in the youngest bucket. Bucketing it would be silent and would inflate every provision by whatever the youngest rate is applied to money that nobody is late paying. So it is ErrNotYetDue, following `r/books`' `TestKindOfReportsInvalidRatherThanBucketing` — refused, not silently bucketed.

The "current" column of a conventional aging report is therefore the caller's, and the caller already has it: on r/invoices it is `OutstandingFrom - OverdueFrom`. This package describes the past-due tail and declines to guess about the rest.

The degenerate two-bucket case, and the off-by-one in it

A two-bucket schedule with edge `1` and rates `{0, RateScale}` reproduces r/invoices' `OverdueFrom` exactly, over the obligations whose `DueAt` has arrived: everything strictly past due is priced at 100% and everything else at 0%.

The edge is **1 and not 0**, which is worth the sentence because DISCOVERY.md § 1 says 0 and is wrong. `isOverdue` is `now > dueHeight`, strictly — an invoice at exactly its due height is due and not yet late, so that invoice has age 0 and must land in the *unpriced* bucket. Edge 0 makes bucket 0 empty and prices it at 100%, overstating the loss by every invoice due precisely today. An off-by-one on a half-open boundary is the defect class this package is most exposed to, and the discovery record shipped one in the very claim that identified the consumer.

Edge 0 is now refused outright — ErrBadEdges, see check — so the overstatement described above is no longer expressible rather than merely documented. It was expressible, and accepted, until the pre-deployment audit found it: AUDIT.md Y2. A paragraph naming a defect class while the validator admitted an instance of it is the shape worth remembering here.

Note also what an `Amount` is in that correspondence: `OverdueFrom` sums `amount - paid`, the unpaid remainder. [Obligation.Amount] is that remainder and not a face amount. A caller that passes face amounts prices money it has already collected.

What bounds the work

Bucket lookup is a linear scan over the edges, which is cheaper than a binary search at these sizes and is the shape an auditor can check by reading. MaxBuckets is 16, so there are at most 15 edges, and MaxObligations is 1024: one Schedule.ReportAt therefore performs at most 15,360 comparisons plus 1024 additions, and Schedule.Provision performs at most 16 multiplications and divisions. Every exported method re-runs `check`, which costs at most 31 further comparisons.

Those are the figures, not an assertion that they are affordable. A realm that ages several portfolios in one transaction, or does it inside a `Render`, must size them against the gas it has. This package bounds the work; it does not price it.

What this package does not claim

p/spendbook: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/spendbook p/depreciation: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/depreciation r/invoices: /r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/invoices p/twap: /p/moul/x/twap/v0 p/moul/x/games/accrual: /p/moul/x/games/accrual/v0

Imports

Constants and variables

MaxBuckets bounds the number of age bands, and so bounds the edge scan in Schedule.BucketOf and the loops in Schedule.ReportAt and Schedule.Provision. 16 is past the point where a band is meaningful: a conventional aging report uses four or five.

The minimum is 2, and that is a refusal rather than an omission. A one-bucket schedule has no edges, applies one rate to the whole balance, and is a flat-rate provision rather than an aging schedule — legitimate arithmetic, better said as `floor(total * rate / RateScale)` at the call site, where a reader can see that no aging happened.

const MaxBuckets = 16

MaxObligations bounds how many obligations one Schedule.ReportAt call will accept, following p/lots' own 1024 and for the same reason: the caller's slice length is the only thing that bounds the loop, and an unbounded loop over caller-supplied data is a gas hazard a realm cannot size in advance.

What the bound costs: a realm with more than 1024 open obligations must report in chunks and add the provisions, and that sum is **not** the provision of the whole. Each chunk floors its own buckets, so chunking loses up to one unit per bucket per chunk — the same effect the package doc bounds for the clock, arriving by a different route. A realm that needs the exact figure over a larger pool must accumulate the bucket totals itself and call Schedule.Provision once on the combined report, which is supported: `Provision` takes a Report and does not care where it came from.

p/lots: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/lots

const MaxObligations = 1024

RateScale is the denominator every loss rate is expressed against, so a rate is in basis points: 0 is 0%, 2500 is 25%, 10000 is 100%.

It is a constant rather than a per-schedule field on purpose. A schedule carrying its own denominator lets two schedules in one realm be priced on different scales, and the first symptom of that is a provision wrong by a factor of a hundred with every individual number looking reasonable.

const RateScale int64 = 10000
var (
	// ErrBadBucketCount is returned when a schedule would have fewer than 2
	// or more than MaxBuckets buckets. The bucket count is len(edges)+1, so
	// this is a statement about the edge slice: it must hold between 1 and
	// MaxBuckets-1 entries. A nil or empty edge slice lands here, which is
	// what makes the zero Schedule invalid.
	ErrBadBucketCount = errors.New("aging: bucket count must be in [2, MaxBuckets]")

	// ErrBadEdges is returned when the edges are not positive and strictly
	// increasing. Equal adjacent edges would define an empty bucket, which
	// is not an error in arithmetic but is always an authoring mistake: it
	// silently reduces the schedule's resolution while every bucket total
	// still reports.
	//
	// Positive and not merely non-negative, for the same reason one step
	// earlier: a first edge of 0 makes bucket 0 the empty range [0,0). The
	// rationale here is the whole of the argument for refusing it, which is
	// why the check now reaches the first edge too; see check.
	ErrBadEdges = errors.New("aging: edges must be positive and strictly increasing")

	// ErrBadRateCount is returned when len(rates) != len(edges)+1. The
	// off-by-one here is the most likely way to build a wrong schedule that
	// still looks right, so it is named apart from ErrBadRate: a reader
	// seeing this error knows to count, not to look at values.
	ErrBadRateCount = errors.New("aging: need exactly one rate per bucket, which is len(edges)+1")

	// ErrBadRate is returned when a rate is negative or exceeds RateScale.
	// A rate above 100% would provision more than is owed; a negative rate
	// would provision a gain.
	ErrBadRate = errors.New("aging: each rate must be in [0, RateScale]")

	// ErrRatesDecrease is returned when the rates fall anywhere across the
	// buckets. See the package doc: it is both the domain claim and the
	// precondition for the only monotonicity this package offers.
	ErrRatesDecrease = errors.New("aging: rates must not decrease as buckets age")

	// ErrNotYetDue is returned for an obligation whose DueAt has not
	// arrived, and by BucketOf for a negative age — which is the same
	// condition expressed in age terms. Refused rather than bucketed; see
	// the package doc.
	ErrNotYetDue = errors.New("aging: obligation is not yet due and has no age")

	// ErrBadAmount is returned for an obligation amount that is not
	// positive. Zero is refused as well as negative: a zero obligation
	// consumes one of the MaxObligations slots and changes nothing, so it
	// is a caller bug worth surfacing rather than a free no-op.
	ErrBadAmount = errors.New("aging: obligation amount must be positive")

	// ErrTooManyObligations is returned when more than MaxObligations are
	// passed in one call. See MaxObligations on chunking, and on why the
	// chunked sum is not the same number.
	ErrTooManyObligations = errors.New("aging: more than MaxObligations in one report")

	// ErrBadReport is returned by Provision when a report's bucket count
	// does not match the schedule's. It is the only structural check
	// available: a Report carries no identity, so two schedules with the
	// same bucket count accept each other's reports. A Report must
	// therefore not outlive the Schedule that produced it; prefer
	// ProvisionFor, which makes that impossible.
	ErrBadReport = errors.New("aging: report bucket count does not match the schedule")

	// ErrCrossFoot is returned by Provision when a report's bucket totals
	// do not sum to its stated Total. Reports built by ReportAt always
	// cross-foot by construction, so this fires only for a caller-built or
	// mutated Report — which is exactly why it is an error here and not
	// merely an assertion in the test suite.
	ErrCrossFoot = errors.New("aging: report buckets do not sum to its total")

	// ErrBadBucketIndex is returned by the per-bucket accessors for an
	// index outside [0, NumBuckets).
	ErrBadBucketIndex = errors.New("aging: bucket index out of range")

	// ErrOverflow is returned when the arithmetic would wrap: an age that
	// does not fit in an int64, a bucket total or report total that exceeds
	// what can be priced at RateScale, or a sum of amounts that does not
	// fit. Every other overflow is excluded by the construction-time
	// checks; see check.
	//
	// The pricing bound is enforced in both places it is reachable, which is
	// deliberate and not redundant: ReportAt bounds the running total so
	// that a report it returns can always be priced, and ProvisionByBucket
	// bounds each bucket because it also accepts caller-built reports it
	// never produced. Dropping either one is a reachable defect, and each
	// has its own sabotage case.
	ErrOverflow = errors.New("aging: int64 overflow")
)

Types

type Obligation

type Obligation struct {
	Amount int64
	DueAt  int64
}

Obligation is one thing owed, as the pair this package needs: how much is still unpaid and when it fell due.

Amount is the unpaid remainder, not a face amount — on r/invoices that is `amount - paid`. DueAt is in the caller's time unit, consistently with the `now` passed to Schedule.ReportAt and with the schedule's edges.

It is a value and carries no identity. This package cannot tell two obligations apart and does not try: the same obligation passed twice is counted twice, and deduplication is the caller's, because only the caller knows what makes one of its receivables the same as another.

type Report

type Report struct {
	// Buckets holds the unpaid total per bucket, index 0 being the
	// youngest. Its length is the schedule's NumBuckets.
	Buckets []int64

	// Total is the sum of Buckets. It is stored rather than recomputed so
	// that Provision has something to cross-foot against.
	Total int64
}

Report is one aging snapshot: the unpaid total in each bucket, youngest first, and their sum.

Its fields are exported so a caller can render the line items, and so a realm can accumulate buckets across several Schedule.ReportAt calls and price them in one go — see MaxObligations. The cost of exporting them is that a Report can be built or mutated into something inconsistent, which is why Schedule.Provision re-derives nothing and instead validates: bucket count against the schedule, each bucket non-negative, and the cross-foot.

A Report carries no reference to the Schedule that made it. Two schedules with the same bucket count will accept each other's reports and silently price them on the wrong rates, so a Report must not outlive its Schedule. Schedule.ProvisionFor exists to remove the opportunity.

Exported fieldTypeDoc
Buckets[]int64Buckets holds the unpaid total per bucket, index 0 being the youngest. Its length is the schedule's NumBuckets.
Totalint64Total is the sum of Buckets. It is stored rather than recomputed so that Provision has something to cross-foot against.

type Schedule

type Schedule struct {
	// edges holds the bucket boundaries, strictly increasing and
	// non-negative. Bucket i is the half-open age range
	// [edges[i-1], edges[i]), with bucket 0 starting at 0 and the last
	// bucket unbounded above.
	edges []int64

	// rates holds one loss rate per bucket in units of RateScale,
	// non-decreasing. len(rates) == len(edges)+1.
	rates []int64
}

Schedule is an aging schedule: the age boundaries and the loss rate for each resulting band.

Its fields are unexported and the accessors copy, which is a deliberate departure from p/depreciation's all-scalar exported Schedule. A schedule holding slices is not safely copyable: a caller that kept the slice it passed to New could mutate the edges after validation and every subsequent call would price against a schedule that never passed `check`. Construct with New.

Schedule.BucketOf

func (s Schedule) BucketOf(age int64) (int, error)

BucketOf returns the index of the bucket an age falls in: 0 for the youngest, NumBuckets-1 for the unbounded oldest.

The partition is half-open, [lo, hi), which is what makes it exhaustive and disjoint over every non-negative age with no boundary shared by two buckets. An age equal to an edge belongs to the **older** bucket, so edge 30 means "30 and over is in the next band".

A negative age is ErrNotYetDue, the same error Age returns for the same condition.

Schedule.BucketRange

func (s Schedule) BucketRange(i int) (lo, hi int64, unbounded bool, err error)

BucketRange returns the half-open age range of bucket i as [lo, hi), together with whether it is unbounded above — which is true for the last bucket and only the last bucket, where hi is meaningless.

It exists so that a caller rendering line items does not have to reconstruct the half-open convention from Schedule.Edges. That reconstruction is where an off-by-one lives, and this package has already shipped one of those in its own discovery record; see the package doc.

Schedule.Edges

func (s Schedule) Edges() []int64

Edges returns a copy of the bucket boundaries.

Schedule.NumBuckets

func (s Schedule) NumBuckets() int

NumBuckets is the number of age bands, which is one more than the number of edges. It returns 0 for an invalid schedule, including the zero value, so a caller that skipped Schedule.Valid gets a number that reads as wrong rather than one that reads as a one-bucket schedule.

Schedule.Provision

func (s Schedule) Provision(r Report) (int64, error)

Provision returns the expected credit loss for a report: one number for a ledger.

It is defined as the sum of Schedule.ProvisionByBucket, so the single figure and the line items can never disagree — the same reason p/depreciation defines `Charge` as a difference of `CarryingAt`. The sum cannot overflow: each term is at most its bucket, because every rate is at most RateScale, and the buckets were established to sum without overflow.

Schedule.ProvisionByBucket

func (s Schedule) ProvisionByBucket(r Report) ([]int64, error)

ProvisionByBucket returns the expected credit loss per bucket: the line-item column, youngest first.

Each entry is `floor(bucketᵢ × rateᵢ / RateScale)`, the floor taken per bucket so that this column sums to exactly what Schedule.Provision reports. See the package doc for what that choice costs, with the counterexample in full.

The report is validated rather than trusted: its bucket count must match the schedule, every bucket must be non-negative, and the buckets must sum to the report's stated Total (ErrCrossFoot). A report from Schedule.ReportAt satisfies all three by construction.

A fourth property is needed and is NOT one of the three: every bucket must be at most maxInt64/RateScale, or the multiplication below wraps. Say it separately because the three-item sentence above was true, complete about the three, and still read as "a ReportAt report always prices" — which it did not guarantee. ReportAt now bounds its running total by the same quantity, so the implicature is true; it is true because of a line in ReportAt, not because of anything here. See AUDIT.md Y1.

Schedule.ProvisionFor

func (s Schedule) ProvisionFor(obs []Obligation, now int64) (int64, error)

ProvisionFor is Schedule.ReportAt followed by Schedule.Provision, for a caller that wants the number and not the line items.

Prefer it over holding a Report: a Report carries no reference to the schedule that produced it, so a stored one can be priced against a different schedule of the same bucket count with no error. Doing both steps in one call removes the opportunity.

Schedule.Rates

func (s Schedule) Rates() []int64

Rates returns a copy of the per-bucket loss rates, in units of RateScale.

Schedule.ReportAt

func (s Schedule) ReportAt(obs []Obligation, now int64) (Report, error)

ReportAt partitions the obligations by age at time `now` and returns the per-bucket unpaid totals and their sum.

Every obligation must be due — `DueAt <= now` — and must carry a positive amount; the first that is not fails the whole call, because a partial report that silently dropped a receivable is worse than no report. Which obligation failed is not in the error: a sentinel carries no index, and wrapping was not worth a dependency here. A caller that needs to locate the bad entry has Age and [Obligation.Amount] and can find it in one pass.

An empty slice is legitimate and returns a zeroed report, not an error: a realm with nothing outstanding has a provision of zero and should not have to special-case it.

Cost: at most MaxObligations × (MaxBuckets-1) comparisons; see the package doc.

Schedule.Valid

func (s Schedule) Valid() bool

Valid reports whether the schedule is well-formed. A zero Schedule is not.

Functions

Age

func Age(dueAt, now int64) (int64, error)

Age returns how long an obligation has been due: now - dueAt.

Zero is a legitimate age and means "due exactly now, not yet late". A `dueAt` in the future is ErrNotYetDue rather than a negative age; see the package doc on why it is refused rather than bucketed.

The span is measured in uint64 and the reason is the whole correctness argument, adopted verbatim from p/depreciation's `Periods`. `now >= dueAt` is enforced first, so the true difference is non-negative; its largest value is maxInt64 - minInt64 = 2^64 - 1, which does not fit in an int64 and fits exactly in a uint64. int64 subtraction wraps two's-complement, so the bit pattern of now - dueAt is already the low 64 bits of the true difference, and reading it as a uint64 recovers that difference with no loss and no reachable wrap. ErrOverflow therefore means exactly one thing here — the true age exceeds maxInt64 — and the subtraction needs no guard.

New

func New(edges, rates []int64) (Schedule, error)

New validates and returns a schedule. The edges are the age boundaries, positive and strictly increasing; the rates are one per bucket, so one more than there are edges, each in [0, RateScale] and non-decreasing.

Positive, so the smallest legal first edge is 1 and the smallest legal schedule is `New([]int64{1}, []int64{0, RateScale})` — which is exactly the degenerate strictly-overdue schedule the package doc describes. A first edge of 0 is ErrBadEdges: it would define an empty bucket 0.

New([]int64{30, 60, 90}, []int64{0, 1000, 2500, 5000})

reads as: nothing provisioned under 30 past due, 10% from 30 to 59, 25% from 60 to 89, 50% from 90 on.

Both slices are copied, so the caller may reuse or mutate its own.


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
Importserrors
First-party dependenciesnone

Known limitations

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

curated

Source

Source fileonyx/p/aging/aging.gno at commit c2e10d17ffbf in the project repository (not public — the digest command above is the check that needs no repository)
Matches the deployed bytesyes — byte-identical