# ODW — Onchain Dealing Wire

Version 0.1 · 2026-07-21 · csclear
Status: implemented; live on the development network.

Bilateral conversational dealing for human and AI dealers, with
straight-through settlement into party-deployed on-chain contracts.

Naming: formerly CDP (Conversational Dealing Protocol). Wire-level
identifiers — `cdp_*` verb and table names, the `cdp:` record room
prefix, seal HKDF labels — are frozen for compatibility and keep the
legacy prefix.

## 0. Doctrine position

The platform is a **wire + notary + template publisher**:

- Relays signed messages between exactly two parties per conversation.
- Orders nothing, matches nothing, holds nothing, executes nothing.
- Firm quotes exist **only inside bilateral conversations**. The broadcast
  layer (axes) is indicative-only and cannot be dealt on directly.
- Agreement is formed **between the parties** by their own signed messages;
  the platform's role ends at recording the double-signed confirm.
- Settlement is by the parties: bilateral off-platform, or via autonomous
  contracts (e.g. CollarDeal) that the parties deploy and fund themselves.

### 0.1 Common-carrier rules

The platform operates as a communications carrier, not a financial
intermediary. Seven rules define the line:

1. **Identify the subscriber, never vet the call.** Registration (dealing
   code, line attestation) identifies the operator behind a key — like
   subscriber registration for a phone line. The wire then carries any
   protocol-valid frame; it validates signatures and the state machine,
   never price, size, or commercial merit. Merit limits live in
   client-side mandates.
2. **Neutral carriage.** No refusing conversations by content, no reading
   traffic for advantage, and the operator never trades against or
   provides liquidity to its own network.
3. **Charge for carriage, not outcomes.** Line rental, message and data
   subscription fees. Never a share of deal value.
4. **Keep call records.** Transcripts are party-signed and retained;
   subscribers export them for their own compliance (CDD, record-keeping,
   audit). The platform's records serve the parties' obligations, not a
   platform obligation to police trades.
5. **Suspend lines, don't police speech.** Sanctions or registration
   breach ⇒ code revoked and the revocation published. In-conversation
   control (`HALT`, `END`) belongs to the subscriber.
6. **Sell handsets, not hands.** Clients, MCP servers and terminals are
   customer-premises equipment. Keys are generated and held customer-side;
   the platform can never sign for a subscriber.
7. **Publish standard forms, own no switches.** Settlement contract
   templates are published immutable and deployed by the parties. The
   platform holds no admin keys and sits on no settlement rail.

### 0.2 Perimeter discipline

Messaging networks stay carriers; matching engines become venues. Every
feature ships only if it passes three tests:

1. **No in-system interaction of interests.** Trading interests never
   meet inside the platform. Broadcast surfaces (axes, §10) carry no
   accept, hit, or response affordance — interest is expressed only by
   opening a bilateral conversation. The wire holds no cross-conversation
   state and offers no fan-out or RFQ primitive; any one-to-many behavior
   belongs to subscriber agents under their operators' mandates, outside
   the platform.
2. **No deal-contingent economics.** Fees are per-line and per-message,
   published, uniform. Never per-deal, never a percentage of value.
3. **Uniform published terms.** Eligibility criteria and tariffs are
   published; refusal or suspension only on published objective grounds
   (sanctions, key compromise, protocol abuse) — and every suspension is
   itself published. Per-call discretion is venue behavior and is
   structurally absent.

Corollaries. Confirms **record** an agreement the parties formed between
themselves — the platform's timestamp and chain are evidentiary, never
constitutive. The registry identifies **capacity** (who operates a key,
what an agent is mandated to do), never suitability or commercial merit.
Settlement templates are pure two-party mechanics; a template that
matched, pooled, or ranked interests would be a switch, and the platform
owns no switches (rule 7).

Published operating limits (rule 3 requires these be stated, not
discovered): per-key message rate 30 per 10 s per conversation; envelope
body ≤ 4000 bytes; clock skew ≤ 300 s; directory and mandate feeds serve
up to 500 rows per page; standard anti-abuse filtering applies at the
network edge. The wire relays even when the record layer is down —
record-layer lag is published per conversation as `unshipped` on
`/status` and in the sync frame, so subscribers can see when the
evidentiary record is behind the relay.

## 1. Scope and non-goals

In scope: identity, mandates, envelope format, conversation state machine,
quote semantics, confirmation, on-chain binding, transport topology, agent
integration, indicative broadcast.

The network carries dealing only in what can settle itself: onchain assets
and oracle-priced cash-settled contracts. Off-chain assets enter scope only
by becoming tokens — the network never reaches off-chain. Settlement
templates require max loss computable at inception (prefunded escrow, no
margin calls); anything else deals free-form with settlement entirely the
parties' arrangement.

Non-goals: order books, resting multilateral firm quotes, auto-execution,
netting, multilateral netting or central counterparty of any kind, custody,
credit intermediation.

## 2. Identity, mandates, EVM binding

**Identity** = Ed25519 public key (hex). Handles (e.g. `agentA`) are
directory aliases; the key is authoritative.

**Mandate** — an operator key authorizes an agent key with limits. Carried
in `CALL`/`PICKUP`, verifiable offline by the counterparty before quoting:

```json
{"v":"mandate1","operator_pk":"…","agent_pk":"…",
 "instruments":["XAU-USD","*"],"max_notional_usd":100000,
 "valid_until":1760000000000,"nonce":"…","sig":"ed25519 by operator_pk"}
```

Signature covers canonical JSON of the object minus `sig`. An agent without
a mandate is self-authorized (operator_pk == agent_pk).

**EVM link** — binds a dealing key to a settlement address, dual-signed:

```json
{"v":"evmlink1","ed_pk":"…","evm_addr":"0x…",
 "sig_ed":"ed25519 over canonical minus sigs",
 "sig_evm":"eth personal_sign over same bytes"}
```

Published to the directory; lets a confirm map deterministically to the
wallet that settles.

## 3. Envelope

All messages share one envelope. Canonical form for signing is
`json.dumps(obj, sort_keys=True, separators=(",", ":"))` (same convention
as rfq_client.py); signature is Ed25519 over the canonical bytes of the
envelope minus `sig`.

**On the wire the envelope is carried as `{pk, sig, body}` where `body` is
the canonical-JSON STRING** — not a nested object. The relay verifies the
signature against those exact bytes and never re-canonicalises, so the
string you signed is the string you must send.

```json
{"v":1,"conv":"16-hex-chars (8 bytes)","n":3,"prev":"chain-hash-last-seen-or-null",
 "ts":1760000000000,"from":"ed25519-pubkey-hex",
 "type":"QUOTE","body":{…},"sig":"…"}
```

- `n` — sender's own monotonic counter within the conversation. The relay
  **enforces** strictly increasing `n` per sender: a frame with `n` at or
  below the sender's last recorded value is rejected (`stale n`), making
  verbatim replay of a sender's own signed frames impossible. `n` advances
  only on accepted (recorded) frames, so a reconnecting client recovers its
  counter by replaying the transcript and taking the max `n` of its own
  frames.
- `seq` — assigned by the relay on acceptance; global total order per
  conversation. **Relay order is authoritative for races** (§5).
- `chain` — relay-computed running hash:
  `chain_i = sha256(chain_{i-1} || sig_i)`, `chain_0 = sha256("cdp:" || conv)`.
  Relay echoes `seq` and `chain` to both parties with each delivery.
- `prev` — the last `chain` value the sender saw (may lag; informational).
- Tamper evidence: each party's messages carry their own signature; the
  final `CONFIRM` signs the transcript hash (§6), freezing the whole
  conversation both parties acted on.

Timestamps are ms epoch. Clock skew is tolerated; validity windows are
evaluated against the **relay's** receive time.

## 4. Verbs and state machine

| Verb | Body | Notes |
|---|---|---|
| `CALL` | `{instrument, side?, qty?, note?, mandate?}` | ring a counterparty |
| `PICKUP` | `{mandate?}` | open the conversation |
| `DECLINE` | `{reason?}` | busy / refused |
| `QUOTE` | `{bid?, offer?, qty, firm, valid_until}` | new QUOTE supersedes prior |
| `OFF` | `{}` | withdraw live quote |
| `MINE` | `{quote_seq}` | buy at that quote's offer |
| `YOURS` | `{quote_seq}` | sell at that quote's bid |
| `DONE` | `{terms}` | full terms restatement (§6) |
| `CONFIRM` | `{terms, transcript_hash, evm_sig?}` | countersign — deal record |
| `CHAT` | `{text, kind?}` | free text / files-by-reference |
| `CHECKING` | `{}` | "hold on" indicator |
| `END` | `{reason?}` | close conversation |
| `HALT` | `{scope}` / `{lift:true}` | operator kill-switch / restore (§9) |

State machine (per conversation):

```
IDLE → RINGING (CALL) → OPEN (PICKUP) ⇄ QUOTED (live firm QUOTE)
QUOTED → AGREED (valid MINE/YOURS) → CONFIRMED (DONE + CONFIRM) → CLOSED
any → CLOSED (END / DECLINE / timeout)
```

`CHAT`/`CHECKING` are legal in any open state **after pickup** — the relay
rejects them while `RINGING` (`not picked up`). Multiple sequential deals may
occur in one conversation: after `CONFIRMED`, state returns to `OPEN`.

## 5. Quote semantics

- `firm: true` — dealable by `MINE`/`YOURS` until `valid_until` (ms) or
  `OFF`. Machine-scale windows allowed (sub-second and up); human default 5 s.
- `firm: false` — indicative; `MINE`/`YOURS` against it is invalid.
- **Race rule**: relay `seq` decides. A `MINE` is good iff its assigned
  `seq` precedes any `OFF`/superseding `QUOTE` and relay receive time is
  within `valid_until`. This is the protocol's only ordering dependency and
  why quotes are referenced by `quote_seq`, not by content.
- A valid `MINE`/`YOURS` forms the parties' agreement at the quoted price
  and size ("your risk" convention). The relay marks state `AGREED`; it does
  not itself execute anything.

## 6. Confirmation and terms schema

After `AGREED`, one party sends `DONE {terms}`; the counterparty answers
`CONFIRM {terms, transcript_hash}` with byte-identical `terms`. Both
messages are Ed25519-signed; together they are the deal record (persisted
to the existing `confirm_record` table).

`transcript_hash` = the relay `chain` value at the `AGREED` message — the
confirm freezes the exact negotiation both sides saw.

**Deal identifier**: `tid = sha256(conv || ":" || done_seq || ":" ||
transcript_hash)` (hex). Deterministic — both parties and any auditor
compute the same UTI-style identifier from the record alone; no issuer,
no counter, no carrier involvement. The `CONFIRM` body carries `tid`;
settlement contracts index deals by it.

**Schema tag**: `terms.schema` names the terms layout. The full set of
layouts — field names, types, units, constraints and a worked example
per schema — is published machine-readably by the `instrument_schemas`
MCP tool, generated from the reference implementation so it cannot drift
from what the wire actually carries. Build `terms` to that catalogue;
`freeform1` carries an unstructured deal whose settlement is entirely the
parties' own arrangement. Byte-identical `terms` (the
DONE/CONFIRM match rule) therefore includes the schema tag: parties
cannot confirm the same numbers under different product readings.

**Terms are contract-ready** — the schema is exactly what the settlement
contract needs, so a confirmed deal deploys with zero re-keying:

```json
{"deal_id":"…","conv":"…","instrument":"XAU-USD-COLLAR-DEC26",
 "underlying":"XAU-USD","qty":5,"price":3345.0,
 "strike_low":3200.0,"strike_high":3500.0,"notional_usd":16725.0,
 "expiry":1767139200000,
 "buyer":{"ed_pk":"…","evm":"0x…"},"seller":{"ed_pk":"…","evm":"0x…"},
 "settlement":{"mode":"pyth-dual","contract":"CollarDeal","chain":"polygon",
   "submitter":"buyer","pyth_id":"0x765d…4bb2","transcript_hash":"…"}}
```

**EIP-712 option**: `CONFIRM.evm_sig` carries an EIP-712 signature over the
terms struct by each party's linked EVM key. A settlement contract may
require both EIP-712 signatures to activate — fusing the dealing record and
settlement authorization. Optional because it adds wallet friction;
Ed25519-only confirms remain valid deal records.

## 7. On-chain binding

- The deploying party passes `transcript_hash` (bytes32) to the contract →
  on-chain pointer to the off-chain signed record.
- **V2 (ticket-fused)**: `CollarDealV2.open(terms, sigBuyer, sigSeller)`
  requires both parties' EIP-712 signatures (by their `evmlink1`-bound EVM
  keys) over the full ticket struct — `tid`, `transcript_hash`, and every
  economic field. The contract recovers both signers and reverts unless
  they equal `terms.buyer`/`terms.seller`; `tid` is replay-guarded
  (`dealByTid`) and indexed in the `Opened` event. The ticket IS the
  authorization: anyone may relay a fully-signed open, and a deal cannot
  open with terms differing by one bit from what both wallets signed.
- The funding/deploy tx hash is written back into the confirm record →
  off-chain pointer to the on-chain state. Two-way binding.
- **Submitter designation and lifecycle reporting.** Parties may
  designate who submits in the confirmed terms —
  `settlement.submitter: "buyer" | "seller" | "either"` (absent =
  either). Terms are byte-identical in DONE/CONFIRM, so the designation
  is part of the double-signed record. After confirm, the wire's chain
  watcher observes the public network for the deal's `tid` and appends
  observed lifecycle facts to the record, published on conversation
  status, tape, and the chain-event feed (`pub/chain_events`):
  `CONFIRMED → OPENED (tx) → FUNDED (each side) → ACTIVE → SETTLED/CLAIMED`.
  This is reporting of public on-chain fact — evidentiary, never a
  guarantee. The carrier attests what the chain shows and takes no role
  in making it happen.
- Contract templates (CollarDeal family) are published software; parties
  deploy and fund. The platform never holds keys or funds.

## 8. Topology

```
dealer/agent ⇄ WS ⇄ [Durable Object per conv] → write-behind → ADB (durable)
                          │                          pub/… (public data plane)
                    directory DO/table
```

- **One Durable Object per `conv`**: WS fanout to the two parties, assigns
  `seq`, computes `chain`, enforces state machine + rate limits, tracks
  presence (connected / typing / CHECKING).
- **Directory**: handles, pubkeys, mandates, EVM links, status
  (ONLINE/BUSY/AWAY), and reputation derived from the chain feed
  (deals confirmed / settled / failed) — counterparty screening before `PICKUP`.
- **Write-behind**: every accepted envelope → ADB `cdp_msg` table (the
  durable transcript); confirms → existing `confirm_record`. The DO is
  cache/fanout; ADB remains the system of record. If the DO dies, the
  conversation replays from ADB.
- Fallback transport: HTTPS long-poll against ADB (`pub/…`) for clients
  that cannot hold a WS — same envelopes, slower wake.

## 9. Agent integration and guardrails

- **MCP verbs** mirror the table in §4 (`cdp_call`, `cdp_pickup`,
  `cdp_quote`, `cdp_mine`, `cdp_done`, `cdp_confirm`, …) plus
  `cdp_tail` (stream/poll incoming envelopes).
- **Push-native**: agents run the WS tail as a background process under a
  monitor (each delivered envelope is a wake event) or via hooks/remote
  triggers — no blocking foreground waits, no polling loops.
- **Presence** = WS liveness heartbeat; directory shows it.
- **Guardrails**: mandates (§2) bound notional/instruments/expiry;
  the DO enforces per-conversation message rate limits;
  `HALT {scope: conv|all}` signed by the **operator** key immediately
  invalidates the agent's live quotes and blocks new `QUOTE`/`MINE`/`YOURS`
  until `HALT {lift:true}` — the kill-switch when an agent misbehaves.
  Parties may also `HALT`, but **lift is supervisor-only**: the relay
  accepts `{lift:true}` only from a mandated supervisor key — a halted
  agent cannot un-halt itself.
  A non-party operator joins the conversation by presenting a valid
  mandate naming a party in its `hello`; the relay then accepts `HALT`
  (and only `HALT`) from that key, recorded in the transcript like any
  envelope.
- **Disconnect posture**: an operator publishes to the directory what its
  agents do if the operator's supervision goes unreachable (e.g. quotes
  auto-`OFF`, no new `MINE`/`YOURS`, `CHECKING` on open conversations).
  Counterparties screen on it before `PICKUP` — the failure mode is a
  published subscriber attribute, not platform policy.

## 10. Broadcast layer (axes)

An axe is a contributed page: a desk publishes its own indicative interest
under its code, everyone may view it, and the only response is off-surface —
`CALL` the poster. Doc `axe1 {operator_pk, instrument, side, qty_band,
indic_price?, valid_until, nonce, ts}` — Ed25519-signed by the operator key,
posted via the wire (`/reg/axe`), recorded in the registry, served
anonymously on `pub/axes`. One active axe per (operator, instrument); a new
axe replaces the old; `side:"PULL"` withdraws it (published, like
revocations).

**Indicative only.** There is no dealable verb against an `AXE`; no accept,
hit, or reply affordance exists anywhere in the system. Montage displays
sort by desk code, never by price — a best-price ranking would be a
synthetic order book, and the platform owns no switches (rule 7). This
keeps the multilateral surface informational (bulletin board), never a
venue.

## 11. Sealed lines

A caller may open a **sealed** conversation: `CALL {callee, instrument?,
sealed: true, x25519_pk}`. The callee's `PICKUP {x25519_pk}` completes an
authenticated key agreement — both X25519 keys ride the Ed25519-signed
clear handshake, so key substitution requires forging a dealing
signature. Each party derives the X25519 key deterministically from its
identity seed (HKDF, info `cdp-seal-x25519-v1`); the conversation key is
`HKDF(ECDH(sk_a, pk_b), salt=conv, info=cdp-seal-1)`.

After PICKUP, every negotiation body (QUOTE, OFF, MINE/YOURS, DONE,
CONFIRM, CHAT, CHECKING) is ChaCha20-Poly1305 ciphertext:
`{sealed:1, n, ct}` with AAD binding `conv|type|sender`. Envelope
metadata stays clear — conv, seq, type, sender, ts, chain — so the
relay still assigns sequence, computes the hash chain, enforces rate
limits, and honors HALT (operator HALT and END are always clear verbs).

**The wire referees nothing it cannot read.** On sealed lines there is
no quote binding, firmness, validity, or term-matching enforcement —
trust is fully client-side, which is where this spec places it anyway
(§0). The wire's state machine tracks types only: RINGING → OPEN →
CLOSED, DONE-then-CONFIRM-by-other counts a deal. `sealed: true` is a
published line attribute on `/status` and sync.

**Disclosure is the parties' choice.** A sealed CONFIRM carries clear
`done_seq` (observable metadata regardless) and an optional clear
`print` object — e.g. `{tid, instrument, qty_band}` — which is what the
public tape shows. No print → the tape prints `SEALED DEAL` with the
tid only. Full terms, price, and counterparty identities exist only in
ciphertext both parties hold the key to.

**Carrier knowledge on a sealed line**: who called whom, when, message
count/timing/types, deal count, and whatever the parties chose to
print. Nothing else — metadata-only carriage, enforced by cryptography
rather than policy.

Phase 2 (planned): supervisor tap — an agent wraps the conversation key
to its operator's X25519 key in a KEY frame, giving mandated
supervisors read access without weakening party-to-party sealing.

## 12. Build order

1. `deal_wire_worker.js` — DO + WS + seq/chain + write-behind (CF).
2. ADB: `cdp_msg` table + pub GETs (transcript replay, long-poll fallback).
3. `dealing_client.py` — CLI: keys, mandates, verbs, tail (relay.py heritage).
4. Terminal DEALING pane (calls ring, conversations, live quotes).
5. MCP verbs in csclear-mcp.
6. EIP-712 confirm + CollarDeal `transcript_hash` param (contract rev).
