Cosmic Bull

inventory

gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/inventory on onyx-1 testnet

realmapplicationaccountinglive on onyx-1

Track stock on hand and the cost of what you have sold.

Cost of goods sold is drawn from the lots actually consumed, so margin is read out of the records rather than asserted.

In detail curated

A per-owner inventory sub-ledger whose cost of goods sold is DERIVED from the lots on hand, never supplied: it holds a p/lots.Pool per SKU and Ship takes a QUANTITY and returns the cost its costing method charged against those lots, so no entrypoint anywhere accepts an amount. The chart is THREE fixed accounts -- 1300 Inventory (asset) debited on receipt against 3900 Inventory contributed (equity), and 5000 Cost of goods sold (expense) debited on shipment against 1300 -- opened at OpenRegister with a hard cap of three so the chart cannot grow, which is what makes this realm its book's only writer and the cross-foots EXACT. 3900 is a PLUG this realm is forced to pick and says so in its own doc and Render: a debit to 1300 needs a credit leg, every candidate asserts something unobservable, and the contribution is the only one coherent without a follow-up event -- a register whose goods were bought on credit understates liabilities by exactly balance(3900). Costing method is per item and FIXED at creation via AddFIFO/AddLIFO/AddWAVG; a WAVG item merges into one lot and so escapes the live-lot cap but gains a merge-overflow refusal that is a property of its accumulated history, which is why LotCapacity is the wrong headroom figure for it. A zero-cost receipt and a zero-derived-cost shipment update the pool and post NOTHING, preserving the identities exactly. The weight-bearing checks are per-item Invariant (postedCOGS == pool.ChargedCost(), two independently maintained figures) and the three register cross-foots (sum over items == the ledger balance, EXACT), all FREE reads so any third party verifies a register from vm/qeval with no key -- the same choice r/assets makes and r/books does not. SKUs enumerates the items, closing the discovery gap r/books has. refuseCoins refuses an envelope; no custody, no write-off (shrinkage is indistinguishable from a sale, named not hidden), no revenue, no period close. Two IsCurrent() guards on non-crossing helpers are no-ops for every present caller, kept because the realm is immutable after addpkg.

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/inventory.Height()' \
  --remote https://rpc.onyx.testnets.gno.land:443

Height 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 7 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/inventory"

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 pathgno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/inventory
Namespaceg1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3
Deployed at height358,572
Deploy transaction9a712373c4049922f275542806c4c3c4dfffcdd938a95652a3db4255f741c8e0 look it up on the RPC
Deployerg1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3
Gas used76,848,736
Storagenot attested per package on this chain — see the limitations below
Files on chaininventory.gno gnomod.toml
Deployed bytesinventory.gno — 58,192 bytes
sha25691a2d2dcd86dcfe4b6307a17dfc0a4525c4aaae7eafef5f7e5f3293cdd9b37aa
Realm addressg1dx44e0g369xjntuqz4w8djlh8qazhdh3syxvyz derived, never confirmed against the realm

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

Expected: 91a2d2dcd86dcfe4b6307a17dfc0a4525c4aaae7eafef5f7e5f3293cdd9b37aa — 58,192 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.

API

chain-derived 46 exported functions.

Every function below deep-links to gnoweb's call builder, which generates a ready-to-run gnokey maketx call for it:

AccountKindAddFIFOAddLIFOAddWAVGAddedAtBalanceCOGSDebitCapacityChargedContributedCreditCostOnHandCrossFootCrossFootCOGSCrossFootContributedCrossFootInventoryEntryCountEquationHasItemHasRegisterHeightInvariantInvariantReceiptsInventoryDebitIsBalancedItemCountItemCrossFootItemCrossFootQtyItemNameLotAtLotCapacityLotCountMethodOnHandOpenRegisterOpenedAtRawBalanceReceiptsReceiveReceivedRederiveRegistersRenameRenderSKUsShipTotals

Overview

Package inventory is a per-owner inventory sub-ledger whose cost of goods sold is DERIVED from the lots on hand rather than asserted by a caller.

What this realm is for

A general ledger takes a debit code, a credit code and an amount from whoever is posting. That is the right design for a ledger, and it means gno.land/r/.../books structurally CANNOT know that an entry was a shipment, or that the cost charged for that shipment is the cost the goods were actually bought at. Nothing stops a shipper from booking a flattering number, and nothing in the book can tell afterwards.

This realm closes that gap for one class of entry. It holds each item's cost lots as state, and when goods ship it computes the charge from those lots by the item's declared costing method. The amount is never a parameter: Ship takes a QUANTITY and returns the cost it derived.

The resulting claims are narrow and checkable by anyone, which is the point. Per item, from p/lots' own cross-foot:

receivedCost == chargedCost + costOnHand

and, across a whole register, against the ledger the realm posted to:

balance(1300) == sum of costOnHand over items balance(5000) == sum of chargedCost over items balance(3900) == sum of receivedCost over items

All four are exported — ItemCrossFoot, CrossFootInventory, CrossFootCOGS, CrossFootContributed — and all are FREE reads, deliberately; see "Why the invariants are free reads" below.

Note what the three ledger identities are NOT. Each compares a walk of the lots against an account balance, and the two are maintained by different code: the pool counts for itself inside p/lots, while this realm posts to the book. A disagreement is therefore a real signal and not a tautology. That is the same reason p/lots keeps counters beside its lots.

The chart is three accounts and is fixed

1300 Inventory (asset, debit-normal) 5000 Cost of goods sold (expense, debit-normal) 3900 Inventory contributed (equity, credit-normal)

A receipt is debit 1300 / credit 3900. A shipment is debit 5000 / credit 1300 for the DERIVED cost. There is no OpenAccount entrypoint and the underlying book is created with a hard cap of three accounts, so the chart cannot grow. The accounting equation offsets at 0 = 0 on an empty register and after every posting, because 1300 + 5000 == 3900 is the register-wide restatement of each pool's own cross-foot.

3900 IS A PLUG, AND THIS REALM IS FORCED TO PICK ONE

This is the design's one unavoidable assertion about the world, so it is stated here rather than buried.

gno.land/r/.../assets faced the same question about an asset's cost basis and answered by NOT BOOKING IT: the only honest credit legs were a liability ("2000 Acquisition payable") or an equity plug ("3000 Contributed capital"), both of which assert how the asset was funded, which that realm does not know. It kept the cost in register state as data instead.

That answer is not available here, and the difference is structural rather than a matter of taste. The product IS cost of goods sold. A shipment debits 5000 and credits 1300, so 1300 must carry a balance, so the receipt must reach the book — and a debit to 1300 needs a credit leg. The choice is therefore not "book it or not" but "which plug", and every candidate asserts something unobservable:

So 3900 was chosen because it needs no follow-up transaction to stay coherent, not because it is true. Read it as "goods arrived from somewhere this ledger does not model". A register whose goods were in fact purchased on credit is understating liabilities and overstating equity by exactly balance(3900), and no code path here can detect that. Pairing this sub-ledger with a real payables ledger is out of scope for v1 and is the honest fix.

Costing method is per item and is FIXED at creation

An item is added by AddFIFO, AddLIFO or AddWAVG — three entrypoints rather than one taking a method code, following r/assets' Place / PlaceDeclining precedent, so a caller never has to know an enum's numbering.

p/lots fixes the method at pool construction and documents why: changing it mid-life would recost nothing already charged but would change what the next shipment costs, which is a restatement a sub-ledger must not be able to make silently. This realm inherits that and adds nothing: there is no entrypoint that changes an item's method. An owner who must switch opens a new SKU and discloses it.

The three methods differ observably, which is the point of having them: FIFO and LIFO charge different amounts whenever unit costs differ, and WAVG re-anchors every receipt into a single merged lot so it holds exactly one lot and can never hit the per-item lot cap.

That exemption is not a free win, and the trade is the other way round from how it reads. WAVG escapes the LOT cap and in exchange acquires a refusal FIFO and LIFO do not have: a merged lot must satisfy p/lots' per-lot overflow precondition `Cost <= maxInt64/Qty`, and two lots that each satisfy it separately can merge into one that does not. So under WAVG a receipt can be refused for the pool's ACCUMULATED HISTORY rather than for anything about the receipt — the same receipt a FIFO item would have taken. It is a far corner of the arithmetic and not a practical bound, but it is the reason LotCapacity is the wrong thing to watch for a WAVG item: its figure is a bound WAVG will never approach, so a caller sizing headroom from it is reading the one limit that does not apply. See p/lots' Pool.Receive.

A ZERO-COST POSTING IS SKIPPED, AND THAT IS NOT A SHORTCUT

p/journal rejects a posting of zero (ErrInvalidAmount) — correctly; a zero entry is not an event. But both of this realm's postings can legitimately be zero:

In both cases the pool is updated and NOTHING is posted. The identities above are preserved exactly, because a zero contribution to the ledger side is matched by a zero contribution to the walk side. The same choice r/assets makes for a degenerate declining schedule, for the same reason.

What this realm does NOT do

Why the invariants are free reads, and what the bound costs

r/books exposes Rederive as an owner-only crossing function, to contain the cost of a full re-walk. This realm diverges, deliberately and for the same reason r/assets does: the derivation IS the product, and a claim only its owner can check is not a claim worth making. Rederive, ItemCrossFoot, the three register cross-foots and Render are all free reads, so any third party can verify a register from vm/qeval with no key.

MaxLotsPerItem is 64 against p/lots' own MaxLotsLimit of 1024, and the gap is not timidity. p/lots' audit left the cost of its O(live lots) walks explicitly UNPRICED and recorded that a realm rendering many pools must measure its own gas. This realm is that realm, so the figures are measured rather than reasoned about, under the pinned onyx toolchain.

There are TWO cost axes, not one, and this paragraph used to name only the first: MaxItemsPerRegister*MaxLotsPerItem live lots, and separately MaxEntriesPerRegister postings. It called 64 items of 64 live lots "the worst case this realm can hold", which is false — a register can sit at the lot cap and the entry cap AT ONCE. It was also claimed in review that the two are mutually exclusive by construction; that is wrong too. A one-unit FIFO shipment draws from the oldest lot, which holds 100 units, so 64 of them never exhaust it: 64 items x 64 receipts then 64 items x 64 one-unit shipments leaves 4096 live lots and exactly 8192 entries. The figures below were taken on a register asserted in-fixture to sit at all three caps simultaneously, so the combined shape is measured and not argued about.

Free reads, net of a trivial-read baseline on the same register:

Rederive 506,486,523 Render, register page 186,179,474 CrossFoot 96,213,741 Render, item page 6,467,621 Render, index 192,716

The dearest is 16.9% of the 3,000,000,000 maxGasQuery. The two axes do not compose in any single read's price, because no single read walks both: Rederive walks postings and reads no lot, while CrossFoot and Render walk lots and read three accounts plus EntryCount. Measured rather than inferred — Rederive at the lot cap agrees with the same read on a lot-cheap register (506,495,345) to 0.002%, which is what proves the lot axis does not price it. So the ceiling is the MAX of the two axes and never their product.

A bound is not a price, and one item per call is what keeps the write side cheap: Receive and Ship each touch exactly one pool. Measured on the same toolchain, net of the same kind of baseline:

Receive creating the 64th lot 930,676 Ship of all OnHand, dropping all 64 lots 11,837,593 Receive refused at the lot cap 304,454

The dearest write is 2.3% of the dearest read, so the read side is what the caps exist for. See DEPLOY_READINESS.md for the transcript, the per-header GAS pairing rule, and the two limits of these figures: they came from throwaway test files that were deleted rather than committed, so they are reproducible but NOT guarded by the suite; and gno test's GAS line covers a whole test function, so a figure is only a call's own price when the fixture that enables it lives in a separate test. Both halves bit: the refused receipt first measured at 53,123,019 — 174x its real price — because its 64-lot fixture shared its GAS line.

Deploy order

This realm imports p/journal and p/lots, so on any chain the order is p/journal, p/lots, then this realm. The order is stated and the deployment STATE of either import deliberately is not: a sentence about what is deployed is true at signing time at best, and addpkg freezes it — r/assets carries exactly that fossil. The import relation is the durable fact.

Imports

Constants and variables

The fixed chart. The codes follow r/books' convention, where an account's LEADING DIGIT fixes its kind: 1 asset, 2 liability, 3 equity, 4 revenue, 5 expense. A six-series code would be an invalid account under that convention, which is why cost of goods sold is 5000 and not the 6000 some charts use.

const (
	InventoryCode = "1300"
	InventoryName = "Inventory"

	COGSCode = "5000"
	COGSName = "Cost of goods sold"

	ContribCode = "3900"
	ContribName = "Inventory contributed"
)

MaxEntriesPerRegister caps one owner's journal, far below journal's MaxEntriesLimit of 1000000 because Rederive is linear in the posting count and is a free read here.

A register that fills refuses further receipts and shipments rather than dropping history. There is deliberately no Archive — it would destroy the record the invariants are checked against — so the remedy is a second register under a second address, which is a real limitation and not a hidden one.

const MaxEntriesPerRegister = 8192

MaxItemNameLen bounds an item's human name. Every name is a candidate for Render output.

const MaxItemNameLen = 64

MaxItemsPerRegister caps one owner's SKU count. Every register-wide cross-foot walks every item's lots, and this bound is half of what keeps those reads affordable for everyone.

const MaxItemsPerRegister = 64

MaxLotsPerItem caps one item's LIVE lots, well below p/lots' MaxLotsLimit of 1024. It is the other half of the read bound: the worst case a register-wide walk must visit is MaxItemsPerRegister*MaxLotsPerItem lots.

Exhausted lots are dropped by p/lots, so this bounds concurrent lots and not the number of receipts an item may ever take. A WAVG item merges every receipt into one lot and so can never reach it — which means this is NOT the binding constraint for a WAVG item, and that item has a merge-overflow refusal of its own instead. See the package doc.

const MaxLotsPerItem = 64

MaxSKULen bounds a SKU, which is an avl key, a Render table cell and a Render path component all at once.

const MaxSKULen = 32

RealmPath is this realm's own path, used to build Render links.

const RealmPath = "/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/inventory"

RenderItemLimit caps how many items one register page tabulates.

const RenderItemLimit = 25

RenderLotLimit caps how many lots one item page tabulates.

const RenderLotLimit = 20

Functions

AccountKind

func AccountKind(owner address, code string) (string, bool)

AccountKind returns an account's kind as a word, and whether it exists.

AddFIFO

func AddFIFO(cur realm, sku, name string)

Crossing function. Callable from a transaction via MsgCall, and from another realm as AddFIFO(cross(cur), ...).

AddFIFO adds an item whose shipments are costed oldest-lot-first.

AddLIFO

func AddLIFO(cur realm, sku, name string)

Crossing function. Callable from a transaction via MsgCall, and from another realm as AddLIFO(cross(cur), ...).

AddLIFO adds an item whose shipments are costed newest-lot-first.

AddWAVG

func AddWAVG(cur realm, sku, name string)

Crossing function. Callable from a transaction via MsgCall, and from another realm as AddWAVG(cross(cur), ...).

AddWAVG adds an item whose receipts are merged into a single re-anchored lot and whose shipments are costed at the blended unit cost.

A WAVG item holds exactly one lot however many receipts it takes, so it can never reach MaxLotsPerItem. It can still refuse a receipt: the MERGE has an overflow precondition the other two methods never evaluate, and it is a property of the pool's accumulated history rather than of the receipt. See the package doc; the practical consequence is that LotCapacity is not the figure to watch for a WAVG item.

AddedAt

func AddedAt(owner address, sku string) int64

AddedAt is the height an item was added at.

Balance

func Balance(owner address, code string) (int64, bool)

Balance returns an account's balance in its NORMAL direction and whether the code exists. All three of this realm's accounts are normal, so all three read non-negative.

COGSDebit

func COGSDebit(owner address) int64

COGSDebit is the 5000 balance: everything ever charged out as cost of goods sold.

Capacity

func Capacity(owner address) (itemsLeft, entriesLeft int)

Capacity reports how much room an owner's register has left.

Charged

func Charged(owner address, sku string) (qty, cost int64)

Charged is an item's cumulative shipped quantity and the cost charged out for it. The cost figure is derived: no caller has ever supplied it.

ContributedCredit

func ContributedCredit(owner address) int64

ContributedCredit is the 3900 balance. Read it as "goods arrived from somewhere this ledger does not model" — see the package doc on why 3900 is a plug this realm is forced to pick.

CostOnHand

func CostOnHand(owner address, sku string) int64

CostOnHand is the carrying value of an item's live lots: the figure this item contributes to the 1300 balance. Derived, for the same reason as OnHand.

CrossFoot

func CrossFoot(owner address) (inventoryAgrees, cogsAgrees, contributedAgrees, equationOffsets bool)

CrossFoot reports all four register-wide agreements as separate bools.

They are returned SEPARATELY and there is deliberately no single aggregate bool: a one-word verdict over four checks is the shape that gets quoted without its coverage, and a reader who wants the figures behind any one of these has CrossFootInventory, CrossFootCOGS, CrossFootContributed and Equation.

CrossFootCOGS

func CrossFootCOGS(owner address) (sumCharged, balance int64, agrees bool)

CrossFootCOGS is the register-wide check on 5000: the sum of every item's charged cost against the ledger balance.

CrossFootContributed

func CrossFootContributed(owner address) (sumReceived, balance int64, agrees bool)

CrossFootContributed is the register-wide check on 3900: the sum of every item's received cost against the ledger balance.

CrossFootInventory

func CrossFootInventory(owner address) (sumOnHand, balance int64, agrees bool)

CrossFootInventory is the register-wide check on 1300: the sum of every item's carrying value against the ledger balance.

The identity is EXACT, not approximate, and the reason is structural: this realm exposes no general posting entrypoint, so it is its book's only writer. Adding a general Post would destroy this check, which is the cost of adding one.

EntryCount

func EntryCount(owner address) int

EntryCount is how many entries an owner's book holds.

Equation

func Equation(owner address) (debitNormal, creditNormal int64, offsets bool)

Equation reports the accounting equation: debit-normal against credit-normal, and whether they offset. For this realm that is 1300 + 5000 against 3900, which offsets at 0 = 0 on an empty register and after every posting.

READ THE SIGN BEFORE PRESENTING EITHER FIGURE. journal partitions RAW DELTAS — debits minus credits — so creditNormal is NEGATIVE whenever 3900 carries a credit balance, and `offsets` means debitNormal + creditNormal == 0, not debitNormal == creditNormal. The textbook "assets = equity" has both sides positive; this does not. A caller rendering a balance sheet wants -creditNormal. The first test written against this function asserted the right magnitude with the wrong sign and was red for it, which is why this paragraph exists rather than a reader discovering it.

What `offsets` does NOT prove is journal's obligation 7, inherited here verbatim: the two subtotals are a partition of the same addends, so reclassifying every account in the chart changes both and leaves `offsets` true. A Kind set wrongly at Open time is invisible to it. What pins this realm's chart is the PAIR — debitNormal == InventoryDebit + COGSDebit and creditNormal == -ContributedCredit — and the suite asserts exactly that.

Sharper than that, because the partition is arithmetically inert: the two subtotals are added back together, so `offsets` reduces to Σ debits == Σ credits over the whole chart, which is the FIRST of the three conjuncts IsBalanced checks. A balanced book therefore always offsets, and `offsets` carries no fact IsBalanced has not already carried more strongly. The informative half of this function is the PAIR OF FIGURES, not the boolean — the figures move when a Kind changes and the boolean does not. (The two walks accumulate in different orders, so a mid-sum overflow could in principle separate them. At 8192 entries it would take amounts no register can reach, and it is not a second independent check.)

HasItem

func HasItem(owner address, sku string) bool

HasItem reports whether an owner's register holds a SKU.

HasRegister

func HasRegister(owner address) bool

HasRegister reports whether an address has opened a register.

Height

func Height() int64

Height is the current chain height, so a reader can date every other figure on this page without a second query.

Invariant

func Invariant(owner address, sku string) (posted, derived int64, agrees bool)

Invariant is the per-item derivation check, and it is this realm's central claim:

postedCOGS == pool.ChargedCost()

The left figure is what this realm posted to 5000 for the item; the right is what the pool says it charged. They are maintained by different code in different packages, so a false here means the realm posted an amount its own lots do not imply — which no code path in this realm can produce, and which is exactly why it is worth exporting. A claim is worth more checkable by a stranger than asserted in a comment.

The receipt side is checked the same way by InvariantReceipts.

InvariantReceipts

func InvariantReceipts(owner address, sku string) (posted, derived int64, agrees bool)

InvariantReceipts is Invariant for the receipt side:

postedRecv == pool.ReceivedCost()

InventoryDebit

func InventoryDebit(owner address) int64

InventoryDebit is the 1300 balance: the value of goods on hand per the ledger.

IsBalanced

func IsBalanced(owner address) bool

IsBalanced reports whether total debits equal total credits.

ItemCount

func ItemCount(owner address) int

ItemCount is how many items an owner's register holds.

ItemCrossFoot

func ItemCrossFoot(owner address, sku string) (received, charged, onHand int64, foots bool)

ItemCrossFoot is p/lots' own cost identity for one item:

receivedCost == chargedCost + costOnHand

Received and charged are counters; on-hand is a walk of the live lots. The identity therefore compares two independently maintained things, which is why p/lots keeps them apart.

ItemCrossFootQty

func ItemCrossFootQty(owner address, sku string) (received, consumed, onHand int64, foots bool)

ItemCrossFootQty is the quantity identity for one item:

receivedQty == consumedQty + qtyOnHand

It is not implied by ItemCrossFoot: costs and quantities can disagree independently, and a bug that moved cost without moving quantity would be invisible to the cost identity alone.

ItemName

func ItemName(owner address, sku string) string

ItemName is an item's human name.

LotAt

func LotAt(owner address, sku string, i int) (qty, cost, consumed, remaining, remainingCost int64, ok bool)

LotAt reports the i'th live lot of an item in receipt order, and whether i was in range. It returns primitives rather than a p/lots.Lot so that the figures are readable from vm/qeval without the caller knowing that type.

LotCapacity

func LotCapacity(owner address, sku string) int

LotCapacity reports how many further live lots one item can hold. A WAVG item merges into one lot, so its figure is a bound it will never approach — and reading headroom off it for a WAVG item is therefore reading the wrong limit, because the refusal that CAN bind there is a merge overflow this figure knows nothing about. See AddWAVG.

LotCount

func LotCount(owner address, sku string) int

LotCount is how many LIVE lots an item holds. Exhausted lots are dropped.

Method

func Method(owner address, sku string) string

Method is an item's costing method, as "fifo", "lifo" or "weighted-average".

OnHand

func OnHand(owner address, sku string) int64

OnHand is the quantity of an item on hand, DERIVED by walking its live lots rather than read from a counter — which is what makes ItemCrossFootQty a comparison rather than a tautology.

OpenRegister

func OpenRegister(cur realm)

Crossing function. Callable from a transaction via MsgCall, and from another realm as OpenRegister(cross(cur), ...).

OpenRegister creates the caller's register and opens its three accounts. It panics if the caller already has one.

The chart is opened HERE rather than on demand, and the book is created with a cap of exactly three accounts. Both facts are load-bearing: they are why this realm is its book's only writer and why the cross-foot identities are exact rather than approximate.

OpenedAt

func OpenedAt(owner address) int64

OpenedAt is the height an owner's register was opened at.

RawBalance

func RawBalance(owner address, code string) (int64, bool)

RawBalance returns debits minus credits regardless of kind. Summed over the three accounts it is exactly zero in a balanced book, which is what makes it the useful figure for a consistency check and the wrong one for a report.

Receipts

func Receipts(owner address, sku string) int64

Receipts is how many receipts an item has taken, including those whose lots are exhausted and dropped. It is NOT the live lot count — see LotCount.

Receive

func Receive(cur realm, sku string, qty, cost int64) uint64

Crossing function. Callable from a transaction via MsgCall, and from another realm as Receive(cross(cur), ...).

Receive takes qty units costing cost in total into the item's pool and posts debit 1300 / credit 3900, returning the entry id.

A cost of ZERO is accepted — free goods — and posts nothing; the returned id is then 0, which is not a valid entry id and is the signal that no entry was made. See the package doc.

Panics if the item does not exist, if qty is not positive, if cost is negative, if the pool's lot cap is reached, if a WAVG merge would breach the per-lot overflow precondition — a refusal that depends on the item's history and not on this receipt, see AddWAVG — or if the book is full. Every one of those aborts the whole transaction, which is what makes the pool-then-post order safe: an error return would NOT revert the pool update, a panic does.

Received

func Received(owner address, sku string) (qty, cost int64)

Received is an item's cumulative received quantity and cost.

Rederive

func Rederive(owner address) (debits, credits int64, agrees bool)

Rederive re-walks every posting in the book and reports whether the account totals agree with the journal.

It is a FREE read, unlike r/books' owner-only equivalent, for the reason given in the package doc. The cost is linear in the posting count, which is what MaxEntriesPerRegister bounds.

Registers

func Registers() int

Registers is how many registers exist across the realm.

Rename

func Rename(cur realm, sku, name string)

Crossing function. Callable from a transaction via MsgCall, and from another realm as Rename(cross(cur), ...).

Rename changes an item's human name. The SKU, the costing method and every lot are immutable; a name is presentation only and nothing derives from it.

Render

func Render(path string) string

Render shows the realm index at "/", one owner's register at "/<address>", and one item at "/<address>/<sku>".

SKUs

func SKUs(owner address, from string, limit int) []string

SKUs lists an owner's SKUs in key order, starting at or after from and returning at most limit of them. An empty from starts at the beginning.

This enumerator exists because its absence is a known defect elsewhere in this portfolio: r/books exposes no account enumerator, so a third party cannot discover what to ask it about without already knowing. A sub-ledger whose invariants are per-item is useless to a verifier who cannot list the items, so the list is part of the product rather than an afterthought.

A non-positive limit returns nothing rather than everything. The cap is the item cap, so a caller may always page the whole register.

Ship

func Ship(cur realm, sku string, qty int64) int64

Crossing function. Callable from a transaction via MsgCall, and from another realm as Ship(cross(cur), ...).

Ship consumes qty units from the item's pool and posts debit 5000 / credit 1300 for the cost it DERIVED, returning that cost.

The cost is never a parameter. It is whatever the item's costing method charges against the lots on hand, which is the whole product.

A derived cost of ZERO posts nothing — goods that cost nothing to acquire cost nothing to ship. The return value is then 0 and so is indistinguishable from "no entry made", deliberately: both facts are the same fact here.

Panics if the item does not exist, if qty is not positive, if qty exceeds the quantity on hand (p/lots fails closed with ErrInsufficient and consumes nothing), or if the book is full.

Totals

func Totals(owner address) (debits, credits int64)

Totals are the book's total debits and credits.


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
Importschain/runtime, chain/runtime/unsafe, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/lots, gno.land/p/nt/avl/v0, gno.land/p/nt/markdown/sanitize/v0, strconv, strings
First-party dependenciesgno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/lots

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/r/inventory/inventory.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