Add RFP-027 (LEZ Node API) and supporting appendices - #200
Draft
fryorcraken wants to merge 61 commits into
Draft
Conversation
Surveys what blockchain node APIs and client SDKs expose across Ethereum, Bitcoin, Solana, XRPL, Cosmos, Stellar, NEAR, and Sui: 34 API functions, transport types, and SDK languages per ecosystem. Logos L1 and LEZ appear as rows throughout, with a gap summary of what is absent or narrower than the surveyed norm. Every claim cites first-party documentation or, for Logos, a file and line in logos-blockchain and logos-execution-zone. All 83 references were fetched and confirmed to resolve. Unsourceable entries are marked [NOT FOUND] rather than inferred. Supports the blockchain API RFPs tracked in logos-co/ecosystem#219, #220, and #222. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two integrity failures: - Section 1.15 attributed two quoted phrases to Stellar's documentation that do not appear on that page. Replaced with the wording the source uses. - Section 3.1 claimed XRPL's docs label a Go SDK "official" and built a conflict narrative on it. That page contains no maintainership labels. Factual corrections: - Logos L1 does accept a stream start position: /cryptarchia/blocks_range takes slot_from and slot_to. Stream resumption was wrongly listed as a gap absent on both targets. - XRPL publishes a first-party Rust SDK (XRPLF/xrpl-rust); it was marked [NOT FOUND], which also contradicted the surrounding prose. - LEZ exposes getChannelId, so network identification is absent on Logos L1 alone, not on both targets. - Estimate execution cost is 5 of 8, not 6. - Corrected an XRPL pagination misquote and an overstatement of what the bdk-ffi repository itself builds. Also aligned the Sui row in 1.4 with the [NOT FOUND] convention used for Stellar and NEAR, qualified LEZ getSchema as a type schema rather than an interface description, recited the Cosmos REST broadcast paths to CometBFT, and made the Bitcoin cell in 1.15 agree with the stated count. All 85 references re-fetched and resolving; every citation marker is matched to a reference with no orphans. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Section 3.2 cited Stellar's build-your-own-SDK guide as evidence that Stellar argues for hand-writing client SDKs. That page scopes itself to contract SDKs: "This is for building an SDK for writing smart contracts." It says nothing about client SDK strategy. Replaced with what the sources support: no surveyed chain publishes a written argument for hand-writing client SDKs over generating them. Found by an origin-research agent that checked a premise it had been given rather than accepting it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds two dimensions the catalogue lacked: what each call returns, and why it came to exist. Both are drawn from standards documents, architecture decision records, changelogs, and maintainer discussions, all fetched and quoted verbatim. Response shapes, for the calls where the request is empty and the response carries the design: - Discovery: Cosmos returns a dependency manifest with per-module versions rather than a version string; Logos L1 returns a bare JSON string with no field structure at all. - Health and sync: this is where retention bounds live. Bitcoin splits sync into validated-chain and known-header heights and expresses retention as four fields; XRPL expresses it as a possibly disjoint range expression and signals finality by which field is present. - LEZ discloses indexer staleness through getStatus, which most surveyed chains do not, and which the appendix previously did not credit. - Simulation, submission, and construction-parameter responses. Origin history, replacing six [NOT FOUND] entries: - ERC-191 and NEP-413 independently identify the same hazard, that a signed message must not be reinterpretable as a transaction, and solve it the same way. - XRPL renamed a response field after the old name's implied guarantee was exploited to steal from institutions; shipped behind an API version. - Aptos ships server-side encoding and warns in its own source not to trust it, which is why construction stays client-side elsewhere. - Removing node-side signing let go-ethereum log RPC payloads safely for the first time. - EIP-1898 on read coherence under reorg; EIP-4444 on why retention must be declared rather than discovered; EIP-234 on why an empty result must still identify its position. - Cosmos ADR-070 names IBC relayers and exchanges as the users hurt by sequence-number fetching. Also records two retirement mechanics worth reusing: Algorand's 410-versus-404 distinction, and Solana's fifteen-endpoint removal. 32 new references, all fetched and resolving. Every quotation was verified character-for-character against raw source with whitespace normalised. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Response shapes were prose blobs listing per-chain field names. They are now tables keyed by what the caller learns, so the same concept aggregates across chains that name it differently: "retention floor" gathers Cosmos earliest_block_height and Bitcoin pruneheight, "did it succeed" gathers Cosmos code and XRPL engine_result, and so on. Five sections restructured: node version, health and sync, simulation, submission, and execution effects. Adds a short section introducing the two absence markers, which were previously explained in a single buried sentence. It distinguishes what each means, lists the three reasons a table cell might read [NOT FOUND] (the capability does not exist, exists under an unrecognised name, or lives somewhere unread), and states plainly that neither marker is evidence of a gap in the chain, only the boundary of what was verified. Rationale prose now reads "No specific or relevant context has been found" rather than [NOT FOUND]; the short marker stays in table cells where column width is tight and the meaning is narrower. Also corrects a stale count: the intro claimed rationale was sourced in four cases, which predated the origin research. It is eleven of thirty-four. Every field name in the new tables verified against its cited source. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Section 1.2 marked Logos L1 as having no health or sync-status call. It does. GET /cryptarchia/info returns a state field carrying the consensus engine state (Bootstrapping or Online) and a phase field carrying a four-stage lifecycle: AwaitingGenesisTime, InitialBlockDownload, ProlongedBootstrapPeriod, Following. That is finer-grained than the boolean most surveyed chains return, and the first phase is the only case in the survey where a node documents that it will serve reads but not writes. The response also carries lib and lib_slot, a finality anchor alongside the tip. Records one inconsistency found while checking: the endpoint's OpenAPI annotation declares the body as CryptarchiaInfo, while the project's own HTTP client deserialises it as ChainServiceInfo, which additionally carries phase. The published contract and the first-party client disagree about whether phase is part of the response. This is the false-negative case the document's own sourcing section warns about: a capability present in the code but not found by the survey. Caught by user challenge, not by the fact-checking passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A source-level re-audit of both Logos codebases, prompted by the sync-status false negative, found nine wrong cells. Several were false positives, claiming capability that is not there: - Validator set: the SDP routes list Blend mixnet providers, not consensus participants; the service-type enumeration has one variant. - Broadcast: POST /mempool/add/tx returns an empty body, so unlike every surveyed chain L1 returns no identifier to track a transaction by. - Health: the LEZ sequencer's checkHealth is an unconditional empty success, so all sync reporting comes from the indexer alone. - Transaction status: the OpenAPI declares one type while the service returns another, and the status enum is only Unknown or Pending, so Unknown conflates never-seen, mined, and evicted. - Machine-readable description: L1's OpenAPI omits five wired routes including both signing endpoints and registers three schemas. - Network identity: a chain id is inscribed at genesis and read at ledger init, so the earlier flat absence was overstated. Adds a Potential gaps subsection to each of the 34 functions, with one entry for Logos L1 and one for LEZ, stating what is missing or narrower relative to surveyed practice and citing the source read. Where a surface is comparable it says so rather than inventing a gap. These describe observed state only; the RFP does the prescribing. Notable: a second contract-versus-implementation disagreement, matching the one already recorded at 1.2; L1 signing routes accept a caller-supplied digest with no transaction attached; LEZ programs emit no events at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A layer-by-layer inventory of what the Logos blockchain APIs expose today, so other documents can cite a stable reference rather than re-deriving it from source each time. Covers the LEZ indexer FFI (the read surface applications reach), the indexer RPC behind it, the sequencer RPC, the wallet FFI and lez_core module, the L1 C bindings, the L1 node HTTP routes, and the L1 module. The structural finding it records: two independent stacks serve LEZ and they do not meet. The lez_core module holds no indexer client, and the indexer stack performs no writes, so a capability reaches an application only through the module at the top of one stack or the other. Two boundaries are marked explicitly. Four indexer RPC methods stop at the FFI rather than at the module, including getAccountAtBlock and the finalized-block subscription, so the FFI is where the LEZ read surface is bounded. And nine sequencer methods have no indexer counterpart, of which getAccountBalance and getAccountsNonces are derivable from data the indexer already stores, since its Account record carries both balance and nonce. All counts read from source at the commits named in the document. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three adversarial reviewers checked the API surface reference against the codebases, one per area. All three returned FAIL. Corrections: Factually wrong: - The C header is not committed. It is gitignored and generated by cbindgen at build time, so a fresh clone contains none. The document described "the committed header" and miscounted it as eleven declarations; the stale local artefact has ten, two naming functions that no longer exist. - The indexer FFI does distinguish not-found from failure, contrary to what the document claimed. The source says an empty result "is distinct from an error"; blocks and transactions carry the same distinction through an option type. The real weaknesses are narrower and now stated: query_account cannot express not-found at all because the store returns a default account, and query_status has no status channel. - start_indexer takes three parameters and returns a pointer result carrying the handle, not a bare status. - The OpenAPI document omits six wired routes, not five. - Six sequencer methods lack an indexer counterpart, not nine. The document's own table already said six. - The lez_core module exposes 48 methods, not 46; the count missed two const-qualified accessors. - The indexer module has 11 public methods, not 12, and does not wrap the FFI one to one: reset_storage wraps nothing, calling the module's own stop and deleting the store directory from C++. Imprecise, now fixed: - The L1 module commit is pinned rather than left as "current". - "Most successful responses" replaced with twenty of thirty-six. - Section 6 explains why its table lists 44 paths against 42 constants. Also records that the indexer module flattens the FFI's error signalling into an empty string, losing a distinction the FFI does make. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Potential gaps notes assessed LEZ against the sequencer and indexer RPC. That is the wrong altitude: an application reaches LEZ through a Logos Core module over an FFI, so a capability present on a service RPC but absent from every FFI is not available to anyone and is a gap. Twelve LEZ bullets rewritten. Nine credited capabilities that stop below the FFI: - Network identity, machine-readable description, pinned historical read, and block subscription all exist on the indexer RPC with no FFI export. - Node version, balance for an arbitrary address, transaction lookup carrying a block id, and batch nonces all exist on the sequencer RPC with no FFI export. Two were analysing the quality of something unreachable: the pinned-read note discussed retention for a read applications cannot make, and the stream-resume note discussed reconnection for a subscription that cannot be opened. Three were understated rather than wrong. Transaction status has a reachable call, poll_transaction_status, returning a bare boolean that conflates never-seen, pending, and rejected. The wait note described a retry loop that lives below the boundary. The balance note criticised the wrong function. Adds a framing paragraph to the Gap Summary naming the boundary and listing the ten methods that stop at it, and cites the companion surfaces appendix. Found by an adversarial review of all 34 functions against the four repos, corroborating an independent pre-check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The node API survey had a blind spot: it treated node-side signing only as a custody boundary chains retreat from, and never asked what an integrator uses instead. It contained no mention of BIP-32, BIP-39, BIP-44, mnemonics, derivation paths, or extended public keys, and none of its 34 functions covered key or address derivation. An exchange needs deposit-address derivation and offline signing before it needs any read method. This is a separate appendix rather than a section, because wallet libraries are a different category from the node API surfaces the other document inventories. rust-bitcoin, BDK, ethers.js, and bitcoinjs-lib derive keys and sign locally, much of which never touches a node. Corrects two things the research established against my initial framing. "Ethereum removed node signing" is too strong: geth removed its own custody namespace, while eth_sendTransaction, eth_sign, and eth_signTransaction remain in the standard specification. And geth's documented replacement is Clef, an external signer that still speaks RPC, not solely the wallet-library path that integrators actually take. Both are recorded, with the removed-to-replacement mapping from geth's own deprecation page. Records the finding that watch-only deposit-address derivation, the capability an exchange needs most, splits on the signature curve rather than on design: secp256k1 chains support public derivation, ed25519 chains permit only hardened derivation and so cannot. Stellar's SEP-0005 states both halves of that case. Chains without it adopt a different deposit model, such as muxed accounts or destination tags. Also notes that Bitcoin and Ethereum, the two largest, are the only two with no first-party wallet library, and covers hardware and offline signing including the Cosmos constraint that Ledger accepts only the deprecated Amino encoding. All 20 references fetched and resolving; quotations verified verbatim. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Convert the bracketed [n] citation markers to [[n]](#ref-n) links resolving to anchored reference entries, so citations are navigable in both GitHub and Obsidian. Hyperlink the five source repositories in the API surfaces table. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Defines the read API for LEZ as exported functions on the indexer FFI: 24 Functionality requirements covering effects, simulation, per-transaction status, batch and pinned account reads, subscriptions, pagination, and error semantics. Each requirement carries a readiness marker (Ready, Ready but not exposed, Computed but not persisted, or New) classified against the LEZ source, so a proposer can see which requirements are exposure work and which are new. Scoped to the indexer read surface, one of six deliverables for the wider blockchain API and SDK work (logos-co/ecosystem#219). The sequencer is excluded: its own source marks its account queries transitional, and indexer to sequencer communication is expected to move to a libp2p mempool. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A program event system merged to the LEZ default branch on 2026-08-27, after the commit these appendices pin. ProgramOutput gained an events field, and events are queryable by getEvents, by a subscribeToEvents subscription, and over the FFI as query_events, filtered by block range, transaction hash, program, and selector. The LEZ cells for sections 1.28 and 1.32 said [NOT FOUND]. They now name the methods, with a new reference 121 pinned to the dev branch rather than restating the survey's own pinned commit. Two limits are recorded: events are dropped for privacy-preserving transactions, and an indexer captures only what its configured event filter matches. Also notes that a ninth FFI query, query_events, exists after the commit the API surfaces appendix pins. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Functionality section specified 24 deltas against the current FFI, so functions that exist and are used daily (query_block, query_account, query_transaction, query_status, query_block_by_hash, query_events) had no requirement at all. A proposer had to read LEZ source to learn the baseline, and existing behaviour had no acceptance criteria. It now defines 21 functions across eight groups, each with a signature, its semantics, and requirements carrying a readiness marker: 15 Ready, 5 Ready but not exposed, 5 Computed but not persisted, 22 New. Each group opens with an itemised list of the rough equivalent on Ethereum, Bitcoin, Solana, and Zcash. Zcash is included because it is the closest production analogue to LEZ: every transparent read is address-indexed and open, every shielded read is viewing-key scoped, which is the same boundary LEZ has. Adds simulation cost reporting. LEZ caps public execution at 32M cycles and already runs execute-only, so the executor's user-cycle count is computed on every public transaction and discarded at lee/state_machine/src/program/mod.rs. Returning it costs nothing. Cost is reported as cycles rather than a fee, since there is no fee system on the default branch, and exhausting the cycle budget must be distinguishable from a program error: every executor failure currently collapses into ProgramExecutionFailed(String). Corrections found by verifying every claimed-existing signature against source: query_account returns a bare FfiAccount, not FfiOption, so the target signature is now stated separately from the current one; query_chain_tip has no capability below the FFI and is New rather than exposure work; the zone identifier is read from indexer config, not derived from chain data; the before-exclusivity requirement is split from the has-more signal, which does not exist; and two citations of the transaction wire types are corrected to their current lines. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
RFP-021 cited Zcash in six places as a surveyed chain, but the appendix surveyed only eight and never mentioned it. Those claims were unsourced. Zcash now has a row in all 34 function tables plus the chains, transport, and SDK tables, with 12 first-party references. Counts that quantified the survey were recomputed rather than incremented, since Zcash falls on the supported side of several of them. Corrections found against first-party sources: Zcash carries no testmempoolaccept, so it has neither simulation nor an acceptance check; getaddressbalance, getaddresstxids, getspentinfo and getblockdeltas are insight-explorer gated rather than open on an arbitrary node; and z_getbalanceforviewingkey requires the key already imported into the node's wallet. RFP-021 still needs updating to match. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… API The section argued from a source comment marking sequencer methods for removal, which conceded the sequencer was a target that happened to be going away. The architectural point is stronger: the sequencer is inside the black box, and the indexer is the only surface a consumer sees. The indexer serves every read as a cache and proxy, sourcing from wherever inside LEZ the data lives. The libp2p migration now argues that internal routing is invisible to consumers rather than sitting as an aside. Also drops effects requirement 5. No integrator in this RFP's own list acts differently for it: an exchange must not credit a private deposit either way, and a wallet reads bodies rather than effect summaries. Requirement 2 already discriminates the transaction body type. Renumbering is deferred to a later pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The wallet syncs private accounts by pulling ascending block ranges from the sequencer and trial-decrypting each privacy-preserving body, and it fetches commitment membership proofs from the sequencer to spend a note. Both are consumers reaching into what the rationale now calls internal, and neither had an indexer requirement. query_block_vec becomes query_blocks, matching the plural-noun naming of query_accounts and query_transactions_by_account and dropping a container type from an API name, and takes an ordering parameter on the same terms as query_transactions_by_account. A descending-only read cannot serve a forward scan. The as-built name stays in the API surfaces appendix. Adds commitment membership proofs as a read, carrying the shape the sequencer's getProofsAndRoot returns today. Out of Scope no longer argues method by method. That mapping was both the wrong argument under the new rationale and incomplete, omitting getBlockRange and getProofsAndRoot. Numbering is left inconsistent for the deferred renumbering pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every Zcash bullet was written before Zcash was in the appendix, so each was checked against the survey rows and their prose. Four cited methods are insight-explorer gated rather than generally available: getaddressbalance, getaddresstxids, getspentinfo and getblockdeltas need a node started with txindex, experimentalfeatures and insightexplorer, then reindexed. That is a property of the deployment, not of the chain, and the RFP described getaddressbalance as open to anyone. z_getbalanceforviewingkey carries a second condition the RFP omitted: the key must already be imported into the node's wallet, so it is not a query against an arbitrary node with a key presented inline. The claim that getblockchaininfo reports the active consensus branch is not supported by the appendix, so it is removed rather than left standing on an unverified source. Zcash also publishes a ZeroMQ surface, worth stating where the Bitcoin bullet beside it cites ZeroMQ, and it lacks Bitcoin's -zmqpubsequence loss-detection publisher. Resolves the simulation TODO: Zcash has no equivalent on either leg, and carries no testmempoolaccept, so it does not inherit even the acceptance check Bitcoin offers. Survey counts move from eight chains to nine. Simulation stays at six, Zcash adding to the denominator only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
On LEZ a privacy-preserving transaction is executed by the wallet, which runs the program locally over notes only it can decrypt and submits a proof that the execution was correct. The proof is an input to the transaction rather than a result of executing it, so a node holds neither the witness material nor the plaintext a simulation would need, and there is nothing for the indexer to simulate before the wallet has already done the work. Zcash reaches the same conclusion from the same premise. Public execution could be simulated by the indexer, but siting it there splits one capability across two components and answers against finalised state rather than the state the caller is building on. The wallet already holds the executor for the private path, so it can answer for both kinds of transaction against a state it chose. That is also what simulation is for elsewhere: Stellar returns the transaction data and minimum resource fee the caller copies back in before submitting, which makes it a construction step rather than a read. The design rationale now records why the one norm an RPC provider is expected to offer does not fit LEZ, and Out of Scope names it so a proposer finds it where they look. Drops the six simulation requirements and the reliability, performance, usability and test obligations that referenced them. Resolves the simulation privacy TODO, the gas-assumption TODO, and the misleading figure TODO, all of which sat on requirements that no longer exist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every function in the RFP took the handle as its first argument, so repeating it two dozen times said nothing a reader did not already know from the document being an indexer API. The handle is real: a C ABI has no receiver, so it is a parameter in the generated header, and each export null-checks it. That convention is now stated once in the readiness preamble, along with the null-handle outcome, rather than being implied by every signature and stated nowhere. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A count is not how pagination works. The requirement that every paginated response reports whether more results remain is what lets a caller walk to the end, and it already stands. No surveyed chain exposes a count of an account's transactions on its node API. Every chain that answers for address history returns a listing: Bitcoin's listtransactions takes count as a page size rather than returning a total, Solana, XRPL, Cosmos, Stellar and Sui return entries, and Ethereum and NEAR have no such method at all. Ethereum's eth_getTransactionCount is the nonce, a construction parameter rather than a history count. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The RFP required that a caller walking an account's transactions neither skips nor repeats an entry, and then specified an offset, which cannot hold that property: entries landing ahead of the offset shift every later position. The reference integrator is an exchange reading deposits newest-first, which is exactly the walk that breaks. query_transactions_by_account and query_pending_digests now take an opaque cursor, and every paginated response carries the cursor to resume from alongside the has-more signal, so a caller never constructs a position itself. The pending set is the sharper case, since entries leave it as they are included, so an offset there names a different entry between calls. Cursors are also the dominant idiom among the surveyed chains: XRPL, Stellar and Sui cursor, Solana anchors on a signature, and Bitcoin alone pages by offset. query_blocks keeps its block bound, which is a position in an append-only sequence rather than an index into a shifting set, and stays in the pagination section: it walks history, while the metadata section answers where the chain ends and which network this is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The signatures no longer carry the handle and the convention is stated in the readiness preamble; only the note asking for it was left behind. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The RFP asked the account reads to distinguish a never-seen account from one holding zero, which does not survive contact with what the record holds. An Account is program_owner, balance, data and nonce, all Default-derived, so the record returned for an unknown identifier is the default account, which is also what an uninitialised account holds. An account that has ever signed carries a nonce, one owned by a program carries an owner, one holding data carries data, so the cases a caller cares about are already legible from the record. What remains ambiguous is an account materialised by a zero-value touch and never used again, which is empty in every sense a caller acts on. That is thin ground for an ABI break on the one existing export, so the signature stands and the API documents that the read answers what an account holds rather than whether it exists. The reads are now scoped explicitly to public state. A private balance has no account record to return at all: private state is a commitment set and a nullifier set carrying no account identifier, so it is not an encrypted row the API declines to read, it is not a row. A default record therefore says the state holds no public balance for that identifier, and is not evidence the party holds nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
BlockId is a u64 height counting from a genesis of 1, and the codebase treats it arithmetically: pagination subtracts one from it, snapshots index by dividing it, and the sequencer assigns a local named new_block_height straight into the field. The hash is the separate 32-byte value the header carries, held in storage as a secondary index onto the height. The distinction matters because a height names a position rather than a block. The sequencer's reorg handling describes inscribing a second block at a height the channel already holds, so one identifier can resolve to different blocks at different times while a hash resolves to one block or to none. A new rationale section defines the two terms. The block subscription now delivers height and hash together, since a height alone does not say which block occupied it; BlockMeta already exists as that pair. The pinned account read returns the hash it answered against, and reliability #1 asks for the same so a repeated read can be shown to have resolved to the same block. Also fixes the chain tip requirement, which asked for "the block identifier together with the block's height" as though those were two values. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
query_zone_id becomes query_network_identity and answers both halves of the question an integrator is really asking, since a zone identifier alone does not distinguish the same zone running against different L1 networks, and answering the two separately lets a caller check one and assume the other. The chain identifier is the one inscribed in the Logos Blockchain genesis block as a Cryptarchia parameter, a bounded UTF-8 string such as logos-chain-1, rather than a value the indexer is configured with: a configured string agrees with whatever an operator typed rather than with the chain the node settles to. That value is real but unserved. It is read at ledger initialisation, no L1 route returns it, and the indexer's Bedrock configuration carries only an endpoint, so reaching it is work outside the indexer and a proposal says how. Platform Dependencies now records this as the one exception to the no-blockers claim, and a further requirement keeps the surface deliverable meanwhile by having the call report the value as unavailable rather than returning a placeholder. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The section already asked for a README and two doc packets, but neither covers the module author's path: how to take an empty repository to a Logos Core module that consumes this FFI. The README documents building and operating what this RFP delivers, which is a different reader. The versioned API reference was absent entirely. It is specified as generated from the machine-readable surface description required by Functionality #50 rather than maintained by hand, so it cannot drift from the header it describes, and it is a standing per-version obligation rather than a packet submitted once. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each of the six deliverables now links its own issue in the ecosystem repo: #235 for this RFP, #236, #237, #238, #239, and #222. Item 5 becomes the indexer client library. It is a client for the JSON-RPC surface item 3 exposes rather than a library with logic of its own, which is what separates it from the wallet SDK, and the shared SDK suffix made two opposite things look alike. The issue is retitled to match. Out of Scope cited #220, the older issue predating the LEZ split, and now points at the deliverables list instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A transaction failing validation during block production is logged and omitted from the block with no record kept, so a caller polling status saw "not known to the indexer" forever, indistinguishable from a transaction never submitted. Adding submission sharpened this: the RFP handed a caller a hash to track through a query that could never tell it the transaction was gone. Every surveyed chain that answers this answers it the same way, through the mempool. Absence from the chain alone means nothing; absence from both the chain and the pending set is what distinguishes a declined transaction from one still in flight. The pending set requirements existed but were justified only as proof-invalidation for wallets, so nothing connected them to the question a submitter actually asks. The pending set is now queryable by transaction hash, since that is what submission returns, and status gains a pending level derived from it. Past a transaction's validity window, absent from both surfaces is reported distinctly rather than as unknown, which is the same definitive answer Solana gets from blockhash expiry. Before that window, the API says plainly that the case is not distinguishable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The design rationale committed to a LEZ with gas, where cost is something to report and budget against, but no requirement asked for any of it and the word fee appeared nowhere in the scope. An exchange reconciling a withdrawal could not tell what it sent from what it paid to send it, and would have had to reconstruct the fee by arithmetic on the remaining deltas, which means reimplementing the chain's fee rules. The effects query now reports the fee separately, naming the account charged and the amount. That account is not always a signer: a transaction carries an explicitly designated payer alongside its gas limit, tip, and a signed cap on the fee reserve. Gas consumed is reported alongside it, and since only public execution meters gas, zero consumption is distinguished from metering nothing. Adds query_version. Every one of the nine surveyed chains reports its node version and LEZ reports none, so an integrator hitting a defect cannot say which build produced it. It changes only across restarts, so a caller can cache it and use it to detect that the surface it holds a schema for has been replaced. Also corrects two cross-references that pointed at the wrong requirement before any of this session's renumbering. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The retention floor was reported only by its own call, so a caller reading a block the indexer had discarded got the same answer as one asking for a block that never existed. Those are different questions: the first is retried against an archival source and the second is not. A read below the floor is now answered as such, without a second call to a floor that may itself have moved in between. The error requirement asked for an application code from a documented code space, which reads as a mandate to design a taxonomy. That is the implementation's decision. What the RFP specifies instead is the property: errors are machine-readable and stable, and two failures a caller would recover from differently never arrive as the same value. A separate requirement names the distinctions a caller acts on, which is the part an implementer cannot infer: malformed request, below the retention floor, indexer stalled, and backend failure, with transient separable from permanent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fryorcraken
commented
Sep 9, 2026
| composable approach. The first two deliverables define surfaces; the rest | ||
| consume them. | ||
|
|
||
| 1. **The FFI API for the LEZ indexer.** *This RFP* |
Collaborator
Author
There was a problem hiding this comment.
Rename to "LEZ node"
fryorcraken
commented
Sep 9, 2026
| ([logos-co/ecosystem#238](https://github.com/logos-co/ecosystem/issues/238)): | ||
| a library per language over the wallet FFI, BDK-shaped rather than a client | ||
| for a wire protocol. | ||
| 5. **The indexer client library** |
Collaborator
Author
There was a problem hiding this comment.
rename to LEZ SDK
The section explained that a height names a position and a hash names a block. The requirements already say which of the two each read takes and returns, so the rationale restated them without settling anything. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rename the deliverables around the LEZ node API and the LEZ wallet API, merge the JSON-RPC proxy module with its client library since the two are halves of one wire contract, and name the development kit LEZ-DK. Five deliverables remain where there were six, and the cross-references follow. Add three diagrams for the shapes an integrator builds: a Basecamp app reaching the lez_core module over one Logos Core FFI, an Android app carrying the LEZ-DK for Kotlin with a transport client, and a desktop app embedding the node itself. Record what BDK settles, since the LEZ-DK is modelled on it. Packaging is one artifact per language, and the wallet and a client stay separate objects that the integrator wires together rather than one calling the other. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The RFP named its subject after the indexer, which is one part of a LEZ node rather than the whole of it. A node also carries sequencer and other capabilities, and which part answers a given call is not something a consumer can observe. Rename the document, its title and its heading accordingly, and update the README table. Drop the claims about what sits inside a node: that it acts as a cache and proxy, that the sequencer is a component behind it, and how the two communicate. None of it is API, and the caching framing understated what a node is. The Out of Scope entry on reaching the sequencer becomes one on reaching inside a node at all. Say that the API carries writes as well as reads, since a signed transaction and its new commitments reach the network through it. The as-built component names stay as they are: lez_indexer_module, libindexer_ffi, and the appendix inventory all still refer to the indexer, which is what those artifacts are called. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A JSON-RPC proxy projects the APIs a module exposes, not the FFI itself, so item 3 no longer describes its own subject as "the Logos Core FFI API". Point simulation at the wallet API rather than the wallet FFI, matching the rename. Carry the black-box pass into the requirements that still read the node as its indexer: the version, retention floor, ingestion state, current state, last finalised block and range bounds all belong to the node. Leave FFI where it names the mechanism this RFP delivers. The requirements say "the FFI exposes" because the deliverable is a set of exported C functions with a module above it, and the as-built component names are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The status requirement prescribed a derivation and warned against a field that is unusable, which is guidance for the team writing the code rather than a contract with an integrator. It also settled for a weaker guarantee than the one an integrator needs: deriving finality from a node's position in its own ingestion says the node has caught up, not that the result has settled. State the meaning instead. Final means the block is final on the underlying L1, so a caller acting on it is acting on a settled result, and each level documents what a caller may conclude from it. Carry an intermediate degree of certainty where LEZ recognises one, as BedrockStatus does with Safe, rather than collapsing it. Report the L1 position the level was assessed against, so a caller can tell how current the answer is. Drop the rationale section arguing against building status on bedrock_status. It argued against no position anyone holds, and an implementation hazard in the team's own code is not what this document is for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The section asserted that private transactions are unreadable and public ones are not, which is too coarse to design against. A privacy-preserving message carries public actions alongside private ones, and a private action carries a nullifier, a commitment, the root it was proven against, and ciphertext. A node can report that the action occurred, prove membership, and serve the ciphertext; it cannot decrypt, and it cannot link a commitment to a nullifier, which the protocol states outright. State the constraint, what it shapes in the API, and what has to improve. The gap is that the boundary is inferred from the shape of what comes back rather than documented: an integrator crediting deposits should learn from the documentation, not from experiment, that a deposit into a private account is invisible to it and a deshield into a public one is not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The privacy paragraph said a node cannot read "which account, which amount, or which program state changed". All three were loose. It is a private account that is concealed; amounts on the public leg are readable, since a public action carries its post-state in the clear; and programs hold bytecode, while accounts hold the state, which is balance, data and nonce. Remove the rhetorical scaffolding through the section and the BDK section that precedes it: "that is not nothing", "it is worth describing", "the one that matters", "even in principle". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… rationale Functionality #12 required the account reads to answer for public state alone and to say so. They cannot do otherwise: query_account takes an AccountId and reads a HashMap<AccountId, Account>, while private state is a commitment set and a nullifier set holding no account identifier at all. There is no path by which the read could consult private state and no way for a caller to ask it to, so the requirement stated that a function reading the public map reads the public map. #11 already carries the part an integrator needs, that the read answers what an account holds rather than whether it exists. Renumber the Functionality list and the references into it. Point each Design Rationale section at the requirements it justifies, so a reader can get from an argument to the text that carries it: gas to #5 and #6, retention to #56 and #57, the privacy boundary to #2, #4, #16, #17 and #42, and simulation to Out of Scope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three of the five requirements it cites already hold: #2 is Ready and #4 is Computed, not persisted, so describing them as a gap misreported the state of the work. Fold the paragraph into the one above it, which was already saying what the constraint shapes, and separate the two halves by what they need: the consumer-facing reads exist and the work on them is to document the boundary, while the wallet's proofs and forward ordering are new. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The wallet reaches the sequencer's getProofsAndRoot directly today (lez/wallet/src/lib.rs:660-667), so the membership proofs #16 and #17 require are not a capability that does not exist. What is new is serving them from the node, which is the point the surrounding rationale makes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI runs mdformat --check, which the file failed. Most of the diff is rewrapping to the 80 column setting. The Supportability list gains zero padding because it runs past nine items, which renders the same and is cited by nobody. One change was a real defect: renumbering the Functionality list left a tab in front of the Transaction effects heading, so mdformat read it as a code block. The mermaid blocks are byte identical, and all three diagrams still render. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Android diagram read as though Android implied the LEZ-DK, when the division is what an application is built from rather than what it runs on. Name the existing case for what it is, an application already shipping that is adding LEZ support, and add a fourth diagram for a Basecamp app on Android, which reaches lez_core over the Logos Core FFI and uses no kit at all. Say what the LEZ-DK is for while there: integrating LEZ into applications that already exist, and reaching the users those applications already have. A developer starting something new is encouraged to build on Logos Core and Basecamp instead, whether or not LEZ is the whole of what they are building. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Carry the diagram titles to a single convention: capitalise only proper nouns, so a pre-existing Android wallet and a pre-existing desktop wallet app read as descriptions rather than product names. Name the two Basecamp cases in parallel, as new apps that use Basecamp and Logos Core, which is the division the four diagrams turn on. Say in the overview that Basecamp and Logos Core remain the first recommended way to build on LEZ on every platform, and that the kit exists so an existing application can add LEZ support without re-architecting. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Basecamp case does not vary by platform, so a second diagram of it on Android showed the same shape twice. The remaining Basecamp diagram already says it holds on any platform it runs on. Drop desktop from the embedded-node case for the same reason: an application that links the node rather than reaching one over a transport is making a deployment choice, not a platform one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Build both libraries and report what they weigh, since the architecture offers a choice between an embedded node and a remote one without saying what embedding costs. At --release for x86_64-unknown-linux-gnu, the LEZ node FFI is 28.0 MiB stripped and the Logos Blockchain FFI is 84.5 MiB, so an application embedding both starts near 112 MiB for one protocol pair. The LEZ figure excludes proving: the risc0 prove feature is enabled by the wallet FFI and a benchmark tool alone. Neither figure is a mobile one, and neither can be yet, because the ZK circuit artifacts both libraries depend on are published for Linux, macOS and Windows only. Record that, and record a candidate strategy for it: let the published artefacts differ by platform, carrying the node on desktop and server and omitting it on mobile, since a developer consuming a prebuilt library cannot set a build feature. Draw the Logos Blockchain node the LEZ node reads finalised state from, which no diagram showed. It appears as blockchain_module beside lez_core in the Basecamp app, and inside the remote host for the Android integration. That host is named logosctl, per logos-co/logos-docs, rather than the headless Logos Core it was called before. Link the Logos Blockchain tracking issues where the text said TODO, and fix an anchor that pointed at a placeholder. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The event-system exclusion promised "the two reasons given in the Design Rationale", but that rationale was removed earlier and the bullet states both reasons itself, so drop the pointer. It also said "the requirements below" from inside Out of Scope, which is after them. Rename the identifiers left behind by today's edits: the third diagram kept desktop and desktopApp after desktop was dropped from its framing, and lbmod named a subgraph in one diagram and a leaf node in another. The comparison with bdk-android undercut itself, calling the LEZ node "close to twice a complete Bitcoin kit" and then explaining two sentences later that the figure excludes the wallet and proving. Say instead that the node alone already outweighs a complete kit, and name the architecture the 15.3 MiB is for. Say the embedded-node diagram omits the L1 node "unlike the two above", since the other two now show it. Narrow a config citation that straddled two structs. Settle on artefact over artifact, the spelling the heading anchor depends on. Carry the renames into the README's placeholder rows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two things bear on the architecture and neither is decided. Mobile support is not planned for testnet 0.3, so the Basecamp shape the first diagram describes is reachable on desktop only for now. And whether a mobile device runs light chain nodes on the peer-to-peer networks, reaches remote nodes over RPC, or combines the two is unsettled, which leaves the sizes and the per-platform artefact strategy provisional until it is chosen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bitcoind still carries a wallet API in the node and geth once did. The ecosystem has moved away from it, which the wallet libraries appendix already documents, so the section points there rather than restating the history. Worth recording anyway: because lez_core carries the wallet beside the node, the JSON-RPC proxy module of deliverable 3 is the only further component needed to offer that shape, which would reach server and cloud environments alongside the Logos Core path. Whether to offer it is a product decision, and this RFP defines only the node half. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft. RFP-027 needs review before this is ready.
Adds RFP-027 (LEZ Node API) and the three appendices supporting it.
Numbered 027 rather than 021: three older open pull requests already claim
021 (#158,
#169,
#57), so this takes the first free
number past every current allocation.
RFP-027: LEZ Node API
Defines the LEZ node API, scoped to
logos-co/ecosystem#235.
25 functions across ten groups, each with a signature, its semantics, and
requirements: 64 in Functionality plus Usability, Reliability, Performance,
Supportability and Privacy. Functions that already work are specified too,
rather than assumed, so a proposer can build and a reviewer can accept against
the document alone.
Each group opens with the rough equivalent on Ethereum, Bitcoin, Solana, and
Zcash.
Each Functionality requirement carries a readiness marker, classified against
LEZ source:
The markers describe effort, not priority. The intent is that a proposer can
see which requirements are exposure work and which are genuinely new.
One of five deliverables, each with its own issue: the LEZ wallet API
(#236), the JSON-RPC proxy
module and its client library
(#237), the LEZ Development
Kit (#238), and further
transport modules with their clients
(#222). The separate
indexer client library issue was folded into #237, since a wire format and the
client that parses it are two halves of one contract.
Target architecture
Three diagrams show what the deliverables build towards. They differ in what
the application is built on, and then in where the node runs: a new app on
Basecamp is a Logos UI module paired with a Logos Core module reaching
lez_coreover the Logos Core FFI, while an application not built from Logosmodules uses the LEZ-DK for its language and either reaches a remote node or
embeds one.
Basecamp and Logos Core stay the recommended way to build on LEZ. The LEZ-DK
exists so an existing application can add LEZ support without re-architecting,
which reaches distribution channels LEZ otherwise would not.
The positions worth reviewing
The LEZ node API is the only API for LEZ chain state access. A LEZ node is
a black box: what it is made of, which part answers a given call, and how those
parts talk to each other are implementation rather than API. It carries writes
as well as reads, since a signed transaction and the new commitments a
privacy-preserving one produces reach the network through it. Consequences: the
wallet's private account sync and its commitment membership proofs are required
of the node, transaction submission sits on this surface because a wallet that
has signed a transaction otherwise has nowhere sanctioned to send it, and the
pending set is exposed, which is what lets a caller tell a declined transaction
from one still in flight.
A transaction status states what it means, not how it is computed. Final
means the transaction's block is final on the underlying L1, so a caller acting
on it is acting on a settled result. It is deliberately not a statement about
how much of the chain a node has processed, which would report a node catching
up rather than a result settling.
Simulation is not here. Six of the nine surveyed chains execute an
unsubmitted transaction and return its outcome. On LEZ a privacy-preserving
transaction is executed by the wallet, which runs the program locally over
notes only it can decrypt and submits a proof that the execution was correct,
so the proof is an input rather than a result and there is nothing for the node
to simulate first. Siting public simulation on the node alone would split one
capability across two components and answer against finalised state rather than
the state the caller is building on. It belongs to the wallet API.
What a node can answer about a privacy-preserving transaction. Such a
transaction is not opaque in full: its public actions carry account identifiers
and post-states in the clear, and each private action carries a nullifier, a
commitment, the root it was proven against, and ciphertext. A node can report
that a private action occurred, prove a commitment's membership, and serve the
ciphertext; it cannot read the private account touched or the state it now
carries, and it cannot link a commitment to a nullifier. The requirements make
each read say which side of that boundary it answers for.
Errors specify distinctions, not codes. The RFP does not enumerate an error
taxonomy. It requires that errors be machine-readable and stable, that two
failures a caller would recover from differently never arrive as the same
value, and it names the distinctions a caller acts on. Which values exist is
the implementation's decision.
Artefact size, measured
The architecture offers a choice between an embedded node and a remote one, so
both libraries were built to see what embedding costs. At
--releaseforx86_64-unknown-linux-gnu, the LEZ node FFI is 33.6 MiB unstripped and 28.0MiB stripped; the Logos Blockchain FFI, which a LEZ node reads finalised state
from, is 84.5 MiB. An application embedding both starts near 112 MiB for one
protocol pair, against
bdk-android3.0.0 shipping 15.3 MiB for a wallet andfour chain backends. The LEZ figure excludes proving: the risc0
provefeatureis enabled by the wallet FFI and a benchmark tool alone.
Neither figure is a mobile one, and neither can be yet, because the ZK circuit
artifacts both libraries depend on are published for Linux, macOS and Windows
only. The section records that, and a candidate strategy for it: let the
published artefacts differ by platform, carrying the node on desktop and server
and omitting it on mobile, since a developer consuming a prebuilt library
cannot set a build feature.
Appendices
blockchain-api-sdk-ecosystem.md— 34 API functions across nine chains,with transports, SDK languages, response shapes, documented origin history,
and per-function gap notes for Logos L1 and LEZ.
logos-api-surfaces.md— as-built inventory of the LEZ indexer FFI andRPC, the sequencer RPC, the wallet FFI and
lez_coremodule, and the L1bindings, routes, and module.
wallet-libraries-ecosystem.md— where wallet functionality lives perchain, key derivation standards, watch-only support, and hardware and
offline signing. Input for the wallet API RFP rather than this one.
Zcash was added as a ninth surveyed chain in this branch. The RFP cited it in
six places while the appendix surveyed only eight, so those claims were
unsourced; sourcing them corrected three, including that Zcash carries no
testmempoolacceptand so has neither simulation nor an acceptance check.Sourcing
Every appendix claim carries a first-party URL that was fetched, and every
quotation was checked against the source text. Logos claims cite
path:linein the repositories at pinned commits.
The surface was also checked against its real consumers: the LEZ wallet UI and
the
lez_coremodule. Every sequencer method those depend on has an equivalentrequired here,
getProofsAndRootincluded.Note that the as-built component names are unchanged throughout:
lez_indexer_module,libindexer_ffiand the appendix's inventory still referto the indexer, which is what those artefacts are called. The rename applies to
the API this RFP defines, not to the code that implements it.
Known gap
The L1 chain identifier
query_network_identityreturns is inscribed in theLogos Blockchain genesis block and read at ledger initialisation, but no L1
route serves it and the node's Bedrock configuration carries only an endpoint.
Reaching it is work outside this RFP, recorded under Platform Dependencies, and
the surface stays deliverable meanwhile by reporting the value as unavailable
rather than guessing.
🤖 Generated with Claude Code