DRAFT 0.3.0 · definitions of the BSA kinds are a draft (round 5); two pilot circles run on test infrastructure · last checked 27 Oct 2026
On this page

Currencies and exchange rates: technical specification DRAFT 0.3.0

Plain-words text · Technical specification · All documents

English text rendered from the project's source documents. The Slovene original is on lananostr.site. Where this text and the definitions differ, the definitions win.

Precedence: where this document and definitions 0.3.0 differ, the definitions (docs/bsa-records-0.3.0.json) win; the difference is a bug to fix in this document. The definitions pin a frozen copy of this text (docs/0.3.0/) by its sha256 (meta.normative_spec); this living text may change afterwards, and a change reaches the definitions only when they are generated again, which then needs a new yes.

Scope and status#

  • Definitions 0.3.0. 0.2.0 was never published and is superseded. RELAY_PUBLISH_OFF=1 stays until the definitions are published (KIND 42893, signed by the Lana authority) and the project owner says yes (one yes per definitions version and one per kind switched on: proposal P7).
  • Decided (the project owner, 16 Oct 2026, answers to questions 1–9): the receiving (seller's) circle quotes the rate from a source it chooses; the payer accepts by signing both amounts; the quote is a public signed record (43919 v3 Rate Quote) signed by the receiving circle's node, with a validity window; both ledgers carry both amounts and the quote id (Q7). Each leg is final at witness quorum (3 of 4) and ≥ 3 relays (Q1). Circle relays from day one, ≥ 4 relays with ≥ 3 operators; root relays only from 38888 (Q2). One witness to start, labelled "not yet decentralised", no money cap; then 4 witnesses with 4 operators, q = 3 (Q3). The Circle Heart of the live circle (Q4, completed by 4a below; formerly the "stewards", E-2 below). No bank data and no payouts in BSA: no held_out, no payout acts (Q6). Common Good becomes keyless project wallets plus a keyless reserve (Q8).
  • Decided (the project owner, afternoon of 16 Oct 2026, answers to the open points; same source file): 4a the live circle has four Heart members (formerly "stewards"): the founder and three other Heart members; k = 3 of m = 4. 6a no money deposits in BSA: no deposit acts, no dep or corr entry, no custodian, no paid_in; a wave grows only from receipts. Start source ecb-daily, read by the node once a day; the app always shows the source date; a quote is valid 600 s (10 minutes; since 23 Oct 2026 the circle parameter quote_ttl_s, 600 s by default: E-1, below). 8b inflow to receiving projects in order of opening, no limits for now (reaches this document only through drain()). 8i world and world-step are keyless. 8j a hanging cross-circle payment is paid by nobody: after 7 days the payer's circle reads the receiving circle's ledger; not booked, with the receiver's seals covering the note's expires → xundo to the payer, naming the receiver's seals; booked → xdone; unreadable → wait (below, "The 8j check"). Raising the step outward is a project like any other.
  • Decided (the project owner, 17 Oct 2026, answers to phase C; same source file, sections "Odgovori na fazo C" and "Odgovor na pojasnilo točk 5 in 1"): C1 the time values and the number of project approvals were world-wide rule values in one record of their own, KIND 43920 BSA Rules, signed by the rules authority; replaced on 23 Oct 2026 by E-1 (next item). C3 refusals of keys that are neither members nor applicants are answered and never booked; at most rej_per_author_per_day (20 by default; a circle parameter since E-1) refusals of one key are booked per morning day (from one morning boundary to the next; D4 review, round 3), above that the intake answers 429 and books nothing; an act is valid act_ttl_s (7 days by default; a circle parameter since E-1). C5 a circle is named by its circle id, the event id of its found act (43916, t = found); every other BSA event names its circle by ["c", <circle id>] (the found act itself carries ["c", ""], the 30910 the circle id as d; a 43910 note by ["c", <payer circle id>, "", "from"] and, across circles, ["c", <receiver circle id>, "", "to"]), relays are queried by #c, and no BSA kind uses an a tag; the circle key stamps the 30910 mirror (and, once, the found act in block 1; that it signs nothing else is a proposal) and can be replaced (circle_key_change: k Heart members, change_notice_s of notice, 72 h by default since E-1, booked by the witnesses); it never signs a 43919 (the node does).
  • Decided (the project owner, 23 Oct 2026, answers before phase E; same source file, section "Odgovori pred fazo E" with its subsections "Dopolnitev" and "Referenca"): E-1 the time values and limits are circle parameters: the values of the definitions are only defaults, and each circle's Circle Heart changes them within the bounds of the definitions; KIND 43920 BSA Rules and the rules authority are retired (the number stays reserved and is never reused), which replaces C1 and drops 1A; fixed for every circle: a project needs at least 3 Heart "for" votes and no "against", and the money and gravity computation (the conversion below is part of it [Consequence]). In this document: quote_ttl_s (600 s by default, bounds 300…3,600), note_ttl_s (600 s, 60…3,600), xcheck_s (604,800 s = 7 days, 86,400…2,592,000) and project_approvals (3, at least 3 and at most 9; the step-raise project included); the bounds are a proposal. A change by param_propose (k Heart members) takes effect only after a notice of at least change_notice_s and at least the old value of every changed time [Proposal]; running deadlines keep their recorded due time (the 8j check's check at) [Consequence]; TTL checks use the parameters in force at the booking [Proposal]; each circle applies its own values across circles (below, "Different parameters in A and B") [Proposal]. E-2 the stewards are the Circle Heart (one person: a Heart member); the technical names steward_* stay [Proposal]. E-5 a project, the step raise included, is decided by a vote: no "against" from any member and at least project_approvals Heart members "for"; there are no objections any more. E-3 and E-9 an excluded key, and a key that left [Proposal], may send its plus to another circle (xout, amount ≤ max(0, balance), never the wave); a left or excluded line with a minus may receive a settlement from another circle (xin, to_amount ≤ −balance, no grow, no settled) [E-9: anyone may settle a debt, never above 0; the shape: Proposal].
  • Decided earlier and unchanged: one currency per circle; no exchange fee; no threshold for unusual moves; payments between currencies switch on when two circles with different currencies exist (16 Oct 2026). From KIND 38888 only the relay tags are read; its currency values are LANA prices and are never used as rates (15 Oct 2026).
  • Replaced (said openly): the 16 Oct decision "a market rate every hour signed by the project owner's rate key for every circle; the last valid rate stays valid (no expiry); rate history never overwritten", the 0.2.0 rule "one rate key named in the definitions with since/until, replaced only by a new definitions revision", and the proposal "per-circle rate panel with a median rule and a tolerance". KIND 43919 is repurposed from "Rate Reference" to "Rate Quote". New meaning of what remained: "last rate valid" = a circle quotes from the newest rate its source gave; "history never overwritten" = every quote used is referenced from the ledger and stored on the relays of both circles, never replaced or deleted. Also replaced (8j, afternoon of 16 Oct 2026): the 7-day refund by public decision (10/15 Oct 2026, paid by Common Good; then proposed: paid by the reserve), the xrefund entry, the rule "a late refusal after a refund moves the amount to the reserve", and the proposal that the reserve pays the step outward. Also replaced (17 Oct 2026): the circle address 30910:<circle pubkey>:<code> as a circle's name, with the a tags of 43919 and 43910 and the #a queries (now the circle id, c tags and #c, C5); quote_ttl and project_approvals as fixed constants of the definitions mirrored in the 30910 (at that time the world rule values QUOTE_TTL_S and PROJECT_APPROVALS of the BSA Rules, 43920, which the 30910 no longer carries, C1; since 23 Oct 2026 the circle parameters quote_ttl_s and project_approvals, E-1); the system accounts derived from the circle address (now from the circle id, below). Also replaced (23 Oct 2026, E-1): the world-wide BSA Rules (43920; retired before it was ever published, its number reserved and never reused), the rules authority, meta.rule_values, RULES_NOTICE_S, rules_adopt, the g3|rules line and the seal's rules tag: QUOTE_TTL_S, NOTE_TTL_S, XCHECK_S and PROJECT_APPROVALS are now the circle parameters quote_ttl_s, note_ttl_s, xcheck_s and project_approvals, with the same start values. Also replaced (23 Oct 2026, E-5): the objection (8c) and the approval of a project by three stewards: a vote instead.
  • Proposal: rate_stale_alert = 120 h (RATE_STALE_ALERT_S = 432,000 s, a fixed constant of the definitions, not a circle parameter); a circle changes its source at any time by a Heart act rate_source_set, signed by 3 of the 4 Heart members; after the 7 days the payer's circle repeats the 8j check once a day; the encoding of the 8j check (below); the bounds of quote_ttl_s (300…3,600 s), note_ttl_s (60…3,600 s) and xcheck_s (1 to 30 days), like the bounds of every circle parameter; that every TTL check uses the parameters in force at the booking, while a running deadline keeps the due time recorded when it started (the 8j wait: the hold's check at = A's xout at + A's xcheck_s then in force, in its o3|hold line) [Consequence of E-1; as the clarification of 17 Oct 2026 (D1 review)]; that during a parameter change's notice apps sign with the stricter of the two values, and across circles with the smaller; that each circle applies its own values to what it books (below, "Different parameters in A and B"). Said openly: the ECB states that its reference rates are for information only and advises against using them for transactions (re-check the wording before publishing); they appear once per TARGET working day, around 16:00 CET.
  • Open: Q10; Q18; P5; P7. The other open points (3a, 8g, 8h) do not touch this document; nor does decision 6c (later on 16 Oct 2026, »6c, da«: one start of 1,000 in the circle's currency for the reserve at founding, booked in block 1 from the keyless origin), which stays in the circle's currency.
  • Before publication, every kind number is re-checked against the live kinds.json and all four relays (COUNT, read only). On 16 Oct 2026 no event of kinds 43910–43919 existed on any Lana relay [M]. KIND 43920 (BSA Rules) was checked free on the four Lana relays and in kinds.json on 17 Oct 2026; since 23 Oct 2026 it is retired and its number stays reserved (E-1).
  • Glossary: node = the witness that proposes and orders blocks (proposer); witness = signer; Circle Heart (Srce kroga; one person: Heart member, član Srca; formerly "stewards"; the technical names steward_* stay); act = 43916; block = 43911 v3; daily seal = 43917 v3; circle record (mirror) = 30910 v3; circle id (rojstna številka kroga) = the event id of the circle's found act (ustanovna listina, 43916 t = found), its name for ever (C5); circle parameters (nastavitve kroga) = the time values and limits each circle sets within the bounds of the definitions (E-1); the BSA Rules (svetovni zapis pravil, 43920) are retired; rate quote (ponudba tečaja) = 43919 v3; project (projekt), project wallet (denarnica projekta) and reserve (rezerva) = keyless system accounts moved only by rule entries (Q8); vote (glas) = a member's "for" or "against" on a proposal; it replaced the objection (ugovor) (E-5); payment note (listek) = 43910; the note's deadline (rok listka) = its expires.
  • Labels: [M] measured 16 Oct 2026, [C] relay configuration read 16 Oct 2026, [R] earlier report, [E] estimate.

43919 v3 Rate Quote#

The same format, word for word, as docs/knjiga-na-relejih.md §3.7 [Decided Q7: the seller's circle quotes, the payer accepts; the format is the proposal for 0.3.0].

  • Regular kind (40000+); strfry stores it as a regular event [C]. Size ~0.5 KB [E; 463 B measured for the 0.2.0 rate record of similar shape].
  • pubkey = the node (proposer) of the quoting (seller's, receiving) circle at the quote's created_at, checked against that circle's roles windows in its ledger (phase 1: the single signer). The node at a time t is that of the newest mirror booked at or before t (its genesis or roles entry's at ≤ t) [clarification, 17 Oct 2026 (D4 review)], so a quote signed in the very second of a node change is the new node's, although that block was still ordered by the old one [clarification, 17 Oct 2026 (D4 review, round 3)].
  • Tags, in this order (c, source, base, rate…, expires, prev, v):
    • ["c", <circle id of the quoting circle>] (the event id of its found act, C5);
    • ["source", <source id = the circle's rate_source at created_at>, "<the source's own time for this rate, unix seconds>"] (for ecb-daily the reference date at 00:00 UTC);
    • ["base", "EUR"] and ["rate", "<currency>", "<whole ppm: units of that currency per 1 EUR × 10^6>"] once for each currency the source gives (["rate","USD",…], ["rate","GBP",…]): the source's values, floor at 6 decimals, never adjusted (no fee); r_EUR = 1,000,000, implicit;
    • ["expires", "<unix seconds, ≤ created_at + quote_ttl_s>"]. quote_ttl_s is a parameter of the quoting circle: 600 s by default, bounds 300…3,600 s (proposal); its Circle Heart changes it with param_propose, with notice and never retroactively (decided 23 Oct 2026, E-1). Both circles judge a quote by the quoting circle's own value: the quoting circle by its parameters in force at the booking, the payer's circle by the value it reads in the quoting circle's ledger, as it reads that circle's node and rate source (proposal; below, "Different parameters in A and B");
    • ["prev", "<this circle's previous 43919 id, or \"\" on its first quote>"];
    • ["v", "3"]. Content "".
  • Rate values are whole ppm as base-10 integer strings, like every BSA number.
  • Forbidden: NIP-40 expiration (relays would drop the record when it expires; validity is the expires tag, the record is kept for ever), NIP-70 -. Readers treat such events as invalid.
  • Valid for a booking (checked by every witness of the quoting circle and by every verifier, from ledger data only):
    1. signed by that circle's node at created_at; c names that circle (its circle id);
    2. source time ≤ created_at < expires ≤ created_at + quote_ttl_s (the quoting circle's value in force at the booking; 600 s by default);
    3. the source id equals the circle's rate_source governance line in force at created_at;
    4. the payer circle's xout at ≤ expires, and to_amount equals the conversion exactly (below). Refusal reasons: stale_quote (expired), bad_quote (anything else).
  • Audit checks, not booking conditions (a quote enters a ledger only when a payment references it, so a witness cannot know every quote the node issued): the circle's prev chain has no gap (independent of key changes), and for the same source id the source time never goes back. Any verifier checks both from the relays; a break is a public 43924 fail.
  • Issuing: the node keeps at most one live quote. It makes a new one only when asked and the live one has expired, has less than quote_margin left (QUOTE_MARGIN_S, a fixed constant of the definitions, not a circle parameter; proposal 120 s), or its source has given a newer rate. The node publishes each quote to its circle's relays before handing it to any app. Two quotes can be valid at once for a few minutes after a source update; both count.
  • Volume: ≤ 86,400 / (quote_ttl_s − quote_margin), with the defaults 86,400 / (600 − 120) ≈ 180 a day per circle while payments from other currencies keep arriving, ≈ 90 KB a day, ≈ 33 MB a year per copy at most; zero on days without such payments [E].

Rate source#

  • Governance line g3|rate_source|<source id>|<public description> (no | in the description); mirrored in 30910 v3 as ["rate_source", <source id>, <public description>]. The quote validity is a circle parameter, quote_ttl_s (decided 23 Oct 2026, E-1), kept in the ledger state (g3|param:quote_ttl_s), not in the mirror; the 30910 carries no circle parameter (no quote_ttl, no project_approvals). Replaces rate_panel and rate_rule (and g3|rate_panel|…, g3|rate_rule|…).
  • Set at genesis: ecb-daily [Decided, afternoon of 16 Oct 2026: the start source]. [Proposal] changed only by a rate_source_set act (43916: one Heart member's proposing act plus endorsements of k = 3 of the m = 4 Heart members; the Heart is decided [4a, E-2], this threshold is a proposal), booked as a roles entry carrying the new 30910 mirror; effective for quotes whose created_at is at or after that entry's at (a quote takes the source of the newest mirror booked at or before its created_at, docs/knjiga-na-relejih.md §4.5) [clarification, 17 Oct 2026 (D4 review)]; no notice period (proposal). Replaces the act rate_panel_set. Earlier quotes and the bookings that used them stay valid.
  • Any source id with a public description is valid: the circle chooses its source itself [Decided Q7], and the definitions gate no source. 0.3.0 describes ecb-daily [Decided, afternoon of 16 Oct 2026]: the ECB's euro foreign exchange reference rates (base EUR; USD and GBP used), published once per TARGET working day; the node fetches once a day after publication (time of day not fixed) and keeps the newest. The app always shows the source date [Decided]; a quote is valid at most quote_ttl_s, 600 s by default [Decided 16 Oct 2026: 10 minutes; a circle parameter since 23 Oct 2026, E-1; a rate_source_set act carries no validity]. A source of the kind "signed rate record on relays by a key the circle names" is possible later; its format is not fixed. the project owner's former rate key could be such a source.
  • Staleness: the node alerts the Circle Heart when its newest source time is older than rate_stale_alert (proposal 120 h; it must exceed the source's normal gaps at weekends and TARGET holidays, where an ECB rate can be about 4–5 days old). It keeps quoting from the newest rate it has; the quote shows the source date. There is never a fallback to 38888 or a hard-coded rate.
  • A circle with no source rate yet issues no quote; the payer's app shows "no rate yet" and nothing is signed.
  • No witness fetches the external source. Witnesses check only validity (above). Anyone may compare a quote with what the named source published and publish a signed statement (43924); nothing stops automatically (no threshold, decided 16 Oct 2026).

Cross-currency payment note (43910 v3)#

  • Tags relevant here: ["c", <payer circle id>, "", "from"], ["c", <receiver circle id>, "", "to"] (the to tag only across circles; C5), ["amount", "<minor>"], ["currency", "<payer currency>"], ["to_amount", "<minor>", "<receiver currency>"] (the payer signs both amounts), exactly one ["e", <43919 id>, "", "quote"] (required across currencies; it replaces the optional ["e", …, "rate"] panel references), ["expires", "<unix ≤ created_at + note_ttl_s and ≤ the quote's expires>"], plus p, n, prev, v=3 as for every note. Tag order of every 43910: p, c (from), c (to)?, amount, currency, to_amount?, e (quote)?, n, prev, expires, idem?, e (request)?, e (project)?, v.
  • note_ttl_s is a circle parameter: 600 s by default, bounds 60…3,600 s (proposal); each circle's Heart changes its own with param_propose, with notice and never retroactively (decided 23 Oct 2026, E-1). Each circle checks a note against its own note_ttl_s in force at its booking: A at the xout, B at the xin; a note whose expires exceeds B's value is refused by B, and A undoes the hold (below, "Different parameters in A and B") [Proposal]. The app signs with the smaller of the two values [Proposal].
  • The app computes to_amount = floor(amount_minor × r_to / r_from) under the quote (for a price set in the receiver's currency: amount = ceil(price × r_from / r_to), below), shows source, source date and expires, and fetches a new quote if fewer than quote_margin seconds remain.
  • Amounts: minor units, base-10 strings, at most 15 digits, BigInt end to end.
  • 0.2.0 had a separate to_currency tag; in v3 the receiver currency is the third value of to_amount.

Checks and legs#

  • Circle A (payer's): signature and template round trip; n/prev equal the payer's head (else stale_prev); not expired; payer is a member (an excluded key, and a key that left [Proposal], may send only its plus: amount ≤ max(0, balance), never the wave [E-3]); cover amount ≤ balance + wave (no held_out: there are no payouts in BSA, Q6); step outward world_used + amount ≤ step in A's currency (else step_full), where world_used (used) = the balance of world = what A's members paid out to other circles less what came in (xout − xin − xundo), which may be negative, and step = the balance of world-step: what members received from other circles they may spend in other circles even with step 0; the step bounds the net outflow [clarification, 17 Oct 2026 (D4 review)]. A must run B's quote checks exactly as B will (signed by B's node of that time, B's source, not expired at this booking's at, expires within B's own quote_ttl_s as A reads it in B's ledger [Proposal, E-1], to_amount exact) and refuses at intake with a public rej stale_quote or bad_quote, nothing held (booked, since the payer is A's member, up to rej_per_author_per_day refusals of one key a day, 20 by default; above that the intake answers 429 and books nothing; C3, E-1). Books xout (payer → world, held). A republishes the quote to its own relays.
  • Circle B (receiver's): A's block carries a valid quorum of A's signer set (read from A's ledger, cached ≤ 24 h); the note's to circle id (["c", <id>, "", "to"]) is B's; the receiver is B's member (or a left or excluded line with a minus that receives a settlement: to_amount ≤ −balance, no grow, no settled [E-9; the shape: Proposal]); the to_amount currency is B's currency; the named 43919 is valid (above) and is B's own (its c = B's circle id); created_at(quote) ≤ at ≤ expires(quote), where at is A's booking time of xout; to_amount = floor(amount × r_to / r_from) under that quote, exactly, and to_amount > 0. Then B's refusal list (and, only if proposal P5 is accepted, its per-partner sales limit). B books xin only in a block whose at ≤ the note's expires (the note rule "after expires an unbooked note can never be booked", docs/knjiga-na-relejih.md §3.3, binds the receiver's circle too; the 8j check depends on it); a note that reaches B later gets a public rej expired. Books xin (world → receiver, with the consequences of any receipt: wave growth and its counter-booking to the reserve, then drain() to the project that is receiving, oldest opening first [Q8, 8b]; with no project receiving it stays in the reserve [follows from Q8]) or a public rej with reason expired, stale_quote or bad_quote. B books this refusal although the payer is not its member: a note that A's booked xout brought is refused with src = that xout block ([height, digest]), so A can undo its hold [ruling; the exception to C3, under which refusals of keys that are neither members nor applicants are only answered].
  • Several refusal reasons [proposal (D4 review, 17 Oct 2026)]: when more than one reason applies to a note, in A or in B, the reason booked is the first in the order of docs/knjiga-na-relejih.md §3.5.
  • Back in A: xdone naming B's block as [height, digest] (and B's genesis digest g, pinned on every leg), booked only if B's xin embeds the same note id and books exactly to_amount; anything else counts as a refusal. The note id commits to to_amount, its currency and the quote, so A needs no more than the hold's o3|hold line to judge the xdone; that the xin moved exactly to_amount is B's own booking rule, read in that block [clarification, 17 Oct 2026 (D4 review, round 3)]. xundo (world → payer) on B's signed refusal (expired included) or by the 8j check below. A timeout alone never undoes A's booking. The payer gets the held amount back at most once, and only that amount.
  • The 8j check, a hanging payment [Decided, afternoon of 16 Oct 2026, 8j; the encoding is a proposal]: once the hold's check at is reached (A's xout at + A's xcheck_s then in force: 604,800 s = 7 days by default, E-1; recorded in the o3|hold line, so a later change never moves it) with neither xdone nor xundo, A's proposer, and every A witness before it signs, reads B's ledger from B's relays (raw readers, ≥ 2 relays, B's signer set from B's own log; [proposal] B's office is taken from B's log: the office below the lowest block read, then each roles entry in the blocks read, in office from the next height; each seal is judged with the office after its h_to; a block counts only with a valid quorum of B's witnesses in office at its height, so a block or seal copy without one, for example one signed only by old, retired or stolen keys that are no quorum of the witnesses in office at that height, is not B's, never counts and never hides a copy that has one, what rests on an office that a missing block leaves unknown is unverifiable, and a key or witness change in B inside the evidence does not stop the check; noise signed by keys that are no quorum of the witnesses in office at a height cannot stall the return, but a key that was in office at a height, for example the single witness of a one-witness circle later stolen, can still fork that height, and a fork stops the evidence, as in the last case below): every B block whose at lies from A's xout at − XCHECK_MARGIN_S (60 s, a fixed constant of the definitions; the margin is a proposal) to the note's expires, found by height inside B's sealed ranges: B's consecutive seals as [day, digest] and the height range they cover: from the seal whose predecessor covered only bookings before A's xout at − XCHECK_MARGIN_S to the first seal whose boundary is later than the note's expires (strictly), one or two seals (an idle B still seals every day). B's seals chain by digest and each names its predecessor's boundary and covers (the last booking time it covers), so every B block before that range books before the window and every B block after it at or after a boundary later than expires, when the note can no longer be booked (docs/knjiga-na-relejih.md §3.6, §4.5). Then:
    • an xin embedding the note id with exactly to_amount → xdone;
    • a rej of the note → xundo (→ payer), as for any refusal;
    • neither, and the sealed range reaches the note's expires → xundo (→ payer) with reason not_booked, naming those B seals as [day, digest] and the height range they cover, and as before the real B seal before the first of them, which every A witness checks against B's chain (B's quorum; it chains to the first seal exactly as that seal's prev describes; without it nothing is booked); A republishes those seals to its own relays. Every witness checks the evidence against B's blocks, not only its seals: the seal before and every named seal must match B's blocks of their ranges (heights, digests, head, covers, entries, state root), and B's first seal always covers from its block 1. The evidence is closed by B's block right after the last seal (it follows that seal's head and books at or after its boundary) or, when B has booked no block since, by B's next daily seal, named as next (the seal of the day after, chained by prev, with B's quorum and an empty range from h_to + 1; a block at that height booked before its boundary refutes it); before relying on the next seal, a witness confirms on at least two of B's relays that no block exists at h_to + 1. A block or seal that is not given leaves the evidence unverifiable, so nothing is booked; a B that stops sealing keeps the hold waiting.
    • B's blocks or seals cannot be read, the chain has a gap, no B seal yet has a boundary later than the note's expires, two valid B seals exist for one day, or two B blocks with valid quorums exist at one height (a fork: the evidence is not used, proposal) → nothing is booked; A checks again (proposal: once a day). A never guesses, and its other bookings go on. Nobody pays: there is no xrefund and no move from the reserve. An xin that B books after the note's expires is invalid: B's witnesses refuse it, A never answers it with xdone, and any verifier publishes a 43924 fail against B.
  • Different parameters in A and B [Decided 23 Oct 2026, E-1: each circle sets its own values; how they meet: Proposal]. Each circle applies its own values to what it books, and a mismatch can only end in a refusal and a return to the payer, so nothing is lost and both sums stay 0:
    • note_ttl_s: A judges the note at the xout by its own value, B at the xin by its own. If B's value is smaller, B refuses the note (rej with src, bad_template); A books xundo (refused) and the payer gets the held amount back. The app signs with the smaller of the two values.
    • quote_ttl_s: the quote belongs to the receiving (selling) circle B, and both circles judge it by B's own value: B by its parameters in force, A by the value it reads in B's ledger (as it reads B's node and rate source). If A read an older value, either A refuses (nothing moves), or B refuses at the xin (rej with src) and A returns the hold (xundo, refused).
    • xcheck_s: only A uses it, for its own holds (recorded at the xout as the hold's check at). A short wait can only find the evidence incomplete (B's seals do not yet pass the note's expires): nothing is booked and the check repeats; it never returns early.
  • The step a newcomer brings [Decided 23 Oct 2026, E-9 (b); the bookings, the rounding and the step brake: Proposal; docs/knjiga-na-relejih.md §3.11]. In the new circle (N) the member_join that names a reference with a wave W above 0 is followed in the same entry by reference_grant ({rule, in: [43921 id], party, to, amount, to_amount, quote?, mv}): grant origin → reserve G and reference_out reserve → world G; the newcomer's wave grows by G. G = W in one currency; across currencies G = ceil(W × r_N / r_old) under the old circle's 43919 valid at the join, so to_amount = floor(G × r_old / r_N) ≥ W (quote names that 43919). G is held like an xout (o3|hold, the 43921 id as the note id, the reserve as payer) until xdone or xundo on the old circle's refusal; that xundo returns G to the reserve, and grant_revoke follows at once: a reference is valid once, so the unused part of the granted wave goes back to origin and the person stays a member [Decided 23 Oct 2026, E-11]. A grant has no expires, so it is never undone as not_booked: a silent old circle keeps it waiting [Decided 23 Oct 2026, E-11 (1A)]. A join with a step is refused while the key's earlier move out of this circle is not settled (its reference there still waits for its return) [Decided 23 Oct 2026, E-11]. In the old circle reference_return ({rule, in: [43921 id], party, src, g, wave, mv}) books reference_in world → reserve what arrives, then close_wave reserve → origin exactly W (a rounding rest stays in the reserve); it is refused (not_found when no reference of the key waits, no_cover when what arrives is below W), with src, so N undoes its hold. [Decided 23 Oct 2026, E-10 (2A), safety] G counts against N's step outward (world used + G ≤ step); a join whose grant does not fit waits on the waiting list.
  • Every leg is a public entry in its circle's ledger, with every touched account's line after it; no salt, no threshold. A cross-circle payment is ~1.8 KB across both ledgers, plus one quote when it is first used [E].
  • Each circle republishes to its own relays the partner block it relied on, every 43919 its bookings used and the partner seal an 8j xundo relied on, so it restores without the partner and without root.
  • Finality of each leg as for every booking: witness quorum and ≥ 3 relays hold the final event [Decided Q1].

Conversion#

  • to_amount = floor(amount_minor × r_to / r_from) in BigInt, rates in whole ppm, r_EUR = 1,000,000, one rounding, no intermediate rounding. USD↔GBP uses the same quote.
  • A price set in the receiver's currency: amount = ceil(price × r_from / r_to); then to_amount = floor(amount × r_to / r_from) ≥ price (not every target is reachable exactly, so it may exceed the price by one minor unit). A cross-currency pay_request counts as paid when to_amount ≥ the requested amount. to_amount = 0 is refused (bad_quote).
  • Test vectors (example rates, not real ones: r_USD = 1,085,000, r_GBP = 846,000): EUR→USD 10,000 → 10,850; EUR→GBP 10,000 → 8,460; USD→EUR 10,000 → 9,216; USD→GBP 10,000 → 7,797; a price of 10,000 EUR minor units paid from USD: amount = 10,850, to_amount = 10,000; a price of 1 EUR minor unit paid from USD: amount = 2, to_amount = 1; 1 USD minor unit → EUR: to_amount = 0, refused.
  • Each conversion rounds down; a round trip under one quote cannot gain. Two circles with different sources, or quotes made at different times, can leave a gap a round trip could use; that exposure is capped by each circle's step outward.

Accounts and the seal#

  • One currency per circle: 30910 v3 carries exactly one currency, fixed at founding. No exchange accounts.
  • System accounts world and world-step are keyless x-only points lift_x(sha256("lb-system/3|" + role + "|" + circle id + "|" + i)) (the circle id as its 64 hex characters, C5) for the first i that gives a valid point, like the project wallets and the reserve [Decided: Q8 for project wallets and the reserve, 8i for world and world-step]. Nobody can sign for them; only rule entries move them.
  • Raising the step outward is a project [Decided, afternoon of 16 Oct 2026, 8j]: decided by a vote like any project, with no "against" from any member and at least project_approvals Heart members "for" (a circle parameter, at least 3 [E-1, E-5]), and filled by drain() like any project; when it is full, the step rises by its amount. Proposal for the form: the project's recipient is the keyless world-step (no consent, no doer), and project_pay moves the amount to world-step; as a system account it makes no wave growth and no counter-booking. Said openly: so the amount does not flow on, and a step-raise project really uses up community money. The project rules are docs/knjiga-na-relejih.md §3.9; the other step conditions are in docs/upravljanje-kroga.md.
  • Their lines are public s3 lines like every account's; no salt.
  • 43917 v3 carries ["world", cur, used, step, built], ["quotes", <RFC 6962 root over the distinct 43919 ids that the entries of the seal's range name, each once, as 32 raw bytes, sorted ascending>, <count = the number of distinct ids>] [clarification, 17 Oct 2026 (D4 review)] and ["reserve", <balance>], as in the ledger document §3.6. There is no day file and no x tag; the records are on the circle's relays.
  • 43917 v3 names the parameter height, ["params", params_h] (seal tag order: …, signers, circle_key, params, qs…, v; the rules tag is gone [E-1]), and every block carries the parameters in force in its state (g3|param:<name> lines); a change takes effect by the rule entry params_effective at its notice end. So a verifier years later checks each quote and note against the quote_ttl_s and note_ttl_s that applied when it was booked [Consequence of E-1: never retroactive; a running deadline, such as the 8j wait, keeps the due time recorded when it started].

Relays#

  • A 43919 lives on the quoting circle's relays (published = stored by ≥ 3 of them, as every event) and, for every quote a booking used, on the payer circle's relays too. While a circle borrows the Lana relays, those are among its relays (within its quota). The 0.2.0 rule "published = accepted by a majority of the root relays" no longer applies to 43919.
  • Circle relays: accept a 43919 whose c is this circle's id and whose author was this circle's node at the quote's created_at (read from its ledger; the write policy as built uses policy.json circles[].nodes, or a retired key with the role node up to its until, for spam control only), and a partner's republished 43919 (as built: from the node of a partner circle configured on this relay).
  • Root (Lana) relays (proposal for the new letter, Q18): accept a 43919 only from a circle that borrows them, within its quota. The 0.2.0 rule "only from the rate key named in the definitions" is dropped.
  • Every relay: refuse expiration and - on 43919; refuse KIND 5 and 62 aimed at a 43919; keep 43919 out of every purge and hard-delete filter and out of lana-relay-sync.

Reading#

  • Apps read the receiving circle's newest 43919 with {"kinds":[43919],"#c":["<circle id>"],"limit":1} from 2 of its relays in parallel, with raw WebSocket readers (never the nostr-tools pool). "None" needs EOSE from ≥ 2 relays; a silent relay never means "no quote". Report the real "answered N of M". If no valid quote is found, the app asks the node through the circle's write intake (api in its 30910: the record the ledger confirms, signed by the circle key that the ledger's mirror at its mirror_of names, C5).
  • Quotes named by a note are fetched by id. No witness and no app fetches the external source; only the quoting circle's node does.

Tests (when built)#

  • Round trips EUR→USD→EUR and through GBP never gain a cent under one quote.
  • to_amount different from the quote's conversion, or 0, is refused bad_quote; A's at after expires is refused stale_quote; A itself refuses an expired quote at intake and holds nothing.
  • A quote not signed by B's node at created_at, with a wrong c (not B's circle id), a source id that was not B's rate_source at created_at, expires > created_at + quote_ttl_s (B's value; 600 by default), or with expiration or - is invalid (bad_quote); a broken prev chain or a source time earlier than the previous quote of the same source is found by the daily audit and published as a 43924 fail.
  • The conversion test vectors above give the same cents in the node, the witnesses and the verifier.
  • rate_source_set with fewer than 3 of the 4 Heart member signatures is not booked (threshold: proposal); after a source change, earlier bookings stay valid and new quotes carry the new source id.
  • A node with no source rate issues no quote; when the source is silent, quotes keep the newest source rate and show its date; there is no fallback to 38888 or a hard-coded rate.
  • Circle parameters: a param_propose that changes quote_ttl_s or note_ttl_s applies from its params_effective at the notice end (a notice at least as long as the old value; never retroactive); blocks carry the parameters and seals name their height; a restore from the relays judges every quote and note exactly as when it was booked; across circles, a note whose expires exceeds B's note_ttl_s is refused by B (with src) and undone by A, a quote is judged by B's own quote_ttl_s in both circles, and a short xcheck_s in A never returns early: the sum is 0 in both ledgers and nothing is lost [Proposal, E-1].
  • Cross-circle: B's refusal (expired included), booked with src = A's xout block although the payer is not B's member, undoes A's hold once (xundo → payer); a timeout alone never undoes; xdone only for a xin with the same note id and exactly to_amount; B refuses a note after its expires, and A never answers such an xin with xdone.
  • The 8j check: before the hold's check at (xcheck_s, 7 days by default) nothing is booked; after it, B's sealed ledger reaching the note's expires without the note gives exactly one xundo not_booked (→ payer, the held amount); B's xin present gives xdone; B unreadable, a gap in B's chain, seals short of expires, or two valid B seals for one day give nothing, and the check repeats; [proposal] a B block without a valid quorum of B's witnesses in office at its height (for example signed only by an old or stolen key) is ignored, two B blocks with valid quorums at one height give nothing, and a key or witness change in B inside the evidence does not stop the check; every A witness recomputes the check and refuses a block whose 8j entry it cannot reproduce; the reserve and every project wallet are untouched.
  • A circle restored from its own relays alone, without root and without the partner, finds every 43919 its bookings used and reproduces every leg.
  • Every booking keeps each circle's ledger at 0; the counter-booking of xin goes to the reserve and from there to a project wallet, never to a keyed account.