const MaxCodeLen, MaxNameLen
MaxCodeLen and MaxNameLen bound an account's identifiers. Both are at or below journal's own limits, so the engine's validation is a backstop here rather than the only check.
Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.
Package books is a per-owner general ledger: an address opens a chart of accounts and posts balanced double-entry ent...
Package books is a per-owner general ledger: an address opens a chart of accounts and posts balanced double-entry entries against it.
Every other ledger-like realm in this portfolio is SINGLE-PURPOSE and its state shape IS its question. invoices tracks receivables. payroll tracks accrued wages. budgets tracks allocations. fee_split tracks shares. Each is a correct answer to one question and structurally cannot answer a second, because there is nowhere to put a transaction that is not already one of the modelled kinds.
This realm has no opinion about what the accounts mean. The owner defines the chart; the realm enforces that every entry balances, that it cannot be altered after the fact, and that only the owner may write it.
This realm holds nothing. There is no coinio import, no payable path, no fee, no withdrawal, no admin.
So every mutating entrypoint REFUSES a transaction that arrives carrying coins. A realm with no withdrawal path that silently accepts ugnot strands it permanently, and "we don't hold funds" would then be true of the design and false of the balance. Having no custody is not the same as being safe to send coins to, and the difference is one guard.
Read this before trusting any number this realm returns.
With no custody there is no solvency invariant tying "Cash 5000" to 5000 real ugnot anywhere. A book can be internally perfect and describe nothing. IsBalanced proves arithmetic, never truth.
Consequently this realm exposes NO CROSS-OWNER AGGREGATE. Summing every book's debits would be summing every stranger's self-reported assertion, and the getter's existence would invite reading it as a signal. What it exposes instead is owner-scoped, which is the only question a consumer can act on — and a consumer acting on its OWN book is trusting only itself.
An account's code's leading digit fixes its kind, by the standard chart-of-accounts convention:
There is no kind parameter. This is deliberate and it is the realm's answer to p/journal consumer obligation 7, which exists because of that package's audit finding Y1: NO CHECK IN p/journal VALIDATES AN ACCOUNT'S KIND. An account opened Asset that should have been Liability reports a sign-flipped Balance() forever while IsBalanced, RederiveFromJournal and AccountingEquation all three stay healthy — every one of them is built on Debits and Credits, which the kind does not touch.
Deriving the kind does not eliminate that hazard. It MOVES it: after the derivation the kind cannot disagree with the code, so the unauditable question "is this kind right?" becomes the visible question "is this code right?", which any reader of the chart can check against the convention above. That is the whole claim and it is deliberately not stated as more.
The cost is a mandatory numbering convention: a code whose first character is not 1-5 is refused. For a realm whose purpose is auditable books, that is the feature.
There is no edit and no delete. A wrong entry is corrected by posting its mirror image, which leaves both the error and the correction readable. Each entry may be reversed at most once; a reversal is itself an entry and is itself reversible once.
An owner may seal everything up to a height with CloseThrough, after which no entry may be dated at or before it. The seal only moves forward. This is the owner's own discipline, not a protocol rule — nothing forces an owner to close anything.
Entries are dated with runtime.ChainHeight() and never with a caller- supplied value, satisfying p/journal obligations 2 and 3. Heights throughout, never seconds. A realm that let a caller choose the date would have no period discipline at all.
p/journal permits 10 000 accounts and 1 000 000 entries. Its AUDIT.md names the gas ceiling on RederiveFromJournal at full cap — a worst case of 64M posting visits in one call — as UNMEASURED and does not claim it. So this realm sets its own caps far below, and exposes Rederive as an explicit owner-callable rather than running it inside a getter where another party would pay for it.
The caps are per-owner, never global. A global cap on books would be a griefing vector: one address could fill it and lock everyone else out. Per-owner, an owner can exhaust their own capacity and nobody else's, and the cost of doing so is gas plus a storage deposit per record.
The owner is cur.Previous().Address(), so a realm that calls in keeps books under its own address with no extra authorization code. That is the realm-to-realm integration surface, and it arrived as a property of the owner-only rule rather than as a feature.
MaxCodeLen and MaxNameLen bound an account's identifiers. Both are at or below journal's own limits, so the engine's validation is a backstop here rather than the only check.
MaxAccountsPerBook caps one owner's chart. Far below journal's MaxAccountsLimit of 10000: IsBalanced sums the whole chart, and this bound is what keeps that read cheap for everyone.
MaxEntriesPerBook caps one owner's journal. Far below journal's MaxEntriesLimit of 1000000, because that limit's interaction with RederiveFromJournal is unmeasured — see the package doc.
A book that fills up refuses further entries rather than dropping history. The remedy is a fresh book with opening balances carried forward; there is deliberately no Archive, because it would destroy history and needs more design than it has had.
MaxLegsPerEntry caps one entry's postings. journal permits 64; this is lower because an entry is parsed from a single string argument and rendered in full, and both of those costs are linear in the leg count.
MaxMemoLen bounds an entry's free text. Below journal's MaxMemoLen of 1024 because every memo is a candidate for Render output.
RealmPath is this realm's own path, used to build Render links.
RenderEntryLimit caps how many entries one Render call emits. A book may hold MaxEntriesPerBook of them and rendering all of them would make the page cost grow without bound.
1func AccountInfo(owner address, code string) (name, kind string, debits, credits, balance int64)AccountInfo returns an account's full state. journal returns Account BY VALUE, so this hands out a copy and no pointer into stored state.
Accounts is the number of accounts in an owner's chart.
Balance returns an account's balance in its NORMAL direction: positive means a debit balance for an asset or expense, and a credit balance for a liability, equity or revenue account.
The second result distinguishes "no such account" from "zero".
Books is the number of books that exist. It is a count and nothing more: there is deliberately no way to enumerate them or aggregate across them.
Capacity returns an owner's remaining headroom, so a consumer can see a full book coming rather than discovering it on a refused entry.
CloseThrough seals the caller's book up to and including a height: no entry may afterwards be dated at or before it. The seal only ever moves forward.
Passing a height at or beyond the current one seals the present and makes the book immediately unpostable until the chain advances past it. That is permitted — it is the owner's own book — and it is called out because it is the one way to brick a book without filling it.
ClosedThrough returns the height an owner's book is sealed to, and whether it has ever been closed. The second result matters: a book closed through height 0 is not the same as a book that has never been closed.
Codes lists an owner's account codes in ascending code order, beginning at from INCLUSIVE and returning at most limit of them. A limit <= 0 returns nothing; a limit above the chart cap is clamped to it. Page by passing the last code received back as from, which re-reads that one code.
Accounts above returns HOW MANY accounts a chart holds, and AccountInfo answers for a code the caller already knows. Neither lets a caller DISCOVER a code. Without this, a trial balance, a balance sheet and an income statement are all unbuildable from outside this realm however many times the chart is interrogated — a consumer could only report on accounts it had independently been told about, which for a chart the OWNER defines is no guarantee at all.
journal.Book has IterateAccounts(fn func(Account) bool), and exporting a realm entrypoint that takes fn from the caller would be the shortest code here and a security defect. A caller-supplied callback is the "callback params" class, and journal's own doc records the sharp edge: fn runs under the CALLING REALM'S STORAGE AUTHORITY, because the receiver is stamped with this realm's PkgID and a top-level fn has no declaring realm to anchor it elsewhere. A hostile fn would execute holding r/books' own authority.
So the iterator stays internal and this returns VALUES. The closure below is written by this realm, appends to a local slice, and re-enters no mutator — which is exactly the condition journal's doc attaches to it.
Codes is a free read and NOT a crossing function, so an owner does not pay for a consumer's paging. The cost ceiling is the chart cap: the underlying iterator has no seek, so `from` is honoured by skipping, and one call walks the whole chart however small limit is. A caller paging a 128-account chart 8 codes at a time therefore does 16 full walks, not one. That is a deliberate trade for not touching p/journal, whose Codes-with-seek would have re-opened Gate A and the sabotage sweeps for all three of its importers.
EntryCount is the number of entries in an owner's journal.
1func EntryInfo(owner address, id uint64) (at int64, memo string, legs int, reversalOf uint64, total int64)EntryInfo returns one entry's header. Postings are returned separately by EntryLegs, because an entry's leg count is variable and a crossing-function return cannot carry a slice usefully.
EntryLegs returns one entry's postings in the "code:side:amount,..." form PostMulti accepts, so an entry can be read and re-posted without a separate encoding.
Equation partitions an owner's chart BY ACCOUNT KIND: the summed raw delta of every debit-normal account (Asset, Expense), the same for every credit-normal one (Liability, Equity, Revenue), and whether the two offset.
The two FIGURES are why this exists. They are the balance-sheet and income-statement partition of the chart, and no other accessor on this realm exposes it — Totals sums debits and credits without regard to kind, and AccountInfo answers one code at a time.
Read this before using the third result for anything.
The kind partition is TOTAL: journal's Kind is 1..5, IsDebitNormal is `k == Asset || k == Expense`, so every account lands in exactly one of the two sums and neither can omit one. Therefore
1debitNormal + creditNormal == sum of RawDelta over ALL accounts
2 == totalDebits - totalCredits
and this realm refuses an entry that does not balance, so that difference is always zero. `offsets` is consequently TRUE for every book this realm can produce, and it is true for a chart whose accounts are classified nonsensically just as readily as for a correct one.
This is the `HasValidSignature` shape §7b already names: a predicate that is a constant under current conditions, and so is evidence of nothing. The suite pins the vacuity deliberately rather than asserting true and reading green — a test that cannot fail is the thing that makes this dangerous.
What `offsets` DOES still distinguish is arithmetic overflow, where journal returns (0, 0, false). So the honest reading is **"the two sums are trustworthy"**, never "the classification is right". A consumer wanting a genuine classification check must compare the kinds against its own expectation, which is the statement consumer's job and not this realm's.
Both figures are RAW deltas (debits - credits), not normalised balances, so a debit-normal total is normally positive and a credit-normal total normally negative. A credit-normal account carrying a positive raw delta is a contra account, which is legitimate, and is why neither figure is forced non-negative.
HasAccount reports whether an owner's chart contains a code.
HasBook reports whether an address has opened a book.
Height is the chain height this realm dates entries with. Exposed so a caller can predict what CloseThrough will seal.
IsBalanced is the CHEAP invariant and this realm's live-verification probe.
It compares two independently-maintained accumulators: the per-account totals summed across the chart, against the book's own running totals. They are written by different code paths, so a drift between them is a real defect and this finds it.
What it does NOT do is re-read the journal, so a defect that corrupted both accumulators identically would survive it. That is Rederive's job, and Rederive is a crossing function because the owner should pay for it.
And the ceiling on both: this proves the book is self-consistent, never that its entries describe real events. See the package doc.
IsReversed reports whether an entry has already been reversed. An entry may be reversed at most once, so this is also "may this still be reversed".
KindOf reports the kind a code would be given, without opening anything. A consumer can call this before OpenAccount to check its own numbering.
OpenAccount adds an account to the caller's book. The KIND IS DERIVED from the code's leading digit; there is no kind parameter. See the package doc.
OpenBook creates the caller's book. It panics if the caller already has one.
Separating this from OpenAccount is deliberate: creating a book is the call that pays the storage deposit for the owner's record, and a caller should see that cost on its own rather than hidden inside their first account.
OpenedAt is the height an owner's book was created.
PostMulti posts an entry of up to MaxLegsPerEntry legs, specified as a comma-separated list of colon-separated triples:
1"1000:D:500,4000:C:300,4100:C:200"
Side is D or C, case-insensitive. Amounts are positive integers in the owner's chosen unit. The string form is used because a crossing function cannot usefully take a slice over MsgCall; treasury_board encodes a list the same way.
Debits must equal credits, and journal names which side is short.
A leg may name an account another leg already named — journal folds them, and "1000:D:500,1000:C:200,4000:C:300" is a legitimate net +300 to 1000. What is refused is an entry in which EVERY leg names the same account: it balances only by netting to exactly zero, so it is the PostSimple mistake wearing the multi-leg syntax. The narrow rule is deliberate — a blanket "no repeated account" would reject the legitimate fold above.
PostSimple posts the two-leg entry that is the overwhelmingly common case: debit one account, credit another, for the same amount.
It exists beside PostMulti because the two-leg form cannot be mis-encoded. A caller who wants one debit and one credit should never have to get a leg specification's syntax right.
RawBalance returns an account's debits minus credits, without the normal- direction sign convention. Exposed because a consumer reconciling against an external system usually wants the raw figure.
Rederive runs journal's STRONG invariant check over the caller's own book: it replays every posting of every entry and compares the result against both accumulators.
It is a crossing function rather than a getter on purpose. The check is O(all postings ever) and the owner is the party who should pay for it — exposing it as a free read would let anyone make an owner's book expensive to query. IsBalanced is the cheap check and is a free read.
Returns the replayed totals and whether they agree.
Render serves the index at "" and one owner's book at "<address>".
Every caller-supplied string — account names and entry memos — goes through the ecosystem sanitizer. Free text reaching Render unescaped is the one injection-shaped surface a realm with no custody still has.
Reverse posts the mirror image of an existing entry in the caller's book, dated now. The original is left exactly as it was: this is a correction, not an edit.
An entry may be reversed at most once. The reversal is itself an entry and may itself be reversed once.
Totals returns an owner's running debit and credit totals.