Design records
crypto-aio 0.1.0 was designed in a written specification and built in a series of planned stages, each reviewed against the code. What those reviews established now lives next to what it explains:
- In the code. A comment states each rule's reason where the rule is enforced: the attack it stops, the node behavior it works around, the trade-off it makes. Tests say in their names what they pin.
- In these pages. The guides and reference describe the behavior you can rely on, and Known limitations lists what the library does not do yet.
The original documents, with the full history of each decision, are in the repository's history: docs/superpowers/ at commit 694cf12. Where they and these pages disagree, these pages, and the code, are right.
Where the specification's topics are now
The specification planned a set of guides; their topics live in these pages:
| Specification topic | Where it is |
|---|---|
| Architecture | The big picture, Source map |
| Configuration | Configuration, Core concepts |
| Transactions | Send a transfer, The life of a transfer |
| Exchange operations | Receive deposits, Run workers and recover, Go to production |
| Stores | Write a durable store |
| Writing adapters | Write a chain family plugin |
| Security | Keys, signers and secrets, Keys, signers and policy |
Open work inside the library
The gaps a user can meet are in Known limitations. These are the ones a contributor meets: none changes what a caller sees today, and each says why it waited.
Broadcasting and verdicts
- Decided against for 1.0: a first-broadcast
rejectedneeding a second endpoint's agreement in the core. Every family already keepsrejectedonly when the claimed reason holds for its own bytes, so it would change theBroadcasterport for little gain; the same holds for a must-conflict set in the core, and for the broadcast fanout taking the first HTTP 200 answer (a lying 200 refusal is only a transientTX_REFUSED). - The resend guard could move to the
Broadcaster, with the Attempt's ordering passed as an optional broadcast field, so every family whose ordering can repeat guards its resends. TON guards in its broadcaster today. An optional field is a minor change, so it can come in a 1.x release. recover()skips its checks when the write target cannot be resolved; rebroadcast still requires the wallet's ownership check. The first inclusion is recorded from one endpoint's observation (the verdict stays under the proof quorum). Wallet resolution for the all-rejected verdict is bounded by neither the caller's signal nor a per-pass cache.- Reconciliation scans from the chain's pending count, and a refusal can hide behind
droppedafter an ambiguous resend.
EVM
- The nonce search behind a verdict repeats on every pass; a cache per sender and nonce would save the reads.
- An endpoint-set change between "slot consumed" and "included final" decides nothing.
- The proof keys were never audited for raw-text differences between honest endpoints (the other families were): a difference stalls, never decides.
crypto-aio/evmcould split its typings per library (ethers, web3).
Bitcoin
- On replace or cancel the own-transaction record is not cleared; a double-spent parent is not proven dead with its child; authenticated parents could be embedded with
updateInput, and a p2pkh finalize could skip a quadratic decode.
Tron, Solana and TON
- Tron reads
getForbidTransferToContractfrom one endpoint, not under the proof quorum; the scripted test node's genesis timestamp is not Nile's. - Solana caches a mint that every endpoint calls absent for the container's life, and could skip a slot read while the finalized slot is still below the blockhash's.
- TON's read path could retry asset failures (keep the junk-jetton tests); a bounce could be authenticated from the chain itself.
Transport
- A trial request to a recovering proof endpoint has no bounded wait for a rate-limit token, so a burst of one can keep it out. A half-open endpoint can admit a second request.
- Rate limits could be per method, proof reads could wait out a short
429, and one bucket per host could cover every handle's transports; this needs measurements on live endpoints.
Structure and tests
- The families' ordering fields stay in each family's exported type:
OrderingDatadocuments that a store keeps them whole, and the store contract pins them (an index signature in the core type would break every family type declared as an interface).engine.tsand TON'sproofs.ts,reader.tsandapi.tsare long, as are the TON and UTXO end-to-end tests. - The only-native guard is text-based.
- No test watches
require.cacheacross families; the packaging check proves today that every entry point loads with no SDK. - The EVM write path runs against a real node (
pnpm test:evm-local, anvil). The other families' write paths run only against their scripted nodes:CRYPTO_AIO_INTEGRATION_WRITE=1enables no live test yet, since funded testnet keys in CI, or local nodes (bitcoind and electrs, a Solana test validator, a TON local network), are still to be set up. Determinism in tests could improve with a container option for the transport id and the random source, and one shared end-to-end environment.
Store ports (found by the PostgreSQL reference store)
- Observations carry no namespace:
getObservationandputObservationkey them by attempt id alone, which is globally unique, so a store cannot partition them by tenant without joining Attempts. Adding a namespace changes the port, so it waits for a major release. parseTaggedreads any plain object whose only key is$bigintor$bytesas a bigint or bytes; there is no escape, so stored data never uses such an object.- Which record
findByRefreturns when several Operations of a namespace hold the ref is unspecified; the core's AttemptRef guard keeps that from happening.