Holder set withheldNode status
REDACTED-002/Revised October 06, 2026

Integration

Intents

An intent is a signed statement of what you want, not a transaction. It carries a side, a size, a slippage bound, and an expiry, and it is signed by a key that must never have interacted with the pool. The coordinator will reject an intent signed by a key it can already see on-chain, because routing a trade for an address that is already linked to the token defeats the purpose and wastes a batch slot.

{
  "side":        "buy",
  "size":        "250000",       // base units
  "max_slip":    "0.015",
  "depth":       3,             // 1-6, or null to let the server pick
  "settle_to":   "<fresh address, this batch only>",
  "expires_at":  1791331200,
  "nonce":       "<32 random bytes, hex>"
}

expires_at is load-bearing. An intent that sits in the queue across many windows is an intent that can be timed, so the coordinator drops anything older than four batches regardless of what the expiry says.

NPC RPC

The coordinator speaks JSON-RPC 2.0 over HTTPS. There are four methods and no authentication — an intent is self-authenticating by signature, and requiring an API key would reintroduce the identity we just spent the whole design removing.

submit_intent(intent, sig)       → { batch_id, slot, window_closes_at }
batch_status(batch_id)         → { state, members, settled_height }
quote_route(side, size, depth)  → { fee_bps, est_latency_ms, cohort }
cohort_health()               → { live_wallets, threshold, node_height }

There is deliberately no lookup_owner and no list_holders. Those endpoints are not restricted or gated; they do not exist, and the coordinator does not retain the data that would let us implement them.

batch_status returns members as a count, never as a list. The count is useful — it is the anonymity set you actually got, as opposed to the one you were quoted — and the list would hand an observer the correlation for free.

Custody

Each relay leg is signed 7-of-12 by the cohort. Twelve wallets hold key shares, seven are required to move anything, and the shares are regenerated on rotation rather than reassigned — so an operator who leaves does not keep a share that still works.

  • No single operator can execute a leg, which also means no single operator can be compelled to.
  • The cohort can execute your order and cannot redirect it. The settlement address is bound into the signed intent, so changing it invalidates the signature the cohort is executing against.
  • Seven colluding operators can steal. This is a real bound, not a theoretical one, and it is why the cohort is spread across operators who have no reason to know each other.

Settlement

Settlement lands at the address in settle_to, derived for that batch and used once. Your client should treat a settlement address as spent the moment it receives, and derive a new one for the next intent.

Consolidating several settlement addresses into one wallet in a single transaction undoes the routing completely: it publishes, in one place, the claim that the same party owns all of them. If you need to consolidate, do it across separate batches and separate blocks, or do not consolidate at all.

Client hygiene

The routing protects the trade. It does not protect everything around the trade, and in practice the surrounding behaviour is what deanonymises people.

  1. Submit over Tor or a connection you did not also use to sign up for an exchange. The coordinator does not log addresses, but it necessarily sees them.
  2. Fund the signing key from something that is not a KYC withdrawal. Routing moves the terminus of the funding graph one hop out; it does not remove it.
  3. Prefer round sizes. An order for 413,772 is a fingerprint that survives any number of hops.
  4. Submit into busy windows if you can. The anonymity set is whoever else happened to be in your batch, and at four in the morning that may be nobody.
  5. Run your own monerod 0.18.5.3 if you are integrating at any size. The reasoning is on the node set page.