Zerochord
How it works

Reading chain state

The provider interface, how pool discovery proves it read one chain state, and how spent outputs are handled.

Every read that feeds a quote also reports the tip it was read at, because the saving claim compares routes at one chain state. A quote built from two blocks is not comparable, and the engine refuses one.

The provider interface

ChainProvider in @cardano-swap/core is what the router needs from the chain. Two implementations ship, KoiosProvider and BlockfrostProvider, so the system never depends on one vendor. Provider choice is configuration, never a code branch on the network name.

Capabilities are declared rather than discovered at runtime:

interface ProviderCapabilities {
  utxosByPaymentCredential: boolean;
  transactionsByPaymentCredential: boolean;
  utxosByAsset: boolean;
  datumByHash: boolean;
  submit: boolean;
}

The first one decides whether a deployment can discover pools at all. A venue's pools sit at many addresses that share one payment credential and differ in their staking part, so discovery is by credential. Providers that cannot answer that query say so through this field.

createProviders(network) builds the primary named in the configuration, and the fallback when one is configured and its credentials are present. A fallback that cannot be built is not an error, and it is never a silent substitute for the primary: the primary still throws when it fails.

Spent outputs are dropped by default

Only a query by out ref can return a spent output at all. The queries by address, by credential and by asset read the live UTxO set.

getUtxosByOutRefs therefore drops spent outputs unless the caller passes { includeSpent: true }, and every Utxo carries a spent flag.

The default is the safe one because forgetting to check is how the defect happened. A swap naming a pool UTxO that an earlier swap had already consumed built and passed local evaluation with no error. The failure was still hard, so nothing filled at a worse rate, but it arrived at submission instead of at build time, which is after the user signs.

Koios answers out refs from /utxo_info, which carries is_spent directly. Blockfrost publishes no endpoint answering "is this out ref spent", so it establishes the answer by asking whether the output is still in its own address's live set, one read per distinct address, and only when the caller did not ask for history.

Proving one chain state

Each adapter's discovery reads the UTxOs, reads the tip once, and stamps that tip on every pool. So checking that all pools carry one block hash asserts something the adapter makes impossible to violate. It reads like an invariant and cannot fail.

What is actually worth proving is the claim behind the stamp: these pools were all live at one chain state.

The proof that works. A transaction output that is unspent at tip T existed at T. So a single batched read that returns every pool output as still live proves they all coexisted at the moment of that read, and therefore at the earlier stamped tip too. That holds however long the original discovery took.

getUtxosByOutRefs returns an OutRefRead that carries the evidence:

interface OutRefRead {
  utxos: Utxo[];
  /** True only when every out ref was answered by exactly one provider query. */
  singleSnapshot: boolean;
  /** How many underlying queries the read cost. */
  queries: number;
}

queries is present so the claim is auditable rather than asserted. A test pushes a request past the batch limit and watches it rise.

An empty request is trivially one snapshot and reports singleSnapshot: true. That is vacuous. A caller proving coexistence must check the set is non empty first.

One call is not one query

Koios answers 50 out refs per /utxo_info request. A request for 66 pools is split in two, and the coexistence proof does not hold across the split.

That is why singleSnapshot exists per call, and why the project's own coexistence checks are taken over sets small enough to be answered by one query. The full Dano Finance pool set is deliberately used in a test to show the flag going false, so the flag is known to be load bearing rather than always true.

Three measured traps

These are recorded because each cost real debugging time.

The chain tip is not monotonic through a load balanced provider. Two consecutive getTip calls were observed going backwards by 113 slots, because the provider balances across nodes sitting at different heights. Bracketing tips are therefore reported as a window and never asserted to advance.

Requiring the tip to hold still across a whole read does not work. Under full test suite load the reads outlast a 20 second block, so that approach retries and then fails. Coexistence is proven by unspentness instead, which does not care how long anything took.

A guard whose failure path has never been observed is not yet a guard. Before spent outputs were dropped, the coexistence check was vacuous: spent outputs came back as though they were live, so the stale set was always empty and the check passed unconditionally. The fixtures that make the failure path observable are kept in the test suite for exactly that reason.

Paging and rate limits

Koios pages at 1000 rows. The provider walks pages until a short page arrives, and refuses to keep reading past a bounded page count rather than returning a truncated list that looks complete.

Rate limit responses are retried with backoff, bounded by a maximum wait.

The browser build

The browser builds Koios directly and refuses any other primary:

this network is configured to use the name provider, and the browser build supports koios only, because the other providers authenticate with a secret that cannot live in a bundle

Blockfrost authenticates with a project id, and a project id compiled into a bundle every visitor downloads is a published secret. Koios needs no credential for the read endpoints the app uses. The optional bearer token only raises the rate limit, and anything given to a browser is public. To raise the limit without publishing the token, put a proxy in front of the provider and point the provider base URL at it.

Pool read caching in the app

The browser reuses a pool read for 15 seconds. A read is several hundred pool UTxOs over paged endpoints, and a person editing an amount changes it several times in a few seconds.

Every quote carries the tip it was computed at and the panel shows it, so the state behind a quote is never hidden.

On this page