Zerochord

Guarantees and security

What is guaranteed and by what, how each route fails, and the cryptographic and key handling details behind it.

This page states what holds, what enforces it, and how each shape fails.

Custody

The user's wallet holds the keys and signs. Zerochord never takes custody, and no component holds a user key.

The wallet that signs is the wallet that pays the network fee. No fee sponsoring code path exists, and cardano-swap integrity proves that by scanning the source rather than asserting it. See Footprint and measurement.

One signature per commitment

A user approves exactly one transaction to commit a swap.

For a Kernel route, that transaction settles the swap. For a Commitment route, it creates the order carrying the binding minimum. A later cancel is its own transaction and its own signature, because it is a separate decision.

The minimum output is enforced on chain

The number shown before signing is the number written into the transaction.

The builders take the Quote object itself, so there is no second copy of minimumOut that could drift from the one displayed. The command line's --min-out can only tighten the bound, and the builder refuses anything looser than the quote.

The deduction that derives the minimum from the quoted output uses a ceiling, so the on-chain bound is never looser than the slippage allowance implies.

What enforces the minimum, per shape

RouteEnforced byFailure mode
Kernel, one or many poolsThe venue's own deployed validator, checking the pool outputs the transaction paysThe transaction fails validation. Nothing settles and nothing fills worse.
Commitment, one venueThe minimum_receive in the order datum, checked by the venue's order validatorThe batcher cannot fill without breaking the bound, so the order rests. Cancel it to recover the funds.
MixedThe settlement validator's minimum_output, checked across the whole claim transactionClaim only succeeds at or above the aggregate. After expiry the refund path returns everything the settlement UTxO holds.

The Kernel guarantee was proved on preprod rather than assumed: an under-payment of one base unit is rejected by the deployed validator.

The settlement validator's guarantee, stated exactly: never worse than the quoted minimum, or recover the pieces. If one leg fills and another does not, the refund returns a mix of input and output assets rather than the original input, because the filled leg cannot be reversed. That is why the router prefers a single class route whenever its net output is within routing.singleClassPreferenceBps of a mixed one.

Timing is never promised where it is not owned

A Kernel route settles in the transaction the user signs.

A Commitment route is filled by a permissioned batcher. The rate is bound by the datum and the timing is guaranteed by nobody. Every screen that shows such a route says so, from one source of wording keyed on the route's atomicity, and every one of them offers a cancel path.

Orders are written with an expiry, so an order cannot rest forever. After it passes, anyone may cancel the order and keep up to the configured tip, with the rest returning to the owner.

The validity window

The ledger refuses a transaction whose upper validity bound has passed, whatever the validator would have said.

The deployed Dano Finance validator caps the interval at roughly 360 slots, so a signed swap stops being submittable within a few minutes. The web application checks the window before sending, raises ValidityWindowClosedError naming the cause, sends nothing, and refetches the quote. It does not let the node answer with OutsideValidityIntervalUTxO and leave the user guessing.

The double satisfaction guard

Both settlement redeemers measure value by reading the transaction outputs that pay the owner. Two settlement UTxOs spent together would each read the same outputs and each conclude, correctly on its own terms, that it was satisfied.

The validator restricts a transaction to exactly one input carrying its own payment credential. With a single claimant there is no shared measurement left to double count. See The settlement validator.

Cryptographic primitives

UsePrimitive
Asset fingerprints (CIP-14)blake2b with a 20 byte digest over policy id concatenated with asset name, bech32 encoded with the asset prefix
Pyth signed price updatesEd25519 over the payload bytes, verified against the trusted signers read from the on-chain Pyth state
ValidatorsPlutus V3, compiled by Aiken v1.1.23
Datums and redeemersPlutus data, encoded and decoded by this project's own codec against the blueprint in onchain/plutus.json

Fingerprints are recomputed on every configuration load and compared against the recorded value, so an altered policy id throws at load rather than producing a wrong quote later.

Pyth's trusted keys are read from the chain rather than configured or hard coded, because Pyth rotates them and the on-chain state is what the validator itself checks against. The off-chain and on-chain checks therefore agree by construction.

Key handling

The operator wallet is a command line concern only. It signs deployments, measurement runs and operator swaps. It never signs for a user, and no user path touches it.

  • It lives in secrets/<network>.operator.json, under the directory named by CARDANO_SWAP_SECRETS_DIR or secrets/ at the repository root.
  • It is written with mode 600 and the mode is set again after writing.
  • secrets/ is excluded from version control, along with *.skey, *.vkey, *.seed and *.mnemonic.
  • wallet create refuses to overwrite an existing wallet unless --overwrite is passed.
  • loadOperator refuses to load on mainnet unless the caller passes an explicit allow-mainnet flag, because every routine operation runs on a test network and a mainnet deployment must be a deliberate act.

Secrets and the browser bundle

Anything compiled into a browser bundle is public. Two consequences are enforced in code.

The browser build supports Koios only. Blockfrost authenticates with a project id, and a project id in a bundle every visitor downloads is a published secret. Configuring another primary provider for a browser deployment throws with that reason.

The optional Koios bearer token is a rate limit raise, not a credential to protect. To raise the limit without publishing a token, put a proxy in front of the provider and point providers.koios.baseUrl at the proxy.

What the product refuses to build

  • No rewards, rebates, points or referral payouts. Nothing pays anyone to transact.
  • No fee sponsoring. The capability does not exist.
  • No mock price source presented as an oracle, and no fabricated data standing in for a real reading. Where something cannot be obtained, the code throws with the reason. A quote engine that quoted from a guessed price would be worse than one that refused to quote.
  • No silent stale data. A failed quote shows no quote. A failed refetch never leaves previous numbers on screen in front of someone about to sign.

Failure reporting

Every failure names what failed and why.

A provider that cannot be reached is not the same as a pair with no route, and the interface names each differently, so an outage never reads as a fact about the market.

Where a figure has no meaning, measure prints not computable and the reason rather than a zero. Where a baseline cannot be recovered, audit prints baseline not reconstructible rather than substituting present day pool state for a past one.

On this page