Dark-pools extension — Order / Match / Settlement across pools
Status: Design — pre-implementation. Extension of settlement-flow.md. Forward-looking; not on the immediate implementation queue.
External references:
- Ethereum Interoperability Layer (EIL) — anchors §5.4 external-interop design.
- Chainlink — Blockchain Oracle Design Patterns — anchors §6.4 oracle design.
- Chainlink — Crypto Dark Pools — DECO + Data Standard context for §6.4.
1. Why this extension is structural
The OrderRegistry treats orders as cleartext investor-to-issuer subscriptions. That is the right shape for primary issuance but breaks down for:
- Block trades on tokenized RWAs, where pre-trade size is itself market-moving information.
- MMF rebalancing (tokenized-mmf.md), where one fund's redemption signals stress to the rest of the venue.
- Repo collateral matching (intraday-repo.md), where a desk's funding ask reveals its liquidity position.
These use cases need intent hidden until match, not just the cash leg. The DBC layer already ships the cash-leg confidentiality primitives — Pedersen (pedersen.go), RingCT, stealth addresses (stealth.go). Dark pools reuse them to hide price + size + counterparty pre-match, then route matched pairs through the existing Order → Invoice → Payment → Receipt pipeline for settlement.
This doc is an extension, not a replacement. The Order state machine gets two new states prepended; everything from Invoice onward is unchanged.
2. Conceptual mapping
Per conceptual-model.md:
- Each pool = Relationship, not a Persona. The operating
DarkPoolcontract IS the Relationship artefact — same discipline as the Token-contract-as-Relationship in the conceptual model. Multi-tenant by design: multiple pool contracts may share one cocoon-L2 Location instance (deployment is flexible — one-per-L2 or grouped per operator preference; not architectural). Operator-Org-Persona (organizations.md) declares its mandate at pool-deployment time. Asset-to-pool granularity is deliberately undecided — one pool contract per asset, per asset class, or multi-asset is an implementation choice, not architectural. - Traders are Personas-as-Parties under a
traderrole-on-relationship-to-pool. Same Persona may trade across pools simultaneously; the role attaches to the Relationship, not the Persona. - The operator is a Party. Specifically, the operator running the matching process is an Org-Persona with a
darkpool:operatorRelationship to each trader. The operator's trust mandate is on-chain (registered matcher code + binding UCAN); the operator's execution is off-chain. See §6.1 for the trust model. - Compliance is party-attributed: every fill carries three independent compliance attestations — venue-compliance, buyer-compliance, seller-compliance. Cross-pool fills require both pools' compliance to agree.
3. State machine — extension of settlement-flow.md
Two new states prepended to settlement-flow.md's pipeline:
┌────────────┐
│ Committed │ trader builds Pedersen commitment over (px, qty, side);
└─────┬──────┘ submits to OrderRegistry as OrderType.PrivateMatch.
│ On-chain footprint = (orderId, commitment, stealthRecipient).
│ operator-Party's matching process finds counterpart
▼
┌────────────┐
│ Matched │ match proposal accepted by on-chain DarkPoolMatcher.
└─────┬──────┘ Two-sided Invoice templates handed to both parties.
│
▼
┌────────────┐
│ Invoice │ ──── settlement-flow.md continues unchanged ────▶ Settled
└────────────┘
Plus three lateral / terminal states:
- Expired —
Committedorder times out before matching. Commitment revealed for audit (scope: open question). - Withdrawn — trader cancels pre-match. Commitment revealed to operator-only (default; per-venue config).
- Rejected (mismatch) — match emitted but a compliance leg fails at Invoice. Both legs roll back atomically.
OrderRegistry.sol gains OrderType.PrivateMatch + OrderStatus.Committed | Matched, plus optional bytes32 commitment and bytes32 stealthRecipient fields (zero on existing rows; backwards-compatible).
4. Privacy model
Layered per conveyance_taxonomy — commitments in private-wallet layer, attestations on cocoon-L2, no L1 leakage.
Design principle: ZK-first, cleartext fallback only where ZK isn't viable for v1. Every cleartext reveal in v1 is a pragmatic starting point with ZK as the preferred evolution. This applies to: matcher verification tiers (T1 → T3), oracle delivery (O1 → O2 DECO-attested), withdrawal reveal, search hints, cross-pool spend lookups, and operator dispute resolution.
| Datum | Pre-match | At match | At settlement |
|---|---|---|---|
| Price / size / side | Pedersen-committed | Revealed to counterparty + operator-Party only (cleartext in T1/T2 verification; ZK-private in T3) | NAV-band check on chain; cleartext fill recorded off-chain only |
| Recipient | Stealth address | Resolved by counterparty via stealth-scan | Persona binding lazy-on-spend |
| Operator visibility | Configurable: zero / digest-only / full | Full (matching agent did the work) | Full, attribution-tagged |
| Regulator visibility | Party-attributed selective-reveal via jurisdiction-scoped UCAN | Same | Same; reveal events are themselves audit-logged |
The Discovery / Attestation / Disclosure model maps cleanly: order commitments are Discovery, match attestations are Attestation, fills surfaced to compliance are Disclosure.
5. Bridging — four layers + spend-book routing
5.1 Layer 1 — L1 ↔ pool (per-pool Railgun on-ramp)
Each pool stands up its own Railgun-aware bridge contract on mainnet; traders pick pool by choice of bridge. Otherwise unchanged from the railgun_l1_onramp memory:
- Trader shields mainnet asset into Railgun at time T1.
- After temporal decoupling (T2 ≠ T1; the privacy property requires this), trader invokes the pool's L1 bridge contract via Railgun
transact(). Bridge sees the Railgun contract as caller, not the trader's EOA. Event reveals(token, amount, l2Commitment)wherel2Commitment = Poseidon(stealth_address, salt). - Cocoon-side: pool's DBCBridge.sol consumes the L1 proof event, mints a DBC under the stealth recipient. No contract change to DBCBridge — the inbound mint shape already supports it; what changes is the provenance of the proof.
- Persona binding lazy-on-spend: the trader's wallet signs a UCAN binding
stealth_address → persona_DIDonly when the DBC is consumed into a dark-pool order.
Withdrawal symmetric: melt DBC via DBCBridge → bridge emits Railgun-consumable note on L1 → trader unshields later.
Per productization_railgun_dbc_comparison Workstream 3. Stealth gain comes from temporal decoupling; shield-and-bridge atomically loses the privacy property.
5.2 Layer 2 — pool ↔ pool peer bridge + spend-book routing
DBCs are fungible across pools. A DBC homed in pool A can be spent in pool B via one of three routing mechanisms.
Shared substrate: PoolRegistry contract (chain-coordinated service per chain_coordinated_services) — directory mapping (chainId, poolId, dbc_id) → homeSpentBookAddr (multi-tenant-aware key per §2). v1 placement: hub cocoon-L2 instance — single source of truth, low ecosystem latency, defers the L1-anchoring question to Stage 4 rollup work. The hub is itself a chain-coordinated service; its operator-Party is mandated and accountable like any other.
Forward direction: combination of L1 + L2 hubs. L1 carries pool entry points / advertising / trust roots (operator-Party DIDs, registered matcher code hashes, audit anchors) — dark pools' existence is public on L1. L2 hub federation carries flow coordination — dark pools' flow stays private at this layer. Multi-hub topology, each hub directing for some subset of pools. This maps to the conveyance taxonomy: L1 = public-permissionless, L2 hub = consent-based, private wallet = self-sovereign.
Updated by mint events from any pool; replicated to peers by relays. Read by any pool's settlement contract before invoking one of the three mechanisms below.
5.2.a Direct cross-chain spend (synchronous lookup)
Pool B's settlement contract reads home via PoolRegistry, submits spend(keyImage, salt, fillContext) to pool A's SpentBook through a relay, awaits a contract-signed attestation, completes settlement.
Use when: low-contention same-block-ish settlements; relay round-trip acceptable. Trade-off: settlement stalls if relay slow; subject to cross-pool spend races.
5.2.b Lease + commit — atomicity + market-making + overcommit-protection primitive
A general-purpose SpentBook lease primitive serves three use cases. The first two are core (v1); the third is an advanced model that the same primitive enables without contract changes.
Use case A — cross-pool spend atomicity. Pool B (or its settlement contract) leases the key image to guarantee an atomic settlement window:
- Acquire:
leaseKeyImage(keyImage, salt, leaseDuration, lessee) → leaseId. SpentBook verifies key image unspent + unleased, recordskeyImage → {leaseId, lessee, expiresAt}, refuses all other spend or lease attempts during the window. - Use: within
leaseDuration, pool B's settlement callsfinalizeLease(leaseId, finalAttestation)via relay — converts lease to a real spend. - Timeout:
expiresAtelapses without finalisation → automatic release.
Use case B — wallet → pool liquidity provision. A trader's wallet leases a DBC to a pool so the pool's matching process can fill orders against that liquidity without the trader being online for every fill. Same primitive, different lessee + longer duration:
- Pledge: wallet calls
leaseKeyImage(keyImage, salt, leaseDuration, lessee=operatorPartyKey)— operator-Party now has consumption authority over the DBC until expiry. - Fill: when the pool matches an order against the pledged DBC, the operator-Party calls
finalizeLeaseas part of settlement. - Expiry: if the lease window closes with the DBC unspent (or only partially consumed if leasing supports partial fills — open question), the unused capacity returns to the wallet's spendable holdings automatically.
Use case C — crossing-time lease for over-committed interest (advanced model). The trader expresses interest (Pedersen-committed orders) at multiple pools backed by the same DBC, intentionally over-committing from an interest perspective — total expressed interest exceeds liquid capacity. The wallet does not pre-lease. Instead, the trader's UCAN delegates darkpool:spentbook:lease to each pool's operator-Party with a very short max-duration caveat (seconds, not minutes). When any pool's matching process identifies a crossable match, it acquires the lease just-in-time:
- Just-in-time acquire: operator-Party at the first-to-cross pool calls
leaseKeyImageusing the trader's delegated UCAN, with the crossing window asleaseDuration. - Finalize: the cross completes within the window via
finalizeLease. - SpentBook protects overspend: concurrent attempts from other pools' operators fail with
LeaseRejected(key image already leased or already spent). The trader's interest at those pools dies for this DBC; their commitments revert toWithdrawn(configurable per §11).
The pattern works because the SpentBook is the canonical arbiter of spend authority — even when interest is over-committed at the pool / interest layer, only one matching process can cross. There is no overspend risk; what fails is the intent at the losing pools, not any settlement.
Common properties of the lease primitive:
- The lease resolves cross-pool spend races: first to acquire wins; concurrent attempts rejected during the window.
- Atomicity is by reservation, not synchronous spend.
LeaseAcquired / LeaseFinalized / LeaseReleased / LeaseExpiredevents are emitted by SpentBook. Wallets and pool agents subscribe to these instead of polling SpentBook state. Event-driven lifecycle is a hard requirement — polling all leased key images across all SpentBooks doesn't scale.
Use case A is when: high-value cross-pool fills, non-trivial settlement latency, multi-leg trades needing a co-ordinated window. Use case B is when: market-making, wallet wants its DBC to be matched while the trader is offline, longer windows (minutes to hours) appropriate. Use case C is when: institutional traders want broad participation across many venues without pre-allocating liquidity to each, accepting that some interest will be invalidated by faster crosses elsewhere. Closest traditional analogue: IOIs routed across multiple venues.
5.2.c Burn-mint cross-chain migration
When a DBC will see repeated use in pool B (or its trader moves), permanent home migration via burn-on-A + mint-on-B:
- Trader calls
burnForMint(keyImage, salt, destinationPoolId, destinationStealthAddr)on pool A's SpentBook — burn recorded withbridge-outflag + burn attestation emitted. - Pool B's peer-bridge contract consumes the burn attestation, mints a fresh DBC under B's SpentBook with provenance pointing to A's burn.
- PoolRegistry updates
dbc_id → (B's chainId, B's SpentBookAddr).
Use when: long-lived DBCs with many expected future spends in pool B; portfolio reorganisation; pool decommissioning.
Failure modes
| Failure | 5.2.a | 5.2.b | 5.2.c |
|---|---|---|---|
| Pool A unreachable | settlement stalls (retryable) | lease safe — key image locked locally in A, spendable nowhere | can't initiate burn |
| Compromised relay | mitigated: SpentBook attestation signed by contract-bound key, not relay key | same | same |
| Clock drift between chains | n/a | SpentBook A uses own block timestamp; pool B must finalise before A's clock says expired | n/a |
| Pool B fails mid-flow | leaks information (A learns of intent) | key image stuck until lease timeout | atomicity by relay-confirmed burn before mint |
5.3 Layer 3 — match → on-chain DVP settlement
Match output = three artefacts:
- Pair of Invoice templates with shared
MatchIDcross-references (one buyer-side, one seller-side; uses settlement-flow.md'sPriorInvoiceIDfield). - Match attestation UCAN signed by operator-Party.
fact: {matchId, orderA, orderB, matchedAt, fillPrice, fillAmount, instrument, verificationTier}. Audience = the settlement bridge contract. - Two DBC-builder requests to dbcagent.go, each carrying a
MatchIDcaveat. Calculator refuses to build a payment carrying aMatchIDunless attestation UCAN validates.
New contract DarkPoolSettlement.sol (one per pool) accepts (attestationUCAN, invoiceA, paymentA, invoiceB, paymentB):
- Verifies attestation signed by registered operator-Party (UCAN chain rooted at operator-Persona's pool-deployment-time mandate).
- Verifies both invoices share
matchId. - Verifies key images unspent — local SpentBook for same-pool DBCs; §5.2 routed lookup for foreign.
- Verifies fill-price inside oracle band at match time (§6.2 + §6.4).
Only on all checks passing does it call OrderRegistry.markSettled(...) twice atomically. DVP = both legs settle in one tx, or neither does.
5.4 Layer 4 — EIL-style external interop (beyond cocoon ecosystem)
Per the Ethereum Foundation's Ethereum Interoperability Layer (EIL) spec (Nov 2025), Ethereum L2s share a standard mechanism for trust-minimized cross-L2 operations. EIL builds on ERC-4337 account abstraction with:
- One-signature multi-chain ops bundled as a Merkle tree of UserOperations.
- Cross-Chain Liquidity Providers (XLPs) auctioned on Ethereum L1 to provide instant transfers + gas, secured by L1 staking.
- Compatibility requirements: settle to Ethereum L1, expose a canonical bridge, be EVM-compatible.
For cocoon-L2-as-an-Ethereum-L2 (Stage 3+ productization scope per base_chain_integration_staging), each dark-pool's cocoon-L2 instance can be an EIL participant. The dark-pool trader gains:
- External liquidity inflow: a trader on any EIL-compatible L2 (Optimism, Base, Arbitrum, …) can fund a dark-pool order on a cocoon dark-pool via one signature, no manual bridging.
- External withdrawal: dark-pool fills can be withdrawn to any EIL-compatible L2 with the trader's preferred liquidity venue.
- Liquidity-provider economics: XLPs bid on the EIL auction to provide cocoon-pool entry/exit liquidity — connects naturally to cocoon's chain-coordinated services marketplace.
Integration shape (forward-looking — Stage 3+):
- Cocoon-L2 instances expose canonical bridge per EIL v1 requirements.
- Cocoon's L1-anchoring (Stage 4 rollup) provides EIL's trust assumptions.
- The EIL XLP auction may run on cocoon-L2 itself (chain-coordinated services) or on Ethereum L1 — design call.
EIL vs §5.1 Railgun on-ramp — complementary, not competing:
| Property | §5.1 Railgun on-ramp | §5.4 EIL |
|---|---|---|
| Primary virtue | Privacy via anonymity-set + temporal decoupling | Latency + multi-chain ergonomics |
| Source chain | Ethereum mainnet (L1) only | Any EIL-compatible Ethereum L2 |
| Settlement speed | Slow (temporal decoupling required) | Fast (XLP-backed instant) |
| Privacy property | Strong (EOA unlinked from cocoon-pool entry) | Weak (standard EIL leaks origin chain + EOA) |
| Trust model | Railgun anonymity set + cocoon bridge | EIL L1-staked XLPs + dispute arbitration |
A trader may use Railgun for high-privacy entry from L1, EIL for fast bridging from another L2. Both target the same end state (DBC in a cocoon pool).
v1 placement: §5.4 is Stage 3+ Interop work. v1 ships §5.1 (Railgun on-ramp) and stubs §5.4 hooks for later. EIL spec is testnet-ready Nov 2025; cocoon's productization timeline aligns with EIL mainnet maturity.
6. Matching, price discovery, and oracle design
6.1 The matching contract — registered code, off-chain execution by operator-Party
The matcher is registered code, off-chain execution by a trusted operator-Party. The canonical match logic — the matcher's spec / bytecode — lives on-chain at a known address per pool. A designated operator-Party runs the matcher off-chain for latency and computational headroom. The operator's outputs are accepted on-chain only against the registered code's specification; the operator's signed attestation provides the trust handover.
Operator-as-Party in the conceptual model:
- Operator is an Org-Persona with a
darkpool:operatorRelationship to each trader, formed at pool-join time. - The operator's mandate is on-chain: a binding UCAN naming the operator-Party + a reference to the registered matcher code. Traders consent to this mandate at pool-join. Surprise is impossible.
- Operator trust is load-bearing. If the operator produces matches inconsistent with the registered code, traders dispute via the operator-Relationship's dispute path. Slashing of operator-staked DBC, reputation impact, and revocation of the operator-mandate UCAN are the consequences.
Verification model — three tiers, selectable per pool / per-instrument-class:
| Tier | Verification | Latency | Trust assumption |
|---|---|---|---|
| T1 — Signed | Operator-Party signs match output; on-chain matcher accepts on signature alone | Lowest (sub-second) | Operator fully trusted; misbehaviour caught post-hoc by traders' own checks + dispute path |
| T2 — Optimistic | Operator signs + posts a fraud-window bond; matches finalize after the window unless disputed by traders. Disputes resolved by replaying registered code on-chain. | Medium (minutes for the dispute window) | Operator trusted with on-chain dispute fallback |
| T3 — Proven | Operator runs registered code in a TEE or produces a ZK proof of execution; matcher verifies the proof | Higher (proof generation overhead) | Trust-minimized; v2+ |
V1 ships T1 only. Disputes are post-hoc Relationship-level reputation events at the operator-Party tier, not on-chain replays. T2 (optimistic fraud-bond) and T3 (ZK/TEE) are forward-compatible but their mechanics — bond sizing, dispute windows, on-chain matcher-replay gas budgets, slashing thresholds — are deferred to dedicated follow-on design sessions. The matcher's verification function is an abstraction that allows swapping the tier without disturbing trader UX or settlement.
The proposed match the operator-Party submits to the on-chain matcher:
(orderA, orderB, fillAmount, claimedClearingPx, revealA, revealB, traderAttestationA, traderAttestationB, verificationTierProof)
DarkPoolMatcher.sol verifies:
- Both orders are in
Committedstate in OrderRegistry. - Pedersen reveals open to the values traders committed to.
- Sides are opposite (one buy, one sell); instruments match.
- Reveal prices are compatible at the claimed clearing price (buyer's px ≥ clearing px ≥ seller's px).
claimedClearingPxis inside the configured price band of the pool's reference oracle (§6.2 + §6.4).- Trader attestations (UCANs) authorise the match against their commitments.
- Tier check: operator signature (T1) / fraud bond (T2) / TEE-or-ZK proof (T3) per pool config.
On success: state transitions both orders to Matched, emits MatchEmitted carrying MatchID, and triggers DarkPoolSettlement.sol for DVP (§5.3).
What stays on chain:
- Registered matcher code (canonical spec, immutable post-deploy).
- Operator-Party mandate (UCAN chain rooted at pool-deployment-time).
- Match validation envelope (event emission, MatchID assignment, settlement trigger).
- Trader consent attestations.
- Oracle price-band check.
- Verification tier check.
What lives off-chain (executed by operator-Party):
- Discovery: finding crossable pairs in the commitment set.
- Sorting / fairness logic per registered policy (price-time priority, pro-rata, periodic-auction clearing, etc.).
- Execution of the registered matching algorithm.
- Match proposal assembly.
This is the "smart contract as registered spec" pattern, distinct from "smart contract as on-chain executor". The contract is the authority, not the runtime.
6.2 Price discovery — pool configuration
Each pool configures a price discovery mode at deployment. The mode is part of the pool's Relationship-contract identity (§2): traders consent to it when joining the pool; the operator cannot switch modes without deploying a new pool.
Mode P1 — Reference-price cross. The pool consumes prices from an oracle; the matcher clears trades at oracle price ± configured band. Most common shape for institutional block trades on regulated instruments.
Mode P2 — Periodic auction. The pool aggregates commitments over a window (every N blocks), then runs a uniform-clearing-price auction at window close. The clearing price is produced by the auction, not consumed from an external feed. Suits instruments where NAV is stale or doesn't reflect willingness-to-trade. The matcher contract for an auction pool replaces the bilateral match-proposal pattern with a periodic auction-resolution call.
Mode P3 — Negotiated cross with reveal. Counterparties negotiate price off-chain (typically via cross-persona-messaging.md), then submit the negotiated price to the matcher with both parties' attestations. The reference oracle is used only as a sanity-band guard (the negotiated price must be within configured tolerance of the reference). Suits illiquid instruments / OTC-style trades.
6.3 Price discovery as part of pool identity
A pool's price-discovery mode is a first-class element of the pool's Relationship contract (per §2). Consequences:
- Traders entering a pool consent to its discovery model as part of the relationship handshake; surprise is impossible.
- Compliance regimes can require specific modes (e.g. "this jurisdiction requires periodic auction with N-minute windows").
- Switching modes requires a new pool deployment + new Relationship. There is no admin function to change discovery mode on an existing pool.
This makes price discovery conveyable in the conveyance_taxonomy sense — it's a property of the Relationship that flows along with every fill, not a hidden contract parameter.
6.4 On-chain oracle design
For P1 (reference-price cross) and the sanity-band check in P3, the matcher (§6.1 check 5) consumes a price feed. How that feed gets on chain is a real design decision. Three patterns from the oracle literature (Chainlink — Blockchain Oracle Design Patterns):
| Pattern | How it works | Pros | Cons |
|---|---|---|---|
| O1 — Push (publish-subscribe) | Trusted publisher pushes prices to an on-chain oracle contract; consumers read | Cheap reads; latency bounded by publisher cadence | Trust in publisher's diligence + integrity |
| O2 — Pull with attestation (request-response) | Consumer requests; relayer fetches off-chain, returns value with signature or TLS-attested proof (DECO-style) | Fresh on demand; auditable provenance | Per-read cost; off-chain dependency in critical path |
| O3 — Schelling-point / aggregator | Multiple publishers post; on-chain logic aggregates (median, Schelling-bounded by 25th-75th percentile) | Trust-minimized; no single point of failure | Complex; latency = publisher consensus; per-publisher cost |
Recommendation for v1: Pattern O1 (push), tier-graded by instrument class.
- NAV-priced regulated instruments (tokenized funds, MMFs): the existing on-chain NAVOracle.sol. Single trusted publisher per instrument (the fund administrator). Tight band. Already used by settlement-flow.md.
- Crypto-asset instruments with lit markets: a cocoon-side oracle contract that mirrors a designated external feed (Chainlink Data Standard is the institutional norm — VWAP, tamper-proof). Cross-pulled into cocoon-L2 via the broader oracle infra (out of scope of this doc but relied on).
- OTC / bilateral instruments: no oracle in the band-check critical path; matcher uses negotiated price directly with a wider sanity band against a coarser reference (custodial reference rate per pool config).
V2+ may upgrade to O2 (DECO-attested pulls) for higher-trust scenarios or O3 (aggregator) for adversarial environments. The matcher's checkOraclePrice(...) function abstracts the pattern — the underlying oracle delivery can change without re-deploying matchers.
Privacy property: oracle reads are public — any party can read NAVOracle. The matcher's read does not leak a trader's identity since it's part of match validation, not a per-trader query. For compliance-sensitive reads requiring trader-identity-blinded provenance (e.g. accredited-investor proofs), privacy-preserving oracles like DECO are forward-compatible — they don't ship in v1 but the matcher abstraction tolerates them.
Privacy-preserving oracle direction (out of scope for v1, captured as a forward concern): DECO-style protocols let a user prove a property about external data (e.g. "I am accredited", "this NAV came from publisher X at time T") to a smart contract without revealing the underlying data on-chain. Useful when the oracle read itself is sensitive (KYC/AML attestations, jurisdiction-restricted price feeds).
Test-feed dependency for slice-zero: the cocoon-side oracle mirror contract for external feeds does not yet exist. Slice-zero scope is therefore NAV-priced regulated instruments only, using the existing NAVOracle.sol. Crypto-asset and OTC instrument classes wait until the broader oracle infrastructure (external-feed mirror contract + test harness) is built. A test fixture at pocs/services/internal/oracle/testfeed/ supplies deterministic NAV scenarios (stable, volatile, gap-down, illiquid) for matcher unit tests; reuses the existing settlement-flow test harness for NAV publishing patterns.
6.5 Auto-aggregator pattern — pool as smart-order-router
A dark pool may itself act as an auto-aggregator for other pools. From the trader's perspective they post an order to pool A; behind the scenes pool A's operator-Party routes liquidity from pools B, C, D via §5.2 cross-pool mechanisms.
How it works:
- Pool A's matching process monitors digest registries (or read-only subscriptions) for pools B, C, D's open commitments — same surface as cross-venue discovery.
- When pool A receives a new order, its matcher tries:
- Internal cross at A first (latency wins).
- If no internal cross, propose a cross-pool match via §5.2 routing (5.2.a direct, 5.2.b lease, or 5.2.c burn-mint).
- The trader sees a single fill, possibly with metadata indicating the routing pool.
Trust model:
- Trader-to-pool-A: trader explicitly trusts A's operator-Party (consented at pool-join time per §2 + §6.1).
- Pool-A-to-foreign-pools: A's operator-Party must trust enough of B, C, D's operators to route to them. Each pair is its own Relationship — same operator-mandate framework as §6.1.
- The trader does not need to trust B, C, D directly. A is the front-pool and bears settlement responsibility from the trader's perspective. If a foreign fill fails, A is on the hook (configurable: refund + retry internally, or pass failure to trader).
Configuration:
- Per-pool deployment flag
aggregator=true/false. - Per-foreign-pool routing allow-list managed by operator-Party, gated by compliance attestations between operators.
- Fee model (out of scope for v1): aggregator pool may charge a routing fee on top of the foreign fill's clearing price.
Traditional analogue: equity smart-order routers (SOR) routing across lit + dark venues. Composes §5.2 + §6.1 into a higher-order orchestration. v1 placement: optional pool config; pools default to internal-only.
7. UCAN capability namespace
Extends capability.go with new strings (no Go code change — string additions match the existing <namespace>/* wildcard pattern):
| Capability | Issuer | Audience | nb caveats |
|---|---|---|---|
darkpool:venue:operate | Operator-Persona root | Operator-Party service key | jurisdiction, max-orders, instruments, registered-matcher-code-hash |
darkpool:submit | Trader-Persona | Trader wallet | venue, max-size, instruments, expiry |
darkpool:match:propose | Operator-Persona | Operator-Party (off-chain matcher runner) | venue, max-fill, price-band, time-window, verification-tier |
darkpool:match-attest:emit | Operator-Persona | Operator-Party (sub of :match:propose) | one-per-matchId |
darkpool:settle | Operator OR Trader | Settlement contract DID | matchId binding |
darkpool:reveal-to-compliance | Trader OR Operator | Compliance agent | jurisdiction, claim-set, audit |
darkpool:spentbook:lookup | Pool-Operator-A | Pool-B settlement / relay | one-per-keyImage (5.2.a) |
darkpool:spentbook:lease | Pool-Operator-A or Trader-Persona | Pool-B settlement or delegated operator-Party (5.2.b use case C) | keyImage, leaseDuration, lessee. Delegable; when issued by Trader-Persona, enables crossing-time lease for over-committed interest. |
darkpool:spentbook:lease-finalize | Pool-B-settlement | Pool-A's SpentBook | leaseId binding (5.2.b) |
darkpool:spentbook:burn-for-mint | Trader-Persona | Pool-A's SpentBook | destinationPoolId, destinationStealthAddr (5.2.c) |
darkpool:aggregator:route | Operator-Party-A | Operator-Party-B (per-relationship) | instruments, max-fill, allow-list (§6.5) |
darkpool:digest-read (later) | Operator | Cross-pool discovery agent | digest-only |
Delegation example for a routine same-pool fill: operator-Persona root → operator-Party service key (:venue:operate) → off-chain matcher runner (:match:propose + :match-attest:emit) → fired-once attestation UCAN for one specific matchId (audience = DarkPoolSettlement.sol).
8. Integration touchpoints
| Component | File | Change |
|---|---|---|
| OrderRegistry | OrderRegistry.sol | Add OrderType.PrivateMatch; add `OrderStatus.Committed |
| Order RPC | order/component.go | New methods: createPrivateOrder, getCommittedOrders, markMatched, withdrawCommitted. |
| NAVOracle | NAVOracle.sol | No contract change. Referenced by matcher (band check, §6.1) + settlement bridge (band check at settle). |
| DBCBridge | DBCBridge.sol | No change. Mint/melt unchanged whether driven by primary issuance or dark-pool fill. |
| SpentBook | SpentBook.sol | New entrypoints — spendForForeignSettlement(...) (5.2.a); leaseKeyImage(...) + finalizeLease(...) + releaseLease(...) (5.2.b); burnForMint(...) (5.2.c). All emit SpentBook-signed attestation events. Required events: LeaseAcquired, LeaseFinalized, LeaseReleased, LeaseExpired — wallets and pool agents subscribe to these instead of polling. |
| dbcagent | dbcagent.go | BuildPaymentRequest gains optional MatchID + MatchAttestationUCAN. Calculator refuses MatchID payments without valid attestation. |
| invoice / payment / receipt | invoice/, payment/, receipt/ | No change. Consumed unchanged. Invoice's Order reference may point at a PrivateMatch order — entities don't care. |
| settlement-flow.md | settlement-flow.md | Cross-referenced as parent; this doc derives its state machine. |
| permissioned-crdt-wallet | permissioned-crdt-wallet.md | Open question — reuse dbc/ stream or new darkpool/ stream for commitments + salts + lease state. |
New contracts (interface sketch only at this stage):
DarkPoolMatcher.sol— per pool, registered code authority + on-chain verifier (§6.1).DarkPoolSettlement.sol— per pool, the DVP bridge (§5.3).PoolRegistry.sol— global directory for cross-chain SpentBook lookup (chain-coordinated service, §5.2).- Pool's L1 Railgun-aware bridge contract — one per pool, on mainnet (§5.1).
- EIL-canonical bridge — one per pool's cocoon-L2 instance, per EIL v1 requirements (§5.4, Stage 3+).
9. Wallet model — pool-agnostic operation + composite view
A trader's DBC wallet operates across multiple dark pools concurrently. From the trader's perspective there is one wallet; the pool is a Relationship dimension, not a separate wallet instance. The wallet is the unifier; PoolRegistry is the substrate that lets it stay consistent.
9.1 What the wallet aggregates per persona
| Stream slice | Cross-pool aggregation |
|---|---|
| DBC holdings | Every DBC the persona controls, tagged with its current homePoolId (resolved via PoolRegistry). Spend availability per-DBC reflects routing-mechanism state (free / leased / in-flight burn). |
| Open commitments | Pedersen commitments for Committed-state orders across every pool the persona participates in. Each commitment carries its (poolId, orderId, salt) so the wallet can reveal on withdrawal or expiry. |
| Active leases | In-flight 5.2.b leases the persona is party to — either lessor (their DBC is leased out) or lessee (they're spending via a foreign-pool lease). |
| Fills / settled positions | Match history across pools, with MatchID traceable to the originating pool. |
| Pool memberships | Per-pool trader-role UCAN chains + capability windows. |
9.2 Composite UI
The wallet UI presents a single composite view back to the trader:
- Balance: aggregate DBC holdings across all pools. Optional per-pool filter; pool tag visible on each holding.
- Open orders: all
Committedorders across all pools in one list. Sortable by pool, instrument, expiry, side. - Pending fills: matched-but-not-settled state, including in-flight cross-pool lease finalisations.
- History: settled positions + receipts, pool-tagged.
- Cross-pool flow indicator: when a fill is cross-pool, the UI shows the routing mechanism (5.2.a / 5.2.b / 5.2.c) and its current step. Useful for surfacing stuck leases or stalled lookups.
9.3 Consistency model for the composite view
The composite view is eventually consistent with PoolRegistry — the wallet may lag a pool's recent mint by relay round-trip latency. UI surfaces "syncing" state per pool rather than blocking the whole view.
v1 defaults:
- Stale-indicator threshold: 60 seconds lag per pool — UI shows "syncing" badge.
- Refuse non-critical actions threshold: 5 minutes lag — wallet disables order-prep buttons.
- Active actions always re-verify against home SpentBook synchronously (sign-to-spend, lease-finalize, withdraw-commitment) — no caching tolerance there.
Intent recorded for forward design: tiered SLA contracts where each operator-Party declares per-pool freshness expectations at pool-join time, with reputation-tied breach signals. v1 keeps flat defaults; the per-pool-declared-SLA model layers on top later.
9.4 Wallet-as-lessor — leasing liquidity to a pool
The wallet uses §5.2.b's lease primitive in the lessor role: pledging DBC liquidity to a pool's operator-Party for a defined window (use case B in §5.2.b). The pool can fill orders against the pledged DBC while the trader is offline; unused capacity returns to the wallet on lease expiry.
Lease lifecycle from the wallet's perspective:
┌──────────────┐
│ Pledged │ wallet signed leaseKeyImage(); DBC marked leased-out;
└──────┬───────┘ capacity visible in composite UI as "leased to <pool>".
│
├──── operator-Party fills → LeaseFinalized event ───▶ Spent
│
├──── trader releases early → LeaseReleased event ───▶ Returned
│
└──── window elapses → LeaseExpired event ──────────▶ Returned
Event-driven — the wallet must not poll SpentBook state to discover lease termination. It subscribes to LeaseAcquired / LeaseFinalized / LeaseReleased / LeaseExpired events on each SpentBook it has active leases against; the events fire wallet-side state transitions and UI updates.
9.5 Over-committed interest (advanced model)
The wallet may express interest (Pedersen-committed orders) across multiple pools backed by the same DBC liquidity, intentionally exceeding actual capacity. This composes the §5.2.b use case C lease pattern:
- Wallet posts commitments at pools 1, 2, 3 with overlapping DBC backing.
- Wallet issues a short-window
darkpool:spentbook:leaseUCAN to each pool's operator-Party — delegated authority to lease the DBC's key image just-in-time at crossing. - First pool to cross acquires the lease (SpentBook enforces single-leaseholder); finalises; trader's interest at the other pools is invalidated for this DBC and the commitments transition to
Withdrawn.
The composite UI must surface over-commitment honestly:
- "Committed interest: 3× actual liquid capacity across 5 pools."
- Per-DBC: "Backing N open commitments; first crossing wins."
- Lease activity feed: when a key image is leased / finalised / rejected, each event updates the affected commitments' state in the UI.
This is an advanced model, not v1. The SpentBook lease primitive (v1) is what makes it safe; the wallet-side policy + UX for managing over-commitment is forward-looking. Open question §11 captures this.
9.6 Wallet stream design
This shapes the permissioned-crdt-wallet's stream allocation. Decision: new darkpool/ stream added as the 6th data-type family alongside keys / perms / DBC refs / agent memory / DID directory. The lease state machine + commitment management have their own subscription topology (event handlers, expiry timers, composite-view aggregation) that doesn't compose with primary-issuance DBC refs.
Wider principle this confirms — wallet stream per data type family. Stream management should feel natural to both providers (services that emit data) and users (wallet that consumes / displays). When functionality is added, ask "is this a new data type family, or does it belong in an existing one?" Don't overload existing streams with adjacent-but-distinct lifecycles. New families get new streams.
10. Implementation slices (informational, not committed)
- DP-1: OrderRegistry extension + Pedersen commitment scheme + new RPC methods.
- DP-2: Operator-Party mandate framework +
DarkPoolMatcher.sol(T1 — signed verification) + match-attestation UCAN. - DP-3:
DarkPoolSettlement.sol+ DVP atomicity (single-pool, no cross-pool routing yet). - DP-4: Layer 1 L1 ↔ pool Railgun on-ramp (gated on Workstream 1 of productization_railgun_dbc_comparison).
- DP-5a: PoolRegistry + direct cross-chain lookup (5.2.a).
- DP-5b: Lease primitive — two-phase commit with timeout + event emission (5.2.b).
- DP-5c: Burn-mint cross-pool migration (5.2.c).
- DP-6: Multi-pool wire-up + cross-pool matching.
- DP-7: Oracle integration (§6.4) — NAVOracle wiring for P1; external-feed mirror contract sketched (deferred to broader oracle infra).
- DP-8: Auto-aggregator opt-in (§6.5).
- DP-9: Layer 4 EIL canonical-bridge surface (Stage 3+ work).
- DP-10: T2 optimistic verification tier (§6.1).
- DP-11: T3 proven verification tier (TEE / ZK) — Stage 4+.
11. Resolved design decisions (v1) + new open questions
The 13 open questions from the original draft were walked through in a design-decision session and resolved into v1 defaults + forward intent. The detailed walkthrough lives in dark-pools-design-outline.md.
11.1 Resolved (v1 defaults + forward intent)
| # | Topic | v1 default | Forward intent |
|---|---|---|---|
| Q1 | Pool deployment | Multi-tenant by design; deployment flexible (one-per-L2 or grouped per operator preference) | Asset-to-pool granularity deliberately undecided (one per asset / class / multi-asset all permitted) |
| Q2 | PoolRegistry placement | Hub cocoon-L2 instance | L1 (advertising + trust roots, publicly visible) + L2 hub federation (flow coordination, private). Multi-hub topology |
| Q3 | Lease duration defaults | A=10s / B=30min / C=3s; hard SpentBook maxes 60s / 24h / 30s | Per-venue config knobs at pool deployment |
| Q4 | Routing mechanism selection | 5.2.b lease+commit as default for cross-pool fills | Operator-Party config knobs for 5.2.a override on known-low-contention foreign pools; 5.2.c never auto-selected |
| Q5 | Wallet stream allocation | New darkpool/ stream | Stream-per-data-type-family principle codified |
| Q6 | Match-attestation freshness | 5 minutes, per-venue configurable | Mode P1 NAV-band pools tighten to 1–2 min |
| Q7 | Withdrawal reveal scope | Operator-only cleartext (v1 pragmatic) | ZK-attested "withdrew legitimately" proof to operator + compliance; cleartext as fallback only |
| Q8 | Composite-view freshness | Flat defaults: 60s stale, 5min refuse, sync-re-verify on active actions | Per-pool SLA contracts + reputation-tied breach signals |
| Q9 | Over-commitment policy | Not in v1; SpentBook lease primitive is forward-compatible substrate | Configurable max ratio per trader (default 5×), reputation influence, invalidated-interest UI feed |
| Q10 | Operator slashing + disputes | v1 ships T1 (signed) only — disputes are post-hoc reputation events | T2 bond sizing, dispute window, matcher-replay gas cap deferred to dedicated session |
| Q11 | Oracle delivery pattern | O1 (push), tier-graded by instrument class; slice-zero NAV-priced only (existing NAVOracle.sol) | O2 DECO-attested for compliance-sensitive reads; O3 Schelling for adversarial environments |
| Q12 | Aggregator failure semantics | Refund + 1 internal retry; per-pool configurable | Reputation-driven routing preference; richer retry policies |
| Q13 | EIL integration timing | Design assumes EIL mainnet first; EIL as separable retrofittable layer | §5.1 Railgun on-ramp covers L1 entry meanwhile |
Plus appendix opens (Cluster I + J) — v1 defaults:
- Search hint encryption: ECDH between trader-Persona and operator-Party + AES-GCM (v1); ZK-attested side/price-band proofs preferred long-term.
- Hint information scope: side + bucketed price-band + size-bucket + instrument. Bucket granularity per-pool configurable.
- Digest publication cadence: every 10 blocks (~2 min on cocoon-L2). Adaptive cadence deferred.
- Pool-as-searcher (Appendix B) is not in v1 — §6.5 passive auto-aggregator is the v1 shape. Reputation primitive, liquidity-policy versioning, reciprocal policies, and conflict resolution are all v2+ work.
11.2 New open questions surfaced during the walkthrough
- Asset-to-pool granularity. Whether one pool contract serves one asset, one asset class, or multiple assets is left as implementation choice. Decide alongside the slice-zero implementation plan.
- L1 advertising/trust-root layer ↔ L2 hub-coordination layer interface. The forward architecture has pool advertising on L1 and flow coordination on L2 hubs. How the two layers interface — registry sync, trust-root propagation, audit anchor commitment — needs design. Probably extends §5.4 EIL territory since EIL already requires L1 anchoring.
- Test-feed infrastructure for non-NAV instrument classes. The cocoon-side oracle mirror contract for external feeds (Chainlink Data Standard etc.) and its test harness do not yet exist. Slice-zero stays NAV-priced only. Building this is a v2 prerequisite for crypto-asset and OTC modes.
These three sit alongside the rest of the design ahead of the next planning session.
12. Out of scope (first cut)
- T3 (ZK / TEE) verification tier for the matcher (§6.1). v1 ships T1 (signed); T2 (optimistic) is a near-term upgrade; T3 stays Stage 4+.
- MEV mitigations beyond commit-reveal. No threshold-encryption, no time-locked commitments, no VDFs in v1.
- Cross-chain (non-cocoon-ecosystem) settlement on Day 1. §5.1 (Railgun on-ramp) is in scope; §5.4 (EIL external interop) is Stage 3+ surface stub only in v1.
- Operator economics + fee model. Maker/taker fees, rebates, DBC-metering of the discovery registry, aggregator routing fees — flagged; not specified.
- Privacy-preserving oracle reads (DECO-style) for compliance-sensitive feeds. Forward-compatible per §6.4 but not in v1.
- Multi-asset / portfolio / iceberg orders. All orders are single-instrument single-leg in v1.
Appendix A — Search and discovery
The body of this spec describes match mechanics — what happens once a candidate match is identified. This appendix covers the layer above: how candidate matches are discovered in the first place. The body stubs this (darkpool:digest-read in §7) but doesn't specify it.
A.1 Why search is a separate concern from matching
Match (§6.1) verifies and clears a specific candidate pair. Search produces the candidate. Splitting them is intentional:
- Search is the operator-Party's pre-trade discovery algorithm — read commitments, triage, propose pairs.
- Match is the on-chain verification — registered code, immutable, audited.
- The split lets discovery algorithms evolve (price-time priority, pro-rata, ML-driven, periodic-auction clearing) without touching consensus-critical match logic.
A.2 Intra-pool search
Within a single pool, the operator-Party reads commitments from OrderRegistry. Each commitment is Pedersen-hidden — the operator cannot see (px, qty, side) without a reveal from the trader.
For triage to be feasible, traders submit alongside each Pedersen commitment a search hint — a small encrypted-to-operator-Party metadata blob carrying minimum sufficient information:
| Hint field | Visibility | Purpose |
|---|---|---|
side (buy / sell) | Encrypted to operator-Party | Triage opposite-side pairs |
price-band (bucketed) | Encrypted to operator-Party | Narrow candidate set to nearby prices |
instrument | Public on OrderRegistry | Triage by tradeable instrument |
min-fill / max-fill | Encrypted to operator-Party | Filter incompatible size buckets |
expiry | Public on OrderRegistry | Sort by time priority |
The operator-Party uses decrypted hints (under its operator key) to identify candidate pairs. For each candidate, it requests full reveal from both sides via a UCAN attestation flow. Reveals are scoped to the matcher contract + the two traders only.
Privacy trade-off: the operator-Party sees more than pure-commitment would allow. But the operator-Party is already trusted to run the registered matcher (§6.1); the hints leak only what's needed for triage. Same trust boundary, not a wider one.
A.3 Cross-pool search
Cross-pool discovery uses the chain-coordinated services pattern ([[chain_coordinated_services]]):
- Digest publication. Each pool's operator-Party periodically publishes a Merkle-rooted digest of its open commitments to a
DiscoveryRegistry.solcontract. Leaves arePoseidon(commitment, instrument-class, jurisdiction-tags, side-hint-aggregate)— enough for a discovery agent to filter for compatibility, not enough to reconstruct any individual order. - Discovery agent. A neutral agent persona — operator-owned, third-party, or chain-coordinated service — holds a UCAN with
darkpool:digest-readscoped across opted-in venues. It scans for cross-venue match candidates (compatible instrument class, opposite-side hint, price-band overlap via public NAV bounds from NAVOracle) and produces hints, not matches. - Bilateral handshake. When a hint surfaces a candidate, the two pools' operator-Parties exchange a co-signed
cross-venue-interestenvelope over cross-persona-messaging.md (DIDComm v2 authcrypt). Each side decides whether to disclose enough order detail to confirm a real match. - Match assumption. If both agree, one pool assumes responsibility for the match — the other side migrates its order (or trader re-submits there). The match itself always happens at exactly one venue per §6.1.
No shared book. Cross-pool search is coordination between operator-Parties; orders are not forwarded or globally shared in raw form.
A.4 Impact on the existing design
| Section | Change |
|---|---|
| §3 / §8 (OrderRegistry) | Gains optional bytes searchHint field alongside Pedersen commitment. Backwards-compatible. |
| §4 (privacy model) | Add searchHint row to the visibility table — visible to operator-Party of its pool, encrypted to all others. |
| §7 (UCAN) | darkpool:match:propose implicitly includes darkpool:hint:decrypt for the operator-Party's own pool. New capability darkpool:digest-publish for operator-Party publishing pool's digest. |
| §8 (Integration touchpoints) | New contract DiscoveryRegistry.sol — chain-coordinated service for digest publication + lookup. Per-jurisdiction-cluster or global; design call. |
| §11 (Open questions) | Hint encryption scheme; how much pre-match information is the right amount to leak to operator-Party; digest publication cadence; reorganisation cost when commitments come and go between digest snapshots. |
| §10 (Implementation slices) | Would add DP-12 (search hints + intra-pool search algorithm) and DP-13 (DiscoveryRegistry + cross-pool discovery agent). |
Appendix B — Pool-as-searcher (SOR extension)
§6.5 auto-aggregator described pool A as a passive smart-order-router — when the internal cross fails, route externally. This appendix extends to active pool-as-searcher: pool A's operator-Party proactively searches across other pools for liquidity matching its book's needs and declares which kinds of liquidity it is prepared to engage with.
B.1 From passive routing to active search
The mechanical primitives are the same as §5.2 + Appendix A — digest scanning, bilateral handshakes, cross-pool DBC routing. What changes is the orchestration:
| Aspect | §6.5 passive aggregator | Pool-as-searcher (this appendix) |
|---|---|---|
| Trigger | Trader posts to pool A; internal cross fails | Pool A continuously scans foreign digests |
| Latency | Per-order, reactive | Anticipatory, proactive |
| Selectivity | Routes to any foreign pool on allow-list | Routes only to pools matching declared liquidity policy |
| Liquidity model | Trader's interest finds liquidity | Pool's interest aggregates trader liquidity |
A pool that opts into searcher mode is declaring: "I will actively look for liquidity matching my book's needs across other pools — and I will only engage with foreign liquidity that matches my declared profile."
B.2 Liquidity policy / routing manifest — registered, on-chain
Each searcher-pool registers a liquidity policy at deployment, alongside its matcher code (§6.1) and price discovery mode (§6.2). The policy is a PoolLiquidityPolicy.sol artefact (or a field on the pool's main registration contract — design call). Like the matcher and price discovery, the policy is part of the pool's Relationship identity (§2): traders consent to it at pool-join; the operator cannot change it without a new pool deployment.
Declared fields:
| Field | Purpose |
|---|---|
jurisdictions | Foreign-pool jurisdictions this pool will route to / accept routing from |
instrumentClasses | Instrument classes the pool engages with (equities, MMF, repo, crypto, etc.) |
minCounterpartyReputation | Minimum reputation threshold for foreign operator-Parties (per [[compliance_agent_layer]] trust-model-as-data pattern) |
routingDirection | inbound / outbound / both — does the pool accept foreign orders, send its own out, or both |
acceptedMechanisms | Subset of {5.2.a direct, 5.2.b lease, 5.2.c burn-mint} the pool is willing to use |
feeStructure | Per-mechanism routing fees charged (when acting as front-pool) or accepted (when serving as foreign-fill source) |
leaseWindowPreferences | Default + max lease durations the pool will acquire / honour |
The policy is the on-chain anchor that makes cross-pool discovery + negotiation trustworthy — neither operator can unilaterally claim "I accept this kind of liquidity" without the policy backing it.
B.3 Match discovery vs match execution — orchestration
Pool-as-searcher emphasizes the discovery phase as a first-class concern:
- Discovery (off-chain, operator-Party A): scan foreign digests, evaluate each against pool A's liquidity policy, identify high-priority candidates.
- Negotiation (off-chain, cross-persona-messaging): bilateral handshake with foreign operator-Party B — each side checks the other's policy + reputation against its own; agrees on terms.
- Routing (on-chain, §5.2): agreed match initiates via cross-pool mechanism. Mechanism choice constrained by intersection of both pools'
acceptedMechanisms. - Execution (on-chain, §6.1): matcher contract at one of the two pools (per handshake outcome) verifies and clears.
The liquidity policy gates step 1 (which foreign pools are candidates at all) and step 2 (which terms are negotiable).
B.4 Composition with §6.5 auto-aggregator
§6.5 and pool-as-searcher are complementary, not exclusive:
- A pool can be both passive aggregator AND active searcher — passive when responding to inbound orders, active when scanning for opportunities for its own book.
- Both compose the same primitives — digest registries, bilateral handshakes, §5.2 routing.
- The liquidity policy in B.2 gates both the passive routing decisions of §6.5 and the active search behaviour of this appendix.
B.5 UCAN capability additions
Extends §7:
| Capability | Issuer | Audience | nb caveats |
|---|---|---|---|
darkpool:liquidity:declare | Operator-Persona root | Self (pool registration contract) | policy-hash, immutable post-deploy |
darkpool:liquidity:discover | Operator-Party | Discovery sub-agent | digest-read scope, instrument-filter, jurisdiction-filter |
darkpool:liquidity:negotiate | Operator-Party A | Operator-Party B | per-bilateral-relationship, policy-cross-check fact |
B.6 Open design questions (beyond §11)
- Reputation primitive — pool-as-searcher relies on
minCounterpartyReputation. What's the reputation primitive? [[compliance_agent_layer]] establishes trust-model-as-first-class-data; does this generalize to operator-Party reputation, or does it need a dedicated mechanism (e.g. on-chain rolling fill-success ledger)? - Liquidity-policy versioning — pools are deployed with a fixed policy; if the operator wants to update, they redeploy. What's the migration path for traders already in the old pool? Forced re-consent? Grandfather window?
- Reciprocal policies — can pool A match against pool B only if certain reciprocity conditions hold ("I'll send liquidity to you only if you accept my fee model too")? Policy-on-policy negotiation is complex; v1 may stay with simple "both sides accept" rather than negotiated reciprocity.
- Cross-policy conflict resolution — when two pools' policies are partially compatible (e.g. overlap on instruments but not mechanisms), is fallback to the intersection automatic, or does it require explicit handshake renegotiation?
B.7 Implementation slices (informational, additions to §10)
- DP-14: PoolLiquidityPolicy registration contract + policy declaration UI/UX.
- DP-15: Liquidity-policy enforcement in operator-Party off-chain code — discovery filtering, negotiation evaluator.
- DP-16: Pool-as-searcher orchestration logic — proactive digest scanning, candidate evaluation, bilateral handshake protocol.
- DP-17: Reputation primitive integration (gated on reputation primitive design).
Appendix coverage summary
- Appendix A specifies the search layer — how matches are discovered, both intra-pool (search hints + operator-Party triage) and cross-pool (DiscoveryRegistry + digests + bilateral handshake). Lists impact on §3 / §4 / §7 / §8 / §10 / §11.
- Appendix B specifies pool-as-searcher SOR — active cross-pool liquidity search gated by a registered, on-chain liquidity policy. Pool declares jurisdictions, instruments, mechanisms, fees, lease preferences it will engage on; the chain-coordinated services marketplace lets other pools discover these declarations. Composes with §6.5 passive aggregator pattern.
Both appendices are forward-looking like the body — implementation slices DP-12 through DP-17 sketch the additional work, gated on resolving the open questions in their respective sections.