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=1stays 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
deporcorrentry, no custodian, nopaid_in; a wave grows only from receipts. Start sourceecb-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 parameterquote_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 throughdrain()). 8iworldandworld-stepare 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'sexpires→xundoto 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 validact_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 asd; 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 anatag; 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_sof 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) andproject_approvals(3, at least 3 and at most 9; the step-raise project included); the bounds are a proposal. A change byparam_propose(k Heart members) takes effect only after a notice of at leastchange_notice_sand at least the old value of every changed time [Proposal]; running deadlines keep their recorded due time (the 8j check'scheck 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 namessteward_*stay [Proposal]. E-5 a project, the step raise included, is decided by a vote: no "against" from any member and at leastproject_approvalsHeart 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, nogrow, nosettled) [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
relaytags 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), thexrefundentry, 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 address30910:<circle pubkey>:<code>as a circle's name, with theatags of 43919 and 43910 and the#aqueries (now the circle id,ctags and#c, C5);quote_ttlandproject_approvalsas fixed constants of the definitions mirrored in the 30910 (at that time the world rule valuesQUOTE_TTL_SandPROJECT_APPROVALSof the BSA Rules, 43920, which the 30910 no longer carries, C1; since 23 Oct 2026 the circle parametersquote_ttl_sandproject_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, theg3|rulesline and the seal'srulestag:QUOTE_TTL_S,NOTE_TTL_S,XCHECK_SandPROJECT_APPROVALSare now the circle parametersquote_ttl_s,note_ttl_s,xcheck_sandproject_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 actrate_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 ofquote_ttl_s(300…3,600 s),note_ttl_s(60…3,600 s) andxcheck_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'scheck at= A'sxoutat+ A'sxcheck_sthen in force, in itso3|holdline) [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.jsonand 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 inkinds.jsonon 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) = itsexpires. - 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'screated_at, checked against that circle'sroleswindows 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 orrolesentry'sat≤ 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>"](forecb-dailythe 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_sis a parameter of the quoting circle: 600 s by default, bounds 300…3,600 s (proposal); its Circle Heart changes it withparam_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 theexpirestag, 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):
- signed by that circle's node at
created_at;cnames that circle (its circle id); source time ≤ created_at < expires ≤ created_at + quote_ttl_s(the quoting circle's value in force at the booking; 600 s by default);- the source id equals the circle's
rate_sourcegovernance line in force atcreated_at; - the payer circle's
xoutat≤expires, andto_amountequals the conversion exactly (below). Refusal reasons:stale_quote(expired),bad_quote(anything else).
- signed by that circle's node at
- 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
prevchain 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 43924fail. - 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_marginleft (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 (noquote_ttl, noproject_approvals). Replacesrate_panelandrate_rule(andg3|rate_panel|…,g3|rate_rule|…). - Set at genesis:
ecb-daily[Decided, afternoon of 16 Oct 2026: the start source]. [Proposal] changed only by arate_source_setact (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 arolesentry carrying the new 30910 mirror; effective for quotes whosecreated_atis at or after that entry'sat(a quote takes the source of the newest mirror booked at or before itscreated_at,docs/knjiga-na-relejih.md§4.5) [clarification, 17 Oct 2026 (D4 review)]; no notice period (proposal). Replaces the actrate_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 mostquote_ttl_s, 600 s by default [Decided 16 Oct 2026: 10 minutes; a circle parameter since 23 Oct 2026, E-1; arate_source_setact 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"](thetotag 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>"], plusp,n,prev,v=3as 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_sis a circle parameter: 600 s by default, bounds 60…3,600 s (proposal); each circle's Heart changes its own withparam_propose, with notice and never retroactively (decided 23 Oct 2026, E-1). Each circle checks a note against its ownnote_ttl_sin force at its booking: A at thexout, B at thexin; a note whoseexpiresexceeds 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 andexpires, and fetches a new quote if fewer thanquote_marginseconds remain. - Amounts: minor units, base-10 strings, at most 15 digits, BigInt end to end.
- 0.2.0 had a separate
to_currencytag; in v3 the receiver currency is the third value ofto_amount.
Checks and legs#
- Circle A (payer's): signature and template round trip;
n/prevequal the payer's head (elsestale_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]); coveramount ≤ balance + wave(noheld_out: there are no payouts in BSA, Q6); step outwardworld_used + amount ≤ stepin A's currency (elsestep_full), whereworld_used(used) = the balance ofworld= what A's members paid out to other circles less what came in (xout−xin−xundo), which may be negative, andstep= the balance ofworld-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'sat,expireswithin B's ownquote_ttl_sas A reads it in B's ledger [Proposal, E-1],to_amountexact) and refuses at intake with a publicrejstale_quoteorbad_quote, nothing held (booked, since the payer is A's member, up torej_per_author_per_dayrefusals of one key a day, 20 by default; above that the intake answers 429 and books nothing; C3, E-1). Booksxout(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
tocircle 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, nogrow, nosettled[E-9; the shape: Proposal]); theto_amountcurrency is B's currency; the named 43919 is valid (above) and is B's own (itsc= B's circle id);created_at(quote) ≤ at ≤ expires(quote), whereatis A's booking time ofxout;to_amount = floor(amount × r_to / r_from)under that quote, exactly, andto_amount > 0. Then B's refusal list (and, only if proposal P5 is accepted, its per-partner sales limit). B booksxinonly in a block whoseat≤ the note'sexpires(the note rule "afterexpiresan 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 publicrejexpired. Booksxin(world→ receiver, with the consequences of any receipt: wave growth and its counter-booking to the reserve, thendrain()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 publicrejwith reasonexpired,stale_quoteorbad_quote. B books this refusal although the payer is not its member: a note that A's bookedxoutbrought is refused withsrc= thatxoutblock ([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:
xdonenaming B's block as[height, digest](and B's genesis digestg, pinned on every leg), booked only if B'sxinembeds the same note id and books exactlyto_amount; anything else counts as a refusal. The note id commits toto_amount, its currency and the quote, so A needs no more than the hold'so3|holdline to judge thexdone; that thexinmoved exactlyto_amountis 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 (expiredincluded) 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 atis reached (A'sxoutat+ A'sxcheck_sthen in force: 604,800 s = 7 days by default, E-1; recorded in theo3|holdline, so a later change never moves it) with neitherxdonenorxundo, 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 eachrolesentry in the blocks read, in office from the next height; each seal is judged with the office after itsh_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 whoseatlies from A'sxoutat−XCHECK_MARGIN_S(60 s, a fixed constant of the definitions; the margin is a proposal) to the note'sexpires, 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'sxoutat−XCHECK_MARGIN_Sto the first seal whose boundary is later than the note'sexpires(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 andcovers(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 thanexpires, when the note can no longer be booked (docs/knjiga-na-relejih.md§3.6, §4.5). Then:- an
xinembedding the note id with exactlyto_amount→xdone; - a
rejof the note →xundo(→ payer), as for any refusal; - neither, and the sealed range reaches the note's
expires→xundo(→ payer) with reasonnot_booked, naming those B seals as[day, digest]and the height range they cover, and asbeforethe 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'sprevdescribes; 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'sheadand books at or after its boundary) or, when B has booked no block since, by B's next daily seal, named asnext(the seal of the day after, chained byprev, with B's quorum and an empty range fromh_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 ath_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 noxrefundand no move from the reserve. Anxinthat B books after the note'sexpiresis invalid: B's witnesses refuse it, A never answers it withxdone, and any verifier publishes a 43924failagainst B.
- an
- 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 thexoutby its own value, B at thexinby its own. If B's value is smaller, B refuses the note (rejwithsrc,bad_template); A booksxundo(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 thexin(rejwithsrc) and A returns the hold (xundo, refused).xcheck_s: only A uses it, for its own holds (recorded at thexoutas the hold'scheck at). A short wait can only find the evidence incomplete (B's seals do not yet pass the note'sexpires): 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) themember_jointhat names a reference with a wave W above 0 is followed in the same entry byreference_grant({rule, in: [43921 id], party, to, amount, to_amount, quote?, mv}):grantorigin → reserve G andreference_outreserve → 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, soto_amount= floor(G × r_old / r_N) ≥ W (quotenames that 43919). G is held like anxout(o3|hold, the 43921 id as the note id, the reserve as payer) untilxdoneorxundoon the old circle's refusal; thatxundoreturns G to the reserve, andgrant_revokefollows 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 noexpires, so it is never undone asnot_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 circlereference_return({rule, in: [43921 id], party, src, g, wave, mv}) booksreference_inworld → reserve what arrives, thenclose_wavereserve → origin exactly W (a rounding rest stays in the reserve); it is refused (not_foundwhen no reference of the key waits,no_coverwhen what arrives is below W), withsrc, 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
xundorelied 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); thento_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-currencypay_requestcounts as paid whento_amount ≥the requested amount.to_amount = 0is 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
worldandworld-stepare keyless x-only pointslift_x(sha256("lb-system/3|" + role + "|" + circle id + "|" + i))(the circle id as its 64 hex characters, C5) for the firstithat gives a valid point, like the project wallets and the reserve [Decided: Q8 for project wallets and the reserve, 8i forworldandworld-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_approvalsHeart members "for" (a circle parameter, at least 3 [E-1, E-5]), and filled bydrain()like any project; when it is full, the step rises by its amount. Proposal for the form: the project's recipient is the keylessworld-step(no consent, no doer), andproject_paymoves the amount toworld-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 aredocs/knjiga-na-relejih.md§3.9; the other step conditions are indocs/upravljanje-kroga.md. - Their lines are public
s3lines 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 noxtag; 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; therulestag 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 entryparams_effectiveat its notice end. So a verifier years later checks each quote and note against thequote_ttl_sandnote_ttl_sthat 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
cis this circle's id and whose author was this circle's node at the quote'screated_at(read from its ledger; the write policy as built usespolicy.jsoncircles[].nodes, or aretiredkey with the rolenodeup to itsuntil, 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
expirationand-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 (apiin its 30910: the record the ledger confirms, signed by the circle key that the ledger's mirror at itsmirror_ofnames, 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_amountdifferent from the quote's conversion, or 0, is refusedbad_quote; A'satafterexpiresis refusedstale_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 wrongc(not B's circle id), a source id that was not B'srate_sourceatcreated_at,expires > created_at + quote_ttl_s(B's value; 600 by default), or withexpirationor-is invalid (bad_quote); a brokenprevchain or a source time earlier than the previous quote of the same source is found by the daily audit and published as a 43924fail. - The conversion test vectors above give the same cents in the node, the witnesses and the verifier.
rate_source_setwith 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_proposethat changesquote_ttl_sornote_ttl_sapplies from itsparams_effectiveat 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 whoseexpiresexceeds B'snote_ttl_sis refused by B (withsrc) and undone by A, a quote is judged by B's ownquote_ttl_sin both circles, and a shortxcheck_sin A never returns early: the sum is 0 in both ledgers and nothing is lost [Proposal, E-1]. - Cross-circle: B's refusal (
expiredincluded), booked withsrc= A'sxoutblock although the payer is not B's member, undoes A's hold once (xundo→ payer); a timeout alone never undoes;xdoneonly for axinwith the same note id and exactlyto_amount; B refuses a note after itsexpires, and A never answers such anxinwithxdone. - 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'sexpireswithout the note gives exactly onexundonot_booked(→ payer, the held amount); B'sxinpresent givesxdone; B unreadable, a gap in B's chain, seals short ofexpires, 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
xingoes to the reserve and from there to a project wallet, never to a keyed account.