From 4cce5621b4e9b0eca8ae501f7957209a5802a3f9 Mon Sep 17 00:00:00 2001 From: srikanth-bitdynamics <259878899+srikanth-bitdynamics@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:22:50 +0530 Subject: [PATCH 1/2] docs: plainer tone in the builder and how-to guides Drop signpost prefixes and inflated phrasing, sentence-case the headings, replace em dashes, and remove mechanical bold. No code, command, link, or technical claim changed. --- docs/guides/add-a-trading-pair.md | 8 ++-- docs/guides/add-lp-or-instrument.md | 10 ++--- docs/guides/builder-guide.md | 57 +++++++++++++++-------------- 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/docs/guides/add-a-trading-pair.md b/docs/guides/add-a-trading-pair.md index 7aaa1b5e..ea967cd1 100644 --- a/docs/guides/add-a-trading-pair.md +++ b/docs/guides/add-a-trading-pair.md @@ -1,12 +1,12 @@ -# Guide: Adding a new trading pair +# Adding a new trading pair -End-to-end recipe for listing a new pair (say `ETH/USDT`) on a running +Recipe for listing a new pair (say `ETH/USDT`) on a running Canton DEX deployment. Assumes the operator backend is already wired to a participant and the base + quote assets already have registries that produce Token Standard V2 holdings, allocation factories, and settlement factories. -If the base or quote asset does **not** yet have a V2-compatible registry, +If the base or quote asset does not yet have a V2-compatible registry, do that first: see [`add-lp-or-instrument.md`](add-lp-or-instrument.md). @@ -139,7 +139,7 @@ curl -s 'http://localhost:8080/v1/swaps?pair=ETH/USDT&limit=10' | Pool created but `/v1/pools` is empty | Pool is operator + lpRegistrar observed only. The backend observes as `operator`, but if you used a different signing party the read won't see it. | | Trades fail even though `DexPair` exists | The pair metadata is only a venue listing. The relevant registries still need to publish V2 holdings, allocation factories, settlement factories, and any choice context required for the instruments. | -## When to NOT do this +## When not to do this - If you're listing many pairs programmatically: write a one-shot script that builds all the commands in one batch, not curl loops. diff --git a/docs/guides/add-lp-or-instrument.md b/docs/guides/add-lp-or-instrument.md index e7067cfe..946953f4 100644 --- a/docs/guides/add-lp-or-instrument.md +++ b/docs/guides/add-lp-or-instrument.md @@ -1,8 +1,8 @@ -# Guide: Issuing a new LP token or lifecycle-rich instrument +# Issuing a new LP token or lifecycle-rich instrument How to mint a new asset on Canton DEX using Token Standard V2 (CIP-0112) -surfaces. This covers both the simple case (fungible LP token) and examples of -lifecycle-rich assets (vested, dividend-paying, restricted) implemented through +surfaces. It covers the simple case (a fungible LP token) and lifecycle-rich +assets (vested, dividend-paying, restricted) implemented through registry-specific contracts. ## What "lifecycle-rich" means here @@ -23,8 +23,8 @@ that can encode: Those are reference-registry features, not Token Standard requirements. A different registry may publish different metadata and choice context while still implementing the same V2 `Holding`, `AllocationFactory`, and -`SettlementFactory` interfaces. A lifecycle-rich instrument in this repo is the -composition of a per-instrument config plus optional issuer-signed +`SettlementFactory` interfaces. A lifecycle-rich instrument in this repo +combines a per-instrument config with optional issuer-signed `Credential` contracts that the recipient must present at mint time. ## Case A. Vanilla LP token (the common case) diff --git a/docs/guides/builder-guide.md b/docs/guides/builder-guide.md index 0d1f7d62..47c1b590 100644 --- a/docs/guides/builder-guide.md +++ b/docs/guides/builder-guide.md @@ -1,6 +1,6 @@ -# Builder Guide +# Builder guide -For engineers who want to pick this reference up, read it, and extend it. Read +For engineers who want to read and extend this reference. Read after [Getting Started](../getting-started.md) (which gets the stack running) and the [Overview](../concepts/overview.md) + [Architecture](../concepts/architecture.md) (which explain the design). @@ -10,7 +10,7 @@ and the [Overview](../concepts/overview.md) + [Architecture](../concepts/archite A runnable Canton DEX that: - uses Token Standard V2 (CIP-0112) for every asset: base, quote, and LP are - represented by contracts implementing `V2.Holding`. + contracts implementing `V2.Holding`. - uses iterated allocations from Token Standard V2 (CIP-0112), now merged into `canton-network/splice` `main`, so pool reserves and resting orders can be adjusted in place without re-funding round trips. @@ -20,31 +20,32 @@ A runnable Canton DEX that: ## Out of scope -Short version: no central limit-order-book matcher, no production order routing, -no oracle integration, no custody, and no compliance/KYC layer. Those belong in -forks or deployment-specific services. +No central limit-order-book matcher, no production order routing, no oracle +integration, no custody, and no compliance/KYC layer. Those belong in forks or +deployment-specific services. -## A guided tour of the workflow families +## Workflow families The four workflow families below are the ones the Daml test suite exercises. Each corresponds to tests under `trading-tests/CantonDex/Tests/`. Read them in this order to understand the venue end-to-end. ### A. Pair / instrument listing -- `trading/CantonDex/Dex/DexPair.daml` — listing record: base + quote instrument - id, fee model, trading mode (`OrderBook`, `Pool`, `Both`), and an `active` flag. -- `trading/CantonDex/Instrument/InstrumentConfiguration.daml` — the reference +- `trading/CantonDex/Dex/DexPair.daml`: listing record with base + quote + instrument id, fee model, trading mode (`OrderBook`, `Pool`, `Both`), and an + `active` flag. +- `trading/CantonDex/Instrument/InstrumentConfiguration.daml`: the reference registry's instrument config (holder/issuer credential requirements, optional - ISIN/CUSIP). This is **not** a Token Standard V2 template; other registries can + ISIN/CUSIP). This is not a Token Standard V2 template; other registries can use different config contracts. - Test: `InstrumentTests.daml::testInstrumentConfigCreate`. ### B. Matched-trade OTC / RFQ settlement (TradingAppV2 pattern) -- `trading/CantonDex/Dex/MatchedTrade.daml` — a V2 adaptation of TradingAppV2. +- `trading/CantonDex/Dex/MatchedTrade.daml`: a V2 adaptation of TradingAppV2. `MatchedTrade_RequestAllocations` creates one request per authorizer; `MatchedTrade_Settle` groups by admin and calls `SettlementFactory_SettleBatch`; `MatchedTrade_Cancel` mirrors the cleanup. -- `trading/CantonDex/Dex/Rfq.daml` + `PolicyReceipt.daml` — the bilateral +- `trading/CantonDex/Dex/Rfq.daml` + `PolicyReceipt.daml` hold the bilateral block-trade flow: trader RFQ, dealer quotes, joint `Rfq_Accept` emitting a `MatchedTrade` that carries an operator-signed `PolicyReceipt` folded into `SettlementInfo.meta`. @@ -55,29 +56,29 @@ this order to understand the venue end-to-end. `vendor/splice/token-standard/examples/splice-token-test-trading-app-v2/`. ### C. Resting orders backed by a V2 allocation -- `trading/CantonDex/Dex/OrderFundingRequest.daml` — trader-signed intent. -- `trading/CantonDex/Dex/Order.daml` — the operator-bound `Order` plus its +- `trading/CantonDex/Dex/OrderFundingRequest.daml`: trader-signed intent. +- `trading/CantonDex/Dex/Order.daml`: the operator-bound `Order` plus its `OrderAllocationRequest`. Funding requires the trader to author the allocation via `AllocationFactory_Allocate`, so the trader's own authority moves the - holding — the operator cannot move trader holdings on its own. -- `trading/CantonDex/Dex/OrderMatchExecution.daml` — the prefunded-trade pattern: + holding. The operator cannot move trader holdings on its own. +- `trading/CantonDex/Dex/OrderMatchExecution.daml`: the prefunded-trade pattern: concrete match legs are supplied as `FinalizedAllocation.extraTransferLegSides` at batch-settlement time; next-iteration cids roll forward onto partial fills. - Tests: `EndToEndTests.daml::testOrderFundingFlow`, `testFinalizedAllocationFundingConservation`. ### D. Constant-product pool with committed allocations -- `trading/CantonDex/Dex/Pool.daml` + `PoolState.daml` + `PoolSlice.daml` — the - split pool: immutable config, the hot reserves/supply/status **state**, and one - committed allocation per **slice** (each slice is its own contract, passed by +- `trading/CantonDex/Dex/Pool.daml` + `PoolState.daml` + `PoolSlice.daml` make up + the split pool: immutable config, the hot reserves/supply/status state, and one + committed allocation per slice (each slice is its own contract, passed by cid). -- `trading/CantonDex/Dex/PoolRules.daml` — the swap-side choices: +- `trading/CantonDex/Dex/PoolRules.daml` holds the swap-side choices: `PoolRules_RequestSwap`, `PoolRules_Swap`, `PoolRules_Pause`, `PoolRules_Resume`. - `trading/CantonDex/Dex/PoolLiquidityRules.daml` + `LiquidityAllocationRequest.daml` - — the delivery-versus-payment add/remove path: `_RequestAddLiquidity` / + hold the delivery-versus-payment add/remove path: `_RequestAddLiquidity` / `_SettleAddLiquidity` and `_RequestRemoveLiquidity` / `_SettleRemoveLiquidity`, co-controlled by `operator` + `lpRegistrar`. -- `trading/CantonDex/Lp/Policy.daml` + `Instrument.daml` — the LP-token component. +- `trading/CantonDex/Lp/Policy.daml` + `Instrument.daml`: the LP-token component. `LPTokenPolicy` is owned by `lpRegistrar`, keyed by a `V2.InstrumentId`, and knows nothing about pools or orders. - Tests: `EndToEndTests.daml::testPoolFullLifecycle`, `testPoolSwapEndToEnd`; @@ -151,7 +152,7 @@ fork can rewrite the matcher without touching any Daml template. The dApp does not sign as the trader. Trader-authority writes (place order, add/remove-liquidity allocations via `AllocationFactory_Allocate`, swap allocation -creation) go through the connected wallet over the **CIP-0103** dApp standard +creation) go through the connected wallet over the CIP-0103 dApp standard (prepare → sign → execute). The operator backend produces unsigned command trees; the wallet signs and submits. RFQ accepts are the one exception in this reference: trader + operator co-sign via `POST /v1/rfq/accept` (a production @@ -162,10 +163,10 @@ from the backend's indexer cache. Keep trader-authority writes in the wallet pat the operator backend should only orchestrate and settle flows it is authorized to submit. -**Why single-command flows.** CIP-0103 interactive submission prepares **one -top-level command** per transaction, and the Splice Amulet Wallet UI only +**Why single-command flows.** CIP-0103 interactive submission prepares one +top-level command per transaction, and the Splice Amulet Wallet UI only batches multiple requested allocations for Amulet allocations. The DEX -therefore uses the token standard's batching utility — +therefore uses the token standard's batching utility, `Splice.Util.Token.Wallet.BatchingUtilityV2` (Splice 0.6.11, vendored under `vendor/splice/daml/splice-util-token-standard-wallet/` and built by the vendored-DAR script): the wallet `createAndExercise`s `ExecuteBatch`, which @@ -199,7 +200,7 @@ confusion in the workflows. ## Upgrade discipline Keep the reference templates as small as possible; do not carry compatibility -choices "just in case" — they add noise for readers. If an adopter deploys a +choices "just in case". They add noise for readers. If an adopter deploys a package and needs to preserve Daml smart-upgrade lineage, follow the participant's upload-check rules: new fields `Optional` and at the end of the record, choices kept (not removed), input/result field types stable, no field reordering. If you From 50b25ed777e34a4b78ef057ffbcf504cf7f30c46 Mon Sep 17 00:00:00 2001 From: srikanth-bitdynamics <259878899+srikanth-bitdynamics@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:41:17 +0530 Subject: [PATCH 2/2] docs: plainer tone across the concepts, guides, and reference pages Extends the same tone pass to the rest of the docs tree: drop signpost headings and preambles, thin the repeated antithesis phrasing, remove mechanical bold lead-ins and emphatic italics, and replace em dashes. Cross-linked headings are left verbatim so anchors keep resolving. No code block, command, link, number, or technical claim changed. --- docs/README.md | 12 +++--- docs/concepts/architecture.md | 55 ++++++++++++-------------- docs/concepts/glossary.md | 8 ++-- docs/concepts/liquidity-and-custody.md | 19 +++++---- docs/concepts/lp-tokens.md | 31 +++++++-------- docs/concepts/non-goals.md | 31 +++++++-------- docs/concepts/overview.md | 16 ++++---- docs/concepts/pricing.md | 22 +++++------ docs/concepts/workflows.md | 44 ++++++++++----------- docs/getting-started.md | 10 ++--- docs/guides/choice-context.md | 6 +-- docs/guides/deployment.md | 14 +++---- docs/guides/operator-guide.md | 4 +- docs/guides/operator-runbook.md | 22 +++++------ docs/guides/registry-integration.md | 22 +++++------ docs/guides/run-on-testnet.md | 20 +++++----- docs/guides/using-the-dapp.md | 2 +- docs/reference/allocation-surface.md | 18 ++++----- docs/reference/ecosystem-feedback.md | 24 +++++------ docs/reference/http-api.md | 42 ++++++++++---------- docs/reference/testing.md | 6 +-- 21 files changed, 211 insertions(+), 217 deletions(-) diff --git a/docs/README.md b/docs/README.md index 9f393ff5..ea1bc8cf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,10 +2,10 @@ A full-stack, **Token Standard V2 (CIP-0112)** reference DEX for the Canton Network: Daml contracts, an operator backend, a React dApp with a CIP-0103 -wallet boundary, tests, and operator runbooks — covering RFQs, prefunded +wallet boundary, tests, and operator runbooks, covering RFQs, prefunded orders, constant-product pools, swaps, and LP tokens. -New here? **[Start with Getting Started](getting-started.md)** — it takes you +New here? **[Start with Getting Started](getting-started.md)**: it takes you from a clone to the full stack running locally (no Canton participant needed). For the ideas behind the design, read the **[Overview](concepts/overview.md)**. @@ -35,7 +35,7 @@ For the ideas behind the design, read the **[Overview](concepts/overview.md)**. ## All documentation -The docs follow the [Diátaxis](https://diataxis.fr/) model — separating +The docs follow the [Diátaxis](https://diataxis.fr/) model, separating learning (tutorial), tasks (how-to guides), understanding (concepts), and lookup (reference). @@ -84,11 +84,11 @@ lookup (reference). ## Also in the repo - **[Getting Started](getting-started.md)** doubles as the local test-suite reference (Daml, backend, and dApp commands with expected counts). -- The [Builder Guide](guides/builder-guide.md) includes a **guided tour of the - four workflow families** — pair listing, matched-trade/RFQ, prefunded orders, +- The [Builder Guide](guides/builder-guide.md) walks through the four workflow + families — pair listing, matched-trade/RFQ, prefunded orders, and pool/swap/LP — with file and test pointers. - [`examples/stable-pool/`](../examples/stable-pool/) is a separate Daml - project that consumes the DEX DAR — a reuse proof point. + project that consumes the DEX DAR (a reuse proof point). ## Governance [Contributing](../CONTRIBUTING.md) · [Code of Conduct](../CODE_OF_CONDUCT.md) diff --git a/docs/concepts/architecture.md b/docs/concepts/architecture.md index 08666819..ac5d19df 100644 --- a/docs/concepts/architecture.md +++ b/docs/concepts/architecture.md @@ -4,16 +4,16 @@ Canton DEX is a token-standard-native reference DEX for Canton. -It is intentionally not a generic settlement engine. What the reference leaves -out on purpose, and why, is collected in [Non-goals](non-goals.md). The goal is -to show builders how to build a real exchange directly on top of: +It is intentionally not a generic settlement engine. [Non-goals](non-goals.md) +collects what the reference leaves out on purpose, and why. The goal is to +show builders how to build a real exchange directly on top of: - Token Standard V2 allocations and batch settlement - registry-backed holdings whose registries implement the V2 holding, allocation, and settlement APIs - Canton privacy, routing, and atomic transaction semantics -## Design Inputs +## Design inputs The architecture is based on three concrete upstream inputs. @@ -63,10 +63,10 @@ extensions, now merged into `canton-network/splice` `main`: - `FinalizedAllocation.extraTransferLegSides` - settle results that return next-iteration allocation state -Those changes are what make it possible to use allocations not only for trade +Those changes make it possible to use allocations not only for trade reservation but also for long-lived pool inventory. -## Core Decisions +## Core decisions 1. Token standard first - the DEX should use V2 allocation primitives directly, not hide them behind @@ -86,7 +86,7 @@ reservation but also for long-lived pool inventory. `Pool` and `MatchedTrade` each carry a single `admin : Party`, and the standard's `TransferLeg.instrumentId` is bare `Text`, so a leg cannot name its own admin. Pairing instruments from two different registries is - therefore not expressible today — see + therefore not expressible today. See [Registry Integration](../guides/registry-integration.md#what-the-dex-does-not-assume) 5. Instrument lifecycle stays outside DEX logic @@ -110,7 +110,7 @@ reservation but also for long-lived pool inventory. - therefore every permitted use of those funds should be validated by Daml contract state and choice logic, not only by an off-ledger service -## System Model +## System model ```text ┌──────────────────────────────────────────────────────────────┐ @@ -142,9 +142,9 @@ reservation but also for long-lived pool inventory. └──────────────────────────────────────────────────────────────┘ ``` -## Workflow-First Reading +## Workflow-first reading -The best way to read this architecture is through the workflows: +Read this architecture through its workflows: - pair listing - OTC / RFQ trade settlement @@ -157,7 +157,7 @@ The best way to read this architecture is through the workflows: Those workflows are described in [workflows.md](./workflows.md). The contracts should be shaped around those state transitions, not the other way around. -## On-Ledger Model +## On-ledger model ### Instrument layer @@ -210,9 +210,8 @@ A practical model is: - each swap adjusts those allocations, settles them, and rolls forward the next-iteration allocations -This is the critical architectural move: pool inventory -should be allocation-native, not a custom internal balance model with a -different settlement bridge behind it. +Pool inventory should be allocation-native, not a custom internal balance model +with a different settlement bridge behind it. ### Executor-control constraint @@ -234,7 +233,7 @@ The intended model is: - the off-chain operator proposes actions, but the ledger-visible contracts validate the quantity, pair, expiry, side, and reserve references being used -> **Further reading — decentralizing the operator.** This validation logic can +> **Further reading: decentralizing the operator.** This validation logic can > itself be decentralized. See the > [BitSafe decentralization manager proposal](https://github.com/canton-foundation/canton-dev-fund/blob/main/proposals/2026-05-BitSafe-decentralization-manager.md) > for decentralizing the execution of validation logic, the @@ -278,8 +277,8 @@ side` is protected at three levels: `PoolRules` / `PoolLiquidityRules` choice that rewrites `reserves` asserts inside the choice that its reserve delta equals the net slice-amount change the same choice performs (created slice amounts minus consumed/drained - slice amounts). These are assertions on the choice's own arithmetic — cheap - and contention-free — so a future code change cannot silently let reserves + slice amounts). These are assertions on the choice's own arithmetic (cheap + and contention-free), so a future code change cannot silently let reserves and slices drift apart. - **Global equality, auditable on-ledger on demand.** The nonconsuming `PoolRules_ReconcileState` choice takes the `PoolState` and the full list @@ -307,7 +306,7 @@ Expected characteristics: - the LP instrument definition should explain redemption policy and pool identity -## Token Standard Usage +## Token Standard usage ### For OTC and RFQ @@ -353,7 +352,7 @@ Without those semantics, a pool-backed design would drift back toward custom escrow or off-ledger reserve tracking, which is exactly what this repo is trying to avoid. -## Admin and Pairing Model +## Admin and pairing model The DEX should support arbitrary trading pairs of `InstrumentId`, but allocations still need to respect token-standard admin boundaries. @@ -365,9 +364,7 @@ That implies: - pool state should store active allocation references in a way that makes admin partitioning explicit -This is an important design constraint, not an implementation detail. - -## Rich Asset Lifecycle Model +## Rich asset lifecycle model The standard holding model remains intentionally small: @@ -401,10 +398,10 @@ In other words, a lifecycle-aware registry may take one instrument version in and hand back a new version with the lifecycle side effects applied. The DEX does not assume this is available for every registry. -The important point is that the traded asset remains a standard holding even +The traded asset remains a standard holding even when its lifecycle is rich. -## Off-Chain Services +## Off-chain services Off-chain services are still necessary, but their job is narrower than in older generic-settlement architectures. @@ -420,13 +417,13 @@ They should focus on: They should not become the main abstraction for moving value around. The token standard remains the settlement substrate. -## Dependency Boundary +## Dependency boundary The reference architecture has a deliberate split: - OTC and RFQ flows follow the `TradingAppV2` allocation-request and per-admin `SettlementFactory_SettleBatch` pattern, against V2 allocations - only — this repo declares no V1 allocation dependency + only (this repo declares no V1 allocation dependency) - pool-backed liquidity requires the V2 allocation extensions used by this repo: committed allocations, iterated settlement, extra leg sides, and next-iteration allocation results @@ -434,7 +431,7 @@ The reference architecture has a deliberate split: If the upstream API shape changes before landing, this repo should preserve the same design intent even if field names or result structures move. -## Component Boundary +## Component boundary The current implementation separates concerns by module and template: @@ -443,7 +440,7 @@ The current implementation separates concerns by module and template: workflows. - `CantonDex.Registry.V2` is a reference registry used for tests and demos. -The DAR implements upstream Token Standard V2 interfaces, but it does **not** +The DAR implements upstream Token Standard V2 interfaces, but it does not define custom Daml interfaces that decouple the LP-token component from the DEX venue component. The shared boundary today is the Token Standard V2 holding/allocation/settlement surface plus explicit template references inside @@ -457,7 +454,7 @@ pair admission yet. That keeps the reference small, while leaving room for forks to add governance, multi-operator approval, or a decentralized rules layer. -## Repository Shape +## Repository shape ```text canton-dex/ diff --git a/docs/concepts/glossary.md b/docs/concepts/glossary.md index 424cc9a4..5ef3b75f 100644 --- a/docs/concepts/glossary.md +++ b/docs/concepts/glossary.md @@ -7,7 +7,7 @@ that explains them in depth. ### Allocation A Token Standard V2 contract that **locks** a holder's [holding](#holding) for a specific settlement, so it can be settled atomically later. The DEX never moves -trader assets directly — it moves them by having the trader create allocations +trader assets directly. It moves them by having the trader create allocations and then settling a batch. See [Allocation Surface](../reference/allocation-surface.md). ### AllocationFactory / `AllocationFactory_Allocate` @@ -27,16 +27,16 @@ when its factory choices are exercised. The operator backend fetches these and attaches them to each submission. See [Choice Context](../guides/choice-context.md). ### CIP-0056 -The **Canton Network Token Standard** — the base standard (holdings, transfers, +The **Canton Network Token Standard**: the base standard (holdings, transfers, metadata) that CIP-0112 revises. ### CIP-0103 -The **dApp Standard** — the wallet interaction standard used for +The **dApp Standard**: the wallet interaction standard used for [prepare/sign/execute](#prepare--sign--execute) interactive submission. The dApp hands trader-authority commands to a wallet over CIP-0103. ### CIP-0112 -The **Canton Network Token Standard V2** — the privacy / performance / +The **Canton Network Token Standard V2**: the privacy / performance / traditional-accounting revision of CIP-0056, adding the allocation + settlement surface this DEX is built on. Often written "Token Standard V2" or "TSv2". diff --git a/docs/concepts/liquidity-and-custody.md b/docs/concepts/liquidity-and-custody.md index dcfec087..885e6a17 100644 --- a/docs/concepts/liquidity-and-custody.md +++ b/docs/concepts/liquidity-and-custody.md @@ -1,4 +1,4 @@ -# LP Liquidity Custody Model +# LP liquidity custody model This document explains how the reference pool represents LP liquidity and how assets cross the pool boundary during add- and remove-liquidity workflows. @@ -10,7 +10,7 @@ assets cross the pool boundary during add- and remove-liquidity workflows. - Between operations, pool reserves are operator-authored committed `PoolSlice` allocations. Slices are **locality units, not LP entitlement units**: they exist so add/remove/ - swap touch only the slices they source, not so a slice "belongs to" an + swap touch only the slices they source; a slice does not "belong to" an LP. - An LP's entitlement is **pro-rata over the aggregate reserves** (`lpHeld / totalLpSupply × reserves`), sourced from selected slices. @@ -35,7 +35,7 @@ if `pool.admin == pool.lpRegistrar`): (`LP → operator` base+quote) settle into **operator-authored receiver allocations**. `nextIterationFunding = {instrument: amount}` is applied on the `FinalizedAllocation` **at the settle step** (not pre-funded at - allocate time — that would trip the coverage check in + allocate time, which would trip the coverage check in `Registry.V2`), matching the pool's slice roll-forward model. The returned next-iteration allocation cids become the new operator-authored `PoolSlice`s. @@ -44,7 +44,7 @@ if `pool.admin == pool.lpRegistrar`): receives freshly-minted LP-token holdings. The settle choice then exercises `LPTokenPolicy_RecordMint` and rewrites -`PoolState` **once** with the new reserves + `totalLpSupply`. +`PoolState` once with the new reserves + `totalLpSupply`. ## Remove (DvP at the boundary, symmetric to Swap) @@ -60,9 +60,8 @@ two-admin settle in one transaction: `holder → burnAccount lpRegistrar`, against the holder's burn-sender allocation. -Then `LPTokenPolicy_RecordBurn` + a single `PoolState` rewrite. The key -correctness point is that funds reach the **holder**, not the operator's -pool account. +Then `LPTokenPolicy_RecordBurn` + a single `PoolState` rewrite. Funds reach +the holder, not the operator's pool account. ## Choreography & authority @@ -74,7 +73,7 @@ pool account. (`pool.lpRegistrar`, sender-side). - The settle choices live on a **co-controlled `PoolLiquidityRules`** contract (`{ operator, lpRegistrar }`, `signatory operator, - lpRegistrar`, choices `controller operator, lpRegistrar`) — not on the + lpRegistrar`, choices `controller operator, lpRegistrar`), not on the operator-only `PoolRules`, which has no `lpRegistrar` visibility. This gives the settle the authority to drive both the operator-signed `PoolState`/`PoolSlice` writes and the lpRegistrar-controlled @@ -95,7 +94,7 @@ pool account. `PoolState` and aborts a stale request. -## What does NOT change +## What does not change - Pricing / share math (`x*y=k` on aggregate reserves; pro-rata shares). - `PoolSlice` shape (still operator-authored, no `owner`). @@ -113,7 +112,7 @@ crediting, and the allocate factory. `RealRegistry` already supports these semantics. One more registry-dependent bound: pool slices are **long-lived committed -allocations**, and some registries cap allocation lifetime — Amulet enforces +allocations**, and some registries cap allocation lifetime: Amulet enforces `tokenStandardMaxTTL` (default **90 days**) from Splice 0.6.11. Against such a registry the operator must roll slices into fresh allocations before the cap expires; see diff --git a/docs/concepts/lp-tokens.md b/docs/concepts/lp-tokens.md index a26a8e7d..9e765290 100644 --- a/docs/concepts/lp-tokens.md +++ b/docs/concepts/lp-tokens.md @@ -1,10 +1,10 @@ -# LP Token Versioning Strategy +# LP token versioning strategy ## Decision **Canton DEX LP tokens are unversioned.** The LP token `instrumentId` for a given pool is derived from the pool's pair (e.g., `BTC-USDC-LP`) and does -**not** include a version suffix or per-iteration discriminator. +not include a version suffix or per-iteration discriminator. ## Rationale @@ -19,11 +19,11 @@ The reference DEX prioritises: 3. **UX simplicity** — the wallet shows one LP balance per pool, not a timeline of versioned slivers. -## Implications for the Pool Contract +## Implications for the pool contract The pool contract template carries `lpInstrumentId : V2.InstrumentId` as a static field fixed at pool creation. It MUST NOT be derived from the pool's -contract id, the current settlement iteration, or the pool's status — those +contract id, the current settlement iteration, or the pool's status. Those all change over a pool's life and would re-version the LP behind users' backs. Concretely: @@ -34,27 +34,27 @@ Concretely: - `PoolLiquidityRules_SettleRemoveLiquidity` burns `lpInstrumentId` tokens. The pool does not care which iteration created them. - The `LPTokenPolicy` registrar must accept any holding of `lpInstrumentId` - for burn — there is no version check. + for burn: there is no version check. -## What about settlement iterations? +## Settlement iterations The V2 allocation API introduces `nextIterationAllocationCid` so the pool can hand pool- side allocations forward across settlement batches without users re-allocating. -This is an **allocation lifecycle** concern, not an instrument-versioning +This is an allocation lifecycle concern, not an instrument-versioning concern. The LP holdings users hold are unaffected by allocation iteration. -## What about fee/rule changes? +## Fee and rule changes If the admin changes the pool's fee model, the LP instrument stays the same. Existing LP holders share in future fees at the new rate. If the change is non-trivial and existing LPs should be honoured at old rates, the operator -must spin up a **new pool** with a **new pair of `lpInstrumentId`** — that +must spin up a new pool with a new pair of `lpInstrumentId`. That is a deliberate migration, not an incidental rebase. -## What about emergency upgrades? +## Emergency upgrades If the LP token policy itself needs to be replaced (security fix, choice -signature change), the upgrade path is a Canton package upgrade — same +signature change), the upgrade path is a Canton package upgrade: same `instrumentId`, new package hash. Holders are unaffected. ## Why one LP instrument per pool @@ -69,7 +69,7 @@ signature change), the upgrade path is a Canton package upgrade — same ## Why this is separate from registry primitive versioning Some lifecycle-aware registry-side instruments may version when their issuer -makes a breaking change — e.g., a coupon payment at epoch N that needs to be +makes a breaking change, e.g., a coupon payment at epoch N that needs to be paid into v_N holdings before they roll forward to v_{N+1}. That is a fundamentally different shape of problem from the LP token: @@ -81,17 +81,16 @@ fundamentally different shape of problem from the LP token: - **LP tokens** have the pool's LP registrar/policy as the issuer. The reference LP token has no off-ledger event to crystallize against a passive holder's balance. So upgrade-on-use plus force-upgrade does not apply in this - reference — the LP token simply stays at one stable `instrumentId` for the + reference: the LP token stays at one stable `instrumentId` for the life of the pool. That means a wallet, lending market, or vault that consumes registry primitives needs to handle upgrade-on-use behavior; the same wallet consuming LP tokens does not. The two surfaces look fungible-equivalent -externally but have different upgrade semantics under the hood, and -that's by design. +externally but have different upgrade semantics. See [CIP-0112](https://github.com/global-synchronizer-foundation/cips/blob/main/cip-0112/cip-0112.md) -for the canonical V1→V2 compatibility framing — V1 instruments continue +for the canonical V1→V2 compatibility framing: V1 instruments continue to exist alongside V2 implementations rather than being bulk-migrated. ## See also diff --git a/docs/concepts/non-goals.md b/docs/concepts/non-goals.md index 5a9ca542..dcdd4bb7 100644 --- a/docs/concepts/non-goals.md +++ b/docs/concepts/non-goals.md @@ -1,21 +1,21 @@ -# What this reference does not include, and why +# What this reference does not include This is a reference implementation, not a product. Several things a production -DEX would carry are left out on purpose — either because they are an operator's +DEX would carry are left out on purpose, either because they are an operator's choice rather than a settlement-pattern concern, or because including them would obscure the one thing the reference exists to show: that spot trading on Canton can be built entirely on Token Standard V2 allocations, with the ledger enforcing custody and conservation. -Each exclusion below is a decision, not an omission. Where a boundary is visible +Each item below is a deliberate choice. Where a boundary is visible in the code, the module is named. ## Not a generic settlement engine The Daml models a DEX: pools, orders, RFQ. It is not a configurable settlement framework that a caller parameterises into arbitrary flows. The settlement -*pattern* — allocate, then settle a batch atomically through the registry's -`SettlementFactory` — is meant to be read and reused, but the templates encode +pattern (allocate, then settle a batch atomically through the registry's +`SettlementFactory`) is meant to be read and reused, but the templates encode the DEX's own rules (constant-product pricing, price-time order priority, best-execution RFQ ranking) rather than exposing a general engine. See [architecture.md](architecture.md). @@ -27,10 +27,9 @@ instruments (`trading/CantonDex/Dex/Order.daml`, `Pool.daml`). Under Token Standard V2 an instrument is identified by `(admin, id)`, so this reference cannot list a pair whose two assets come from different registries — for example Canton Coin quoted against a third-party stablecoin. The settlement layer itself does -*not* require this (a single Daml transaction can settle one batch per admin, and +not require this (a single Daml transaction can settle one batch per admin, and this repository already does so on the LP path); the limitation is in the app-layer -templates. Lifting it is a scoped design change, written up separately, not a gap -in the pattern. +templates. Lifting it is a scoped design change, written up separately. ## Not a production matching engine @@ -40,13 +39,13 @@ matching loop. It clears crossing orders best-price-then-time, settles each matc atomically, and applies simple self-trade prevention (a party's own orders are not paired). It does not implement pro-rata allocation, iceberg or hidden orders, matching priority tiers, or a continuous auction. A production venue would layer -those on; the reference shows that the *settlement* of a match is atomic and -allocation-backed, which is the part specific to Canton. +those on; the reference shows that the settlement of a match is atomic and +allocation-backed, the part specific to Canton. ## A minimal instrument model The vendored standard holding model is kept intentionally small. The reference -issues exactly one lifecycle-bearing instrument — the LP token — as a +issues exactly one lifecycle-bearing instrument, the LP token, as a token-standard instrument with its own registrar and DvP mint/burn (`trading/CantonDex/Lp/`). Token Standard V2 does not mandate `InstrumentConfiguration` or a rich lifecycle, and the reference does not assume @@ -59,7 +58,7 @@ but the reference itself stays at the minimum the DEX needs. `CantonDex.Registry.V2` is a self-contained reference registry so the DEX can be run end to end without depending on an external one. It is not the settlement mechanism. The dApp and operator reach any conforming TSv2 registry through its -factories, choice context and disclosure — the reference does not assume its own +factories, choice context and disclosure. The reference does not assume its own registry is present, and does not require every registry to expose the same conveniences (`architecture.md`, "Dependency Boundary"). On the public testnet the pair's assets happen to be issued by this registry; the flows are written to @@ -78,14 +77,14 @@ The hosted onboarding routes and their caps are documented in **Current deployment status.** On the public testnet at `testnet-dex.bitdynamics.cc`, every tester is onboarded as a hosted party on the -operator's (BitDynamics) validator, and every traded asset — `dBTC`, `dUSD`, and -the pool's LP token — is issued locally by the deployment's own Token Standard V2 +operator's (BitDynamics) validator, and every traded asset (`dBTC`, `dUSD`, and +the pool's LP token) is issued locally by the deployment's own Token Standard V2 registry. This is a bridge: external participants cannot yet bring their own Token Standard V2 party and assets because the general-purpose validator and wallet tooling (DA Utilities) does not yet support Token Standard V2. When that support ships, users connect their own participant's party and trade their own V2 assets through PartyLayer or the dapp-sdk, and the hosted onboarding is retired. -The code path for that is already the intended one — the hosted relay is the only +The code path for that is already the intended one. The hosted relay is the only piece specific to this interim. ## Operational hardening is out of scope @@ -101,7 +100,7 @@ rather than baked into the reference. The operator backend and indexer are a working reference, not a prescription. The indexer is a single-writer SQLite projection sized for a testnet; the backend is -one Node process. They show *what* an integrator needs to read and relay, not the +one Node process. They show what an integrator needs to read and relay, not the only way to build it. The on-ledger contracts are the specification; the off-chain services are one implementation of the surface around them (`architecture.md`, "Off-Chain Services"). diff --git a/docs/concepts/overview.md b/docs/concepts/overview.md index 536ba259..d2ac120a 100644 --- a/docs/concepts/overview.md +++ b/docs/concepts/overview.md @@ -3,19 +3,19 @@ Canton DEX is a runnable **reference implementation** of exchange workflows on the Canton Network. It shows how market state, wallet-authorized funding, registry-defined holdings, Token Standard V2 allocations, and atomic settlement -batches fit together in one application — as real Daml workflows and a working +batches fit together in one application, as real Daml workflows and a working dApp, not just diagrams. If you want to run it, go to **[Getting Started](../getting-started.md)**. This -page explains *why it is shaped the way it is*. +page explains why it is shaped the way it is. ## What it demonstrates - **Token-standard-native funds movement.** Value moves through V2 holdings, - allocations, allocation requests, and settlement factories — not a custom + allocations, allocation requests, and settlement factories, not a custom off-ledger balance model. - **A strict authority boundary.** The operator can only submit the commands it - is authorized to submit; every movement of *trader* assets is authorized by + is authorized to submit; every movement of trader assets is authorized by the trader's own wallet. - **Concrete settlement patterns.** RFQs, matched trades, prefunded orders, swaps, and LP add/remove are each implemented as delivery-versus-payment @@ -32,25 +32,25 @@ page explains *why it is shaped the way it is*. |---|---|---| | **CIP-0056** — Canton Network Token Standard | The base token standard (holdings, transfers, metadata). | The foundation the V2 revision extends. | | **CIP-0112** — Token Standard **V2** | The privacy / performance / traditional-accounting revision, adding the allocation + settlement surface. | Every asset (base, quote, and LP token) is a V2 instrument; funds move via V2 allocations and settlement factories. | -| **CIP-0103** — dApp Standard | The wallet interaction standard (prepare → sign → execute interactive submission). | The dApp hands *trader-authority* commands to a wallet over CIP-0103 rather than submitting them itself. | +| **CIP-0103** — dApp Standard | The wallet interaction standard (prepare → sign → execute interactive submission). | The dApp hands trader-authority commands to a wallet over CIP-0103 rather than submitting them itself. | Token Standard V2 has **merged into `canton-network/splice` `main`** and becomes the network default from **mid-July 2026**. This repo vendors the V2 -sources at a pinned commit — see +sources at a pinned commit. See [`../../vendor/splice/VENDOR_PIN.md`](../../vendor/splice/VENDOR_PIN.md) and the [Allocation Surface](../reference/allocation-surface.md) reference for the exact surface it relies on. ## The trust model -The single most important design idea is **who is allowed to move what**. Four +The single most important design idea is who is allowed to move what. Four authorities, each with a distinct responsibility: | Authority | Owns | Example | |---|---|---| | **DEX contracts** | Market state and workflow validation. | An `Order` records price/size and enforces matching rules. | | **Token Standard contracts** | Asset reservation and settlement. | An `Allocation` locks a holding; `SettlementFactory_SettleBatch` settles atomically. | -| **Registry** | Instrument semantics and choice context. | The registry says what a holding *is* and supplies the context a settlement needs. | +| **Registry** | Instrument semantics and choice context. | The registry says what a holding is and supplies the context a settlement needs. | | **Wallet + operator** | Submission authority. | The **wallet** submits trader-authority commands (funding, allocation creation); the **operator** submits only the commands it is authorized to (binding orders, matching, settling batches). | The operator backend **never** moves a trader's assets on its own. When a diff --git a/docs/concepts/pricing.md b/docs/concepts/pricing.md index ffaeab81..aa1b6a41 100644 --- a/docs/concepts/pricing.md +++ b/docs/concepts/pricing.md @@ -1,8 +1,8 @@ -# Pricing and Oracle Sources +# Pricing and oracle sources -## Short answer +## No on-chain price oracle -**There is no on-chain price oracle in this DEX.** Every executable +There is no on-chain price oracle in this DEX. Every executable price comes from one of four endogenous sources, and the codebase contains no integration with Chainlink, Pyth, an attested feed, a TWAP window, or any external pricing service. @@ -21,7 +21,7 @@ reserves; it does not consult any external feed. The operator backend's `policy/index.ts rankQuotes` ranks dealer quotes but never substitutes a price. -## What this means in practice +## Practical consequences - Pool prices follow reserves. A pool with stale or thin liquidity will quote stale prices. There is no oracle-backed "fair value" @@ -32,8 +32,8 @@ substitutes a price. fee-model the pair config encodes. - RFQ prices are whatever dealers quote. The [PolicyReceipt](../../trading/CantonDex/Dex/PolicyReceipt.daml) records - *which* quote ranked where under which policy version, but does not - certify that the chosen price is "good" — only that the policy was + which quote ranked where under which policy version, but does not + certify that the chosen price is "good", only that the policy was applied honestly. - The fiat estimates the dApp shows next to instrument balances are **live, pool-derived values**, sourced from the operator backend's @@ -44,22 +44,22 @@ substitutes a price. price. They are advisory display estimates, deliberately not used for any executable decision. -## What an oracle would change (and where it would attach) +## Oracle attachment points If a future tranche introduces an oracle, the natural attachment points are: -1. **Slippage / circuit-breaker on `PoolRules_Swap`.** Add an +1. Slippage / circuit-breaker on `PoolRules_Swap`. Add an `oracleAttestation` argument with a signed price + timestamp; the choice asserts the realized swap price stays within a band of the attested price. The signer would be a separate `oracleAuthority` party in the choice context (production registries already follow - this pattern for credential checks — see + this pattern for credential checks, see [registry-integration.md](../guides/registry-integration.md)). -2. **TWAP for compliance reporting.** A separate `PoolPriceObservation` +2. TWAP for compliance reporting. A separate `PoolPriceObservation` template the operator creates after each `PoolRules_Swap`, sampled by an off-chain ingestor. Pure observability, no consensus role. -3. **Fiat-display reference.** The dApp's `assets.ts` could call out +3. Fiat-display reference. The dApp's `assets.ts` could call out to a public price API at the edge. This is presentation-only and does not need to be on-ledger. diff --git a/docs/concepts/workflows.md b/docs/concepts/workflows.md index 67bc2b55..38e467b4 100644 --- a/docs/concepts/workflows.md +++ b/docs/concepts/workflows.md @@ -1,6 +1,6 @@ -# Canton DEX Workflow Design +# Canton DEX workflow design -## Why Workflow First +## Why workflow first The hard part of this reference DEX is not matching Uniswap feature-for-feature. The hard part is getting the Daml workflows right so that: @@ -13,7 +13,7 @@ The hard part is getting the Daml workflows right so that: That means we should design workflows first and let features fall out of those workflows. -## We Do Not Need Full Uniswap Parity +## We do not need full Uniswap parity A production-shaped reference DEX does not need every Uniswap V2 or V3 feature. @@ -47,7 +47,7 @@ add/remove, prefunded orders, OTC/RFQ, cancellation, and operator recovery. If this document makes volume-coverage claims in the future, they should be backed by current market data rather than asserted qualitatively. -## Workflow Design Principles +## Workflow design principles 1. One workflow, one business object - orders, trades, pools, and LP issuance each get their own app contract @@ -68,8 +68,8 @@ by current market data rather than asserted qualitatively. 6. Executor-controlled funds must be usage-constrained on ledger - if committed or iterated allocations put funds under executor-driven settlement control, the app contracts must validate every permitted use - - off-chain services may choose *when* to exercise a workflow, but not - redefine *what* the funds may be used for + - off-chain services may choose when to exercise a workflow, but not + redefine what the funds may be used for 7. Keep hot-path transactions shard-local - avoid workflow shapes that require touching every pool reserve allocation @@ -90,7 +90,7 @@ For a reference deployment, `Matcher` and `PoolOperator` may both be operated by the `DexOperator`, but the workflows should keep their responsibilities separate. -## Core On-Ledger Contracts +## Core on-ledger contracts - `DexPair` - `MatchedTrade` @@ -116,7 +116,7 @@ DAR implements upstream Token Standard V2 interfaces, but it does not define a separate app-facing interface that decouples an LP-token registry package from a venue package. -## Dependency Split +## Dependency split There are two distinct workflow families. @@ -201,7 +201,7 @@ sequenceDiagram L-->>O: settled, trade recorded (private to counterparties) ``` -## Workflow 1: Pair Listing +## Workflow 1: Pair listing Purpose: - define that the DEX supports trading a given base and quote `InstrumentId` @@ -236,7 +236,7 @@ Why it matters: - it is the right place to gate experimental pool support or lifecycle-rich assets -## Workflow 2: OTC / RFQ Trade +## Workflow 2: OTC / RFQ trade Purpose: - prove the baseline token-standard-native trade flow @@ -268,7 +268,7 @@ Why it comes first: - this is the cleanest reference workflow available today - it teaches the core DvP pattern without depending on pool semantics -## Workflow 3: Resting Order Placement +## Workflow 3: Resting order placement Purpose: - represent a bid or ask as DEX state backed by reserved funds @@ -304,7 +304,7 @@ Required invariants: - allocation funding must cover remaining order quantity - order expiry must bound allocation usability -## Workflow 4: Order Match and Settlement +## Workflow 4: Order match and settlement Purpose: - convert two resting orders into one settled trade @@ -331,7 +331,7 @@ Failure and unwind flow: 2. if settlement fails, orders remain unchanged 3. if one order expires mid-flight, the DEX cancels the match attempt -## Workflow 5: Order Cancel or Expiry +## Workflow 5: Order cancel or expiry Purpose: - release funds and remove dead liquidity @@ -351,7 +351,7 @@ Important policy choice: - the operator should sweep orders past their expiry time; for RFQs the operator-controlled `Rfq_Expire` choice enforces the deadline on-ledger -## Workflow 6: Pool Creation +## Workflow 6: Pool creation Purpose: - define a pool and its LP token @@ -380,7 +380,7 @@ Why: - it keeps the workflow challenge in Daml rather than concentrated-liquidity math -## Workflow 7: Add Liquidity +## Workflow 7: Add liquidity Purpose: - fund the pool and mint LP shares @@ -399,7 +399,7 @@ On-ledger flow: allocations via `AllocationFactory_Allocate` 3. operator and `lpRegistrar` settle with `PoolLiquidityRules_SettleAddLiquidity`: funds enter the pool, reserve state is updated, pool-managed committed - allocations are refreshed, and LP tokens are minted to the provider — + allocations are refreshed, and LP tokens are minted to the provider, atomically in one settlement Important note: @@ -407,7 +407,7 @@ Important note: - LP deposits do not need concentrated-liquidity position NFTs - fungible LP shares are enough for the first production-shaped reference -## Workflow 8: Remove Liquidity +## Workflow 8: Remove liquidity Purpose: - burn LP shares and return the provider's proportional reserves @@ -421,7 +421,7 @@ On-ledger flow: 3. operator and `lpRegistrar` settle with `PoolLiquidityRules_SettleRemoveLiquidity`: base and quote are delivered to the holder, the LP tokens burn to the burn account, pool reserve allocations are adjusted down, and reserve references - are rolled forward — atomically in one settlement + are rolled forward, atomically in one settlement Required invariants: @@ -434,7 +434,7 @@ Required invariants: the boundary are untouched. Operator pays for at most ONE re-allocation per side, never one per existing slice -## Workflow 9: Pool Swap +## Workflow 9: Pool swap Purpose: - execute a trader swap against the pool @@ -475,7 +475,7 @@ Failure and unwind flow: 3. if pool reserve references are stale, the operator must refresh state before retrying -## Workflow 10: Asset Lifecycle Interaction +## Workflow 10: Asset lifecycle interaction Purpose: - let lifecycle-rich instruments trade without making the DEX own their @@ -505,7 +505,7 @@ Important boundary: - it should only respond to registry-published tradable instrument versions or metadata updates -## Implemented Reference Scope +## Implemented reference scope The reference implementation covers: @@ -526,7 +526,7 @@ It deliberately defers: 4. advanced oracle surfaces 5. NFT-style LP positions -## Contract Boundary Summary +## Contract boundary summary Keep the market objects (`DexPair`, `Order`, `MatchedTrade`, `Rfq`) separate from the pool accounting objects (`Pool`, `PoolState`, `PoolSlice`) and the diff --git a/docs/getting-started.md b/docs/getting-started.md index 08b00fc9..f8ca5aac 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,11 +1,11 @@ # Local Setup & Testing -One page to clone, build, run, test, and explore the **whole** reference DEX on -your machine — the Daml core, the operator backend, the dApp, and the scripts. +One page to clone, build, run, test, and explore the whole reference DEX on +your machine: the Daml core, the operator backend, the dApp, and the scripts. The local path needs **no Canton participant**: the dev backend ships an in-memory ledger, so you can have the full stack up in a few minutes. -> TL;DR +> Quick start > ```bash > git clone https://github.com/srikanth-bitdynamics/Canton-Dex-Reference-Implementation.git && cd Canton-Dex-Reference-Implementation > bash scripts/run-local-daml-tests.sh # Daml build + tests @@ -191,11 +191,11 @@ For the Dev Fund milestone reviewers, the same commands map to the deliverables: ## Optional: run against a real Canton ledger The dev backend is in-memory. To run on real Canton: -- **LocalNet** — a self-contained Canton + Splice network on one host; build the +- **LocalNet**: a self-contained Canton + Splice network on one host; build the DAR, upload it + the V2 DARs, seed a pair/pool, point the backend at the participant (`CANTON_LEDGER_URL`), and run `npm run start`. See `docs/guides/deployment.md`. -- **Testnet** — `scripts/deploy-testnet.sh` uploads the DAR + seeds; record the +- **Testnet**: `scripts/deploy-testnet.sh` uploads the DAR + seeds; record the vetted package id + seed CIDs in `docs/guides/run-on-testnet.md`. --- diff --git a/docs/guides/choice-context.md b/docs/guides/choice-context.md index 55dcf2e6..a2fed920 100644 --- a/docs/guides/choice-context.md +++ b/docs/guides/choice-context.md @@ -1,4 +1,4 @@ -# Choice Context and Disclosure Retrieval +# Choice context and disclosure retrieval Defines what the DEX operator backend must attach to each transaction it submits, in terms of registry-supplied disclosed contracts and choice-context @@ -28,8 +28,8 @@ required by the registry's Token Standard V2 choices. The operator-backend's ## Choice-context-bearing arguments -Each registry-touching choice the DEX exercises has a **context -shape** the operator must satisfy. Listed here as `(choice, required +Each registry-touching choice the DEX exercises has a context +shape the operator must satisfy. Listed here as `(choice, required context)` pairs. ### Allocation creation diff --git a/docs/guides/deployment.md b/docs/guides/deployment.md index fa02fb6e..d48e57aa 100644 --- a/docs/guides/deployment.md +++ b/docs/guides/deployment.md @@ -1,10 +1,10 @@ -# Deployment Guide +# Deployment guide How to deploy the Canton DEX reference implementation. Three deployment paths are supported: **Docker Compose**, **local dev** (in-memory ledger), and **direct testnet** (real Canton participant). -## 1. Local Dev (no Canton required) +## 1. Local dev (no Canton required) For UI development. Uses the `InMemoryLedger` and seeds a BTC/USDC pair and pool. @@ -45,7 +45,7 @@ docker-compose up -d Persistent state lives in the `backend-data` volume (SQLite indexer DB). To wipe and restart fresh: `docker-compose down -v && docker-compose up -d`. -## 3. Testnet Deployment +## 3. Testnet deployment Direct deployment without containers. Same path as docker-compose's backend service but you manage the Node process yourself (systemd, pm2, @@ -75,14 +75,14 @@ export CANTON_OPERATOR=... node --import tsx scripts/bootstrap-registry.ts ``` -The script is idempotent — running it twice is a no-op. See +The script is idempotent: running it twice is a no-op. See [Registry Integration](registry-integration.md) for what contracts are created and why. Among them is a `Registry.V2` under the **lpRegistrar**. That one is not optional: the pool's LP token is issued by this repository, and its allocation specs name the lpRegistrar as admin, which `Registry.V2` asserts -against its own. Without it, add- and remove-liquidity cannot allocate — +against its own. Without it, add- and remove-liquidity cannot allocate, whatever the pool trades. A second registry, under `CANTON_ADMIN`, is created only if you add a @@ -97,7 +97,7 @@ per-admin registry lookup the design calls for. In a deployment serving foreign tokens, each admin's factory cid comes from that admin's own registry API, not from these variables. -## Environment Variables +## Environment variables See `services/operator-backend/.env.example` and `app/web/.env.example` for the canonical list. Required for production: @@ -114,7 +114,7 @@ for the canonical list. Required for production: | `OPERATOR_ADMIN_TOKEN` | Admin auth token for `/v1/admin/*` | | `ALLOWED_ORIGINS` | CSV of CORS origins to allow | -## Production Checklist +## Production checklist - [ ] `OPERATOR_ADMIN_TOKEN` set to a strong random value - [ ] `ALLOWED_ORIGINS` narrowed to your dApp host (not `*`) diff --git a/docs/guides/operator-guide.md b/docs/guides/operator-guide.md index 0eee6509..b3a6ad27 100644 --- a/docs/guides/operator-guide.md +++ b/docs/guides/operator-guide.md @@ -2,7 +2,7 @@ How the DEX operator (admin) deploys, configures, and runs the venue. -The operator is the party that owns the trading venue — sets up pairs +The operator is the party that owns the trading venue. It sets up pairs and pools, observes settlement events, runs the matching engine, collects fees, and recovers from incidents. @@ -257,7 +257,7 @@ authenticated operational environment. The token is configured via the `OPERATOR_ADMIN_TOKEN` env var. If lost, set a new one and restart the operator backend. Existing admin writes that haven't settled won't be replayable (different token -hash) — re-submit them via the new token. +hash). Re-submit them via the new token. --- diff --git a/docs/guides/operator-runbook.md b/docs/guides/operator-runbook.md index 5d46ac9d..3096425d 100644 --- a/docs/guides/operator-runbook.md +++ b/docs/guides/operator-runbook.md @@ -9,7 +9,7 @@ operational documentation, not here. ## Roles and party model The reference deployment expects four distinct parties. Keeping them logically -separate is part of the design — collapsing them is acceptable for a single- +separate is part of the design. Collapsing them is acceptable for a single- operator dev instance but should not be the production posture. | Party | Owns | Signs | @@ -67,7 +67,7 @@ on a schedule. ### Stale or expired orders -- `Order_Cancel` (operator-driven) — cancels the bound allocation via +- `Order_Cancel` (operator-driven): cancels the bound allocation via `Allocation_Cancel`, releasing the trader's locked holdings back to their authorizer account. The operator's sweep uses it both for orders past `expiry` (checked off-ledger when scheduling the cancel) and for @@ -80,30 +80,30 @@ on a schedule. `currentTime < expiresAt`, so nothing can settle against it. Quote contracts stay until their own `expiresAt`; the operator sweep exercises `RfqQuote_Withdraw` (dealer-driven) or lets quotes age out. -- `Rfq_Cancel` (trader-driven) — the trader retracts before any quote +- `Rfq_Cancel` (trader-driven): the trader retracts before any quote acceptance. ### Stuck matched trades -- `MatchedTrade_Cancel` (venue-driven) — archives outstanding - `TradeAllocationRequest` contracts AND exercises `Allocation_Cancel` on +- `MatchedTrade_Cancel` (venue-driven): archives outstanding + `TradeAllocationRequest` contracts and exercises `Allocation_Cancel` on any allocations that have already been created. Use when one leg's authorizer rejects or times out before settlement. ### Pool maintenance -- `PoolRules_Pause` (operator) — halts new swaps and liquidity actions while +- `PoolRules_Pause` (operator): halts new swaps and liquidity actions while leaving reserve allocations in place. Useful for upgrades and incident response. -- `PoolRules_Resume` (operator) — exits Paused back to Active. +- `PoolRules_Resume` (operator): exits Paused back to Active. - Remove-liquidity is slice-local: the `PoolLiquidityRules_SettleRemoveLiquidity` - settle sources a routine withdrawal from at most ONE boundary + settle sources a routine withdrawal from at most one boundary re-allocation per side. The architecture and workflows docs describe the invariant; the liquidity rules tests exercise the boundary case. ### LP supply reconciliation -- `PoolState_RecordLPSupply` (lpRegistrar) — pushes the registrar-owned LP +- `PoolState_RecordLPSupply` (lpRegistrar): pushes the registrar-owned LP supply ledger back into the pool's pricing state. Run after each mint/burn accept so add-liquidity quoting stays accurate. @@ -222,9 +222,9 @@ package exposes an `EventLog` interface for replayable audit. The on-ledger state is the source of truth and is replicated by the synchronizer. The operator backend's local SQLite is rebuildable from -the ledger and does **not** need to be backed up for correctness; back +the ledger and does not need to be backed up for correctness; back it up only if you care about historical query performance during -rebuild. Operator config in the `operator_kv` table IS worth backing +rebuild. Operator config in the `operator_kv` table is worth backing up. It carries dealer whitelist, RFQ policy parameters, and similar runtime knobs that are not encoded on-ledger. diff --git a/docs/guides/registry-integration.md b/docs/guides/registry-integration.md index 9ffaaa08..92fa5768 100644 --- a/docs/guides/registry-integration.md +++ b/docs/guides/registry-integration.md @@ -45,7 +45,7 @@ For every instrument the DEX trades, the registry must provide: Registries may bound how long an allocation or instruction can live. Amulet (Splice 0.6.11+) enforces `AmuletConfig.tokenStandardMaxTTL` — **90 days by default** — on token-standard allocations and instructions. This matters for -the DEX because pool reserves are held as **long-lived committed allocations** +the DEX because pool reserves are held as long-lived committed allocations (one per slice): against a TTL-capping registry, pool inventory must be rolled into fresh allocations before the cap expires, and settlement deadlines on order/LP allocations must stay inside the registry's cap. The reference @@ -55,8 +55,8 @@ operational task. ## Registry API surface (Daml + OpenAPI) -Token Standard V2 registries are expected to expose **both** the Daml -interfaces and the standard **OpenAPI** endpoints (the specs ship alongside +Token Standard V2 registries are expected to expose both the Daml +interfaces and the standard OpenAPI endpoints (the specs ship alongside each API package in `canton-network/splice` under `token-standard/`). The reference registry implements the Daml interface side in full; its off-ledger surface is the choice-context endpoint the backend's registry-client consumes @@ -112,8 +112,8 @@ operator code: them and to anyone monitoring the operator's stream. When these are enforced in Daml, a malicious operator attempting to -spend funds the authorizer never granted has to submit an **invalid -Daml transaction**, which the engine rejects regardless of operator +spend funds the authorizer never granted has to submit an invalid +Daml transaction, which the engine rejects regardless of operator intent. The mock at [MockRegistry.daml](../../trading/CantonDex/Testing/MockRegistry.daml) @@ -143,7 +143,7 @@ it is registry-specific. What this means in practice for a DEX integrator: - **Active holders upgrade themselves.** One possible pattern is - *upgrade-on-use* — the registry's transfer/allocation factories + *upgrade-on-use*: the registry's transfer/allocation factories rewrite the holding to the current version on any operation that touches it. A holder who is actively trading or otherwise moving their position pays for the upgrade implicitly as part of that @@ -173,7 +173,7 @@ The DEX has three classes of holdings to think about: upgrade-on-use covered. 2. **Pool reserves.** Reserves are held by the pool contract under the - operator's authority. They are *not* passive — every swap rotates a + operator's authority. They are not passive: every swap rotates a slice of reserves through the factory paths. The pool is therefore effectively self-maintaining against issuer upgrades, with the one edge case that a pool sitting completely idle for a long stretch @@ -196,13 +196,13 @@ The DEX has three classes of holdings to think about: - When a wallet command returns a "holding not found" or "holding version mismatch" error after a forced upgrade, re-fetch the holding list and retry rather than surfacing the error to the user. -- Treat instrument *id* (e.g., `BTC`) as the stable join key; treat the +- Treat instrument id (e.g., `BTC`) as the stable join key; treat the per-holding contract id and package hash as ephemeral. The DEX's allocation flow already re-queries holdings on each user action (pre-allocation greedy selection, post-settlement refresh), so incidental force-upgrade exposure is minimal. Where it could bite is -manual replay tooling that caches a stale holding cid — the operator +manual replay tooling that caches a stale holding cid. The operator backend's command path does not cache cids across requests. ## Known limitation: one registry admin per pair @@ -221,8 +221,8 @@ and is shaped for multiple admins, inherited from the upstream batching utility. Each `SettlementBatchV2` carries its own `transferLegs`: the standard requires a batch's allocations to cover exactly the legs the batch is handed, so the caller partitions the trade's legs by the instrument admin of each leg -(`groupLegsByAdmin` in the operator backend). Note that `splitLegsByAuthorizer` -splits by *authorizer*, not by admin, so the request path still emits one +(`groupLegsByAdmin` in the operator backend). `splitLegsByAuthorizer` +splits by authorizer, not by admin, so the request path still emits one specification per authorizer under the trade's single admin. Pairing instruments from two different registries needs a second admin field diff --git a/docs/guides/run-on-testnet.md b/docs/guides/run-on-testnet.md index ff57786a..4358cc27 100644 --- a/docs/guides/run-on-testnet.md +++ b/docs/guides/run-on-testnet.md @@ -1,4 +1,4 @@ -# Run Against a Canton Testnet +# Run against a Canton testnet This guide shows how to point the operator backend and web app at a Canton participant that already has the required DEX and token-standard packages @@ -18,7 +18,7 @@ Do not commit tokens, concrete party ids, or validator-specific package hashes. - Operator, LP registrar, and asset-admin parties allocated on the participant. - Registry factory contracts for the asset admins the DEX will touch. -## Start the Operator Backend +## Start the operator backend ```bash cd services/operator-backend @@ -38,7 +38,7 @@ npm run testnet The backend reads the token from the environment and does not write it to disk. -## Start the Web App +## Start the web app ```bash cd app/web @@ -54,7 +54,7 @@ npm run preview Open . The header should show the configured network and the backend status should report `synced: true`. -## PartyLayer Wallet Live Probe +## PartyLayer wallet live probe PartyLayer support is integrated into the main web app; no separate probe app is needed. Use this checklist when validating a submit-capable wallet adapter @@ -115,7 +115,7 @@ lookup response. The usual causes are missing operator visibility on the created contracts, a wallet receipt without `updateId`, or a party mismatch between the connected wallet and the holdings being spent. -## Smoke Checks +## Smoke checks ```bash curl -s http://localhost:8080/v1/status | python3 -m json.tool @@ -131,7 +131,7 @@ Expected: - `/v1/pairs` and `/v1/pools` return the on-ledger contracts visible to the operator party. -## Bootstrap a Pair and Pool +## Bootstrap a pair and pool Use the admin endpoints in [operator-guide.md](operator-guide.md): @@ -141,7 +141,7 @@ Use the admin endpoints in [operator-guide.md](operator-guide.md): New pools start in `PS_Unfunded`. The first LP funds the pool through the same add-liquidity request/allocate/settle flow used for later deposits. -## Package Hash Alignment +## Package hash alignment If DAR upload or vetting fails with package-version/hash errors, confirm that all local DARs were built against the same upstream Token Standard package @@ -154,17 +154,17 @@ If a pair uses Amulet (CC) as an asset, note the Splice 0.6.11+ requirements: - The validator node (yours or your wallet provider's) must run a version that supports the Token Standard V2 APIs, and the Amulet DARs must be at the - V2-capable versions (`amulet` 0.1.21+, `wallet` 0.1.22+ — see the + V2-capable versions (`amulet` 0.1.21+, `wallet` 0.1.22+; see the [Splice release notes](https://docs.canton.network/global-synchronizer/release-notes/splice)). - Amulet enforces `tokenStandardMaxTTL` (default 90 days) on allocations and - instructions — see + instructions. See [Registry Integration](registry-integration.md#allocation-lifetime-caps). - Known upstream limitation: the **Splice Amulet Wallet UI** can only create multiple requested allocations in a single transaction for *Amulet* allocations. The DEX sidesteps this for its own flows by having one Daml choice author all allocations of a request in a single command. -## Wallet Boundary +## Wallet boundary Operator-authority calls go through the backend. Trader-authority calls, such as authoring allocations for add/remove liquidity, swaps, and order funding, must diff --git a/docs/guides/using-the-dapp.md b/docs/guides/using-the-dapp.md index c9fb77e2..0353dc6f 100644 --- a/docs/guides/using-the-dapp.md +++ b/docs/guides/using-the-dapp.md @@ -1,4 +1,4 @@ -# User Guide +# User guide How traders, LPs, and RFQ counterparties use the Canton DEX. diff --git a/docs/reference/allocation-surface.md b/docs/reference/allocation-surface.md index 9fa45555..dbc6c291 100644 --- a/docs/reference/allocation-surface.md +++ b/docs/reference/allocation-surface.md @@ -1,9 +1,9 @@ # Token Standard V2 allocation surface This document records the specific Token Standard **V2 (CIP-0112)** allocation- -surface features this DEX relies on — committed allocations and iterated -settlement — together with the exact DEX consumers, reconstructed from the -**actual vendored interface** so readers can audit the dependency directly. +surface features this DEX relies on (committed allocations and iterated +settlement), together with the exact DEX consumers, reconstructed from the +actual vendored interface so readers can audit the dependency directly. Token Standard V2 has merged into `canton-network/splice` `main` and becomes the network default from mid-July 2026; this repo vendors the V2 sources at the @@ -12,7 +12,7 @@ commit pinned in For the architectural rationale (why the DEX leans on these extensions for pool inventory, not just trade reservation), see -[`../concepts/architecture.md`](../concepts/architecture.md) — section +[`../concepts/architecture.md`](../concepts/architecture.md), section "3. Token Standard V2 allocation surface". This document is the factual, file-anchored reference; the architecture doc is the design context. @@ -51,7 +51,7 @@ elements the DEX consumes directly. Defined in `AllocationV2.daml` on `AllocationSpecification` (`committed : Bool`). When `True`, the authorizer cannot withdraw the allocation until the settlement deadline passes (or the executors -settle/cancel, or the admin expires it). This is what lets pool liquidity sit in +settle/cancel, or the admin expires it). This lets pool liquidity sit in an allocation that an LP cannot casually pull back. DEX usage: @@ -109,8 +109,8 @@ DEX usage: ### `FinalizedAllocation.extraTransferLegSides` `FinalizedAllocation.extraTransferLegSides : [TransferLegSide]` lets executors -supply the concrete transfer leg sides to authorize **in this settlement -iteration**, on top of the legs fixed at allocation creation. They MUST be empty +supply the concrete transfer leg sides to authorize in this settlement +iteration, on top of the legs fixed at allocation creation. They MUST be empty unless the authorizer enabled iterated settlement. The matching `Allocation_Settle.extraTransferLegSides` choice argument carries them into the settle path. @@ -136,10 +136,10 @@ DEX usage: The vendored `AllocationV2.daml` `Allocation` interface exposes exactly three state-changing choices: `Allocation_Settle`, `Allocation_Cancel`, and -`Allocation_Withdraw`. There is **no** `Allocation_Adjust` choice on the +`Allocation_Withdraw`. There is no `Allocation_Adjust` choice on the V2 surface. Earlier/alternative designs adjusted an allocation's authorized amounts in place via a dedicated choice; on this surface that role is -subsumed by iterated settlement — `Allocation_Settle` carries +subsumed by iterated settlement: `Allocation_Settle` carries `extraTransferLegSides` and `nextIterationFunding` and emits a next-iteration allocation via `nextIterationAllocationCid`, so the funding "adjustment" happens as part of settle rather than as a separate choice. diff --git a/docs/reference/ecosystem-feedback.md b/docs/reference/ecosystem-feedback.md index 845d2b9d..0242fa70 100644 --- a/docs/reference/ecosystem-feedback.md +++ b/docs/reference/ecosystem-feedback.md @@ -11,9 +11,9 @@ The reference DEX is integrated as an adapter in an independent, open-source, venue-agnostic trading client for the Canton Network. The toolkit is live-validated on mainnet against an unrelated spot AMM (Cantex) and connects to a perpetuals testnet (Ekiden); this DEX is a third -adapter (`DexRefAdapter`). The significance is that the *same client code* that +adapter (`DexRefAdapter`). The same client code that trades on an unrelated mainnet venue drives quotes, swaps, orders, matching, RFQ -and liquidity on this one, entirely through the hosted testnet routes — the only +and liquidity on this one, entirely through the hosted testnet routes: the only path open to a party with no wallet of its own. The integration is reproducible from outside with no operator credentials: @@ -26,9 +26,9 @@ PYTHONPATH=src python3 scripts/dexref_testnet_report.py --execute # trades ``` The client allocates its own parties from the public faucet and exercises every -flow against `https://testnet-dex.bitdynamics.cc`. This is the concrete reuse -proof point: an external developer built a working integration against the hosted -testnet, from the public repository, and published it. +flow against `https://testnet-dex.bitdynamics.cc`. An external developer built a +working integration against the hosted testnet, from the public repository, and +published it. ## Evaluation and feedback @@ -43,20 +43,20 @@ routes. The reports are public: [canton-dev-fund#312 comment](https://github.com/canton-foundation/canton-dev-fund/issues/312#issuecomment-5044174855) Because the integrator has no privileged access, the findings are exactly what any -external builder would hit, which is what makes them useful. +external builder would hit. ## Findings and resulting changes Every finding from the six rounds was addressed. They fall into a few themes. -**Precision and wire correctness.** Amounts must be served at ledger precision as +Amounts must be served at ledger precision as exact decimal strings, not re-floated. Fixes: the fills feed no longer routes deltas through `parseFloat().toFixed` (F13); `/v1/swaps` serves the exact strings rather than re-floating them (F20); `/v1/instruments` reports each instrument's `decimals`, so a client can learn scale from the API; pre-fix rows were backfilled rather than left wrong (F21). -**Read-surface consistency.** External clients depend on the read API being +External clients depend on the read API being uniform. Fixes: the status wire value stopped shipping a `PS_`-prefixed enum the dApp silently stripped (F11); `/v1/orders/book` accepts `?pair=` like every other read (F15, additively); the trades feed no longer inverts trader and dealer on @@ -64,12 +64,12 @@ buys (F16); `/v1/trades` includes `counterparty` after the deployment was brough current with `main` (F22); an unscoped, unauthenticated `GET /v1/rfq` that lived only on a soon-to-be-retired branch was fixed on `main` (F23). -**Funding and custody correctness.** Fixes: funding an order locks only what the +Two fixes concern funding and custody: funding an order locks only what the order needs and returns the change, so a party can place more than one order (F12); the off-ratio liquidity add refunds the unmatched remainder and the hosted receipt reports the settled amounts rather than echoing the request (F25). -**Completing the hosted surface.** The hosted routes are the only path for a +The hosted routes are the only path for a walletless integrator, so gaps in them block external evaluation entirely. Fixes: RFQ gained a hosted cancel, so a round trip has an exit other than expiry (F17); order matching gained a hosted, unauthenticated trigger (`POST /v1/testnet/match`) @@ -78,14 +78,14 @@ so matching and its atomic settlement can be verified from outside (F24); (F26). The whole `/v1/testnet/*` surface and the faucet's per-IP party quota were documented with their consequences (F14, F18). -**Behaviour explained rather than changed.** Some reports were answered by design: +Some reports were answered by design: `Holding_Split` is refused by the hosted relay because the relay admits only a fixed choice allowlist, and splitting is a wallet concern the relay does not expose (F19). One item remained open at the time of the report and has since been closed: a resting order the book published but the matcher would not pair (F27). The cause -was a self-cross — a party's own bid and ask — which can never settle. The matcher +was a self-cross (a party's own bid and ask) which can never settle. The matcher now applies self-trade prevention and no longer proposes it. ## How this loop is expected to continue diff --git a/docs/reference/http-api.md b/docs/reference/http-api.md index 09dcdcf2..7b54f8f3 100644 --- a/docs/reference/http-api.md +++ b/docs/reference/http-api.md @@ -1,4 +1,4 @@ -# Operator Backend API Reference +# Operator backend API reference All endpoints are served from the operator-backend HTTP shim at the configured port (default 8080). Every response is JSON. Error responses @@ -16,11 +16,11 @@ have the shape: Every response also carries the `X-Request-Id` header (echoed from the request if supplied, otherwise generated). -## Read Endpoints +## Read endpoints ### `GET /v1/context` -Returns `DexContext` — the static parties and factory CIDs the dApp +Returns `DexContext`: the static parties and factory CIDs the dApp needs to build trader-authority intents. ```json @@ -58,7 +58,7 @@ Open orders for a specific trader. **400** if `trader` is missing. ### `GET /v1/orders/book?pair=BASE/QUOTE` → `{ bids, asks }` ### `GET /v1/orders/matches?pair=BASE/QUOTE` → `{ matches }` -Each entry carries only the terms — `price`, `quantity`, `buyOrderCid`, +Each entry carries only the terms: `price`, `quantity`, `buyOrderCid`, `sellOrderCid`. The orders themselves name their traders and allocations and are not served here. @@ -88,7 +88,7 @@ instrument, with `locked` (in open orders / swaps / allocations) split from Known instruments with metadata: `decimals` from the registry's `InstrumentConfig`, `isin`/`cusip`/`description` from `InstrumentConfiguration`, merged by `instrumentId` and unioned with the instruments referenced by active -pools (so it is populated even before any config is registered — fields are +pools (so it is populated even before any config is registered; fields are `null` where no config exists). `symbol` is the instrument id. Optional `?ids=BTC,USDC` filters. @@ -110,7 +110,7 @@ differ from the ledger in the last decimal place, and a pre-cutover trade can carry `trader` and `dealer` inverted on a buy. `scripts/reindex-derived.ts` recomputes both in place, from data the indexer -already stores — no ledger read: +already stores, with no ledger read: ```bash node --import tsx scripts/reindex-derived.ts --db --dry-run @@ -125,17 +125,17 @@ backfilling history. Trade history for one trader: RFQ `MatchedTrade`s and the `SettledTrade` each order-book fill writes as it settles. **400** without `?trader=`, unless the -request carries the admin token — a row names both parties. **503** if the +request carries the admin token: a row names both parties. **503** if the indexer is not configured. -`?trader=` matches **either side** — a party appears as `trader` on the trades +`?trader=` matches either side: a party appears as `trader` on the trades it initiated and as `counterparty` on those it was matched into. `dealer` is a role and is set only where a signed policy receipt names one, so it is null on order-book fills; `counterparty` is populated either way. ### `GET /v1/swaps?pair=&limit=` → indexer rows -Amounts are **strings**, at the stored 10-decimal scale — `inputAmount` and +Amounts are strings, at the stored 10-decimal scale. `inputAmount` and `outputAmount` are derived from the signed deltas textually, never through a float. @@ -147,20 +147,20 @@ pause/resume also rotate the pool state, and are recorded with a `kind` of RFQs and quotes for one party: the RFQs they raised or were whitelisted for, and the quotes they posted or received. **400** without `?owner=`, unless the -request carries the admin token — the operator observes every RFQ and quote, +request carries the admin token: the operator observes every RFQ and quote, so the unfiltered view is admin-only. ### `GET /v1/rfq/history?trader=&limit=` → indexer rows Settled RFQ acceptances for one trader. **400** without `?trader=`, unless the -request carries the admin token — each row names the trader, the pair, the +request carries the admin token: each row names the trader, the pair, the winning dealer and its rank. ### `GET /v1/admin/config` → `Record` All operator config key-values. -## Quote Endpoint +## Quote endpoint ### `POST /v1/swaps/quote` @@ -188,7 +188,7 @@ All operator config key-values. Advisory; the on-ledger `PoolRules_Swap` choice re-validates with the latest reserves. -## Write Endpoints +## Write endpoints All POST endpoints return **400** for malformed JSON or missing required fields, **413** if the body exceeds 1 MiB. @@ -263,17 +263,17 @@ are the parts of the deposit the minted LP tokens represent and that reach the pool; `refundedBaseAmount` / `refundedQuoteAmount` are the remainder, which `PoolLiquidityRules_SettleAddLiquidity` refunds to the depositor in the same transaction (the settle result reports what it took as `baseAdded` / -`quoteAdded`). In the example above, 1152.26 dUSD — 11.5% of the quote leg — +`quoteAdded`). In the example above, 1152.26 dUSD (11.5% of the quote leg) comes back. At the reserve ratio both remainders are zero, and the first deposit into an unfunded pool sets the ratio, so nothing is left over there either. `maxOffRatioBps` (optional, `0`..`10000`, number or Decimal string) refuses the request with **400** when `offRatioBps` exceeds it, before any contract is -created. Omitting it means no ceiling — the historical behaviour. Use it when +created. Omitting it means no ceiling: the historical behaviour. Use it when a partly-filled add is not what you want: the excess is refunded rather than -lost, but the position you get is smaller than the amounts you sent. Note that -decimal rounding alone can leave a sub-bps remainder on an otherwise on-ratio +lost, but the position you get is smaller than the amounts you sent. +Decimal rounding alone can leave a sub-bps remainder on an otherwise on-ratio deposit, so `0` is stricter than it looks; `1` is the usual "on ratio" check. Compute the paired amount from `GET /v1/pools` reserves to stay on ratio, or send a tolerance and re-quote when it refuses. @@ -297,7 +297,7 @@ burn account, atomically. ## Authentication -**All state-changing routes require operator authorization** — not only +**All state-changing routes require operator authorization**, not only `/v1/admin/*`, but also the trader-facing writes (`/v1/pools/swap*`, `/v1/rfq`, `/v1/orders/*`). They return **401** unless `DEX_OPERATOR_API_TOKEN` is configured (send `Authorization: Bearer `) or, on the in-memory dev @@ -305,7 +305,7 @@ server only, `DEX_DEV_OPEN=1` is set. Read (GET) routes need no auth. Admin routes additionally require the `OPERATOR_ADMIN_TOKEN`. See [Local Setup → Exercising write paths](../getting-started.md#exercising-write-paths-in-demo-mode). -## Admin Endpoints +## Admin endpoints ### `POST /v1/admin/pairs` → `{ pairCid }` ### `POST /v1/admin/pairs/:cid/fee-model` → `{ pairCid }` @@ -315,7 +315,7 @@ routes additionally require the `OPERATOR_ADMIN_TOKEN`. See ### `PUT /v1/admin/config` body `{ key, value }` ### `DELETE /v1/admin/config/:key` -## Wallet Intent Shapes +## Wallet intent shapes The frontend never calls the on-chain ledger directly for trader- authority writes. Instead it hands intents to the active @@ -332,7 +332,7 @@ authority writes. Instead it hands intents to the active | `PostRfqQuoteIntent` | Dealer posts a quote on an RFQ | | `AcceptRfqIntent` | Trader accepts a dealer's quote (co-signed with operator) | -## Error Codes +## Error codes | `code` | HTTP | Meaning | |--------|------|---------| diff --git a/docs/reference/testing.md b/docs/reference/testing.md index d0a8ef5e..f0440f02 100644 --- a/docs/reference/testing.md +++ b/docs/reference/testing.md @@ -8,7 +8,7 @@ participant. ## What it verifies The test covers the same ground as the existing `rfq.test.ts` -(`InMemoryLedger`-driven), but going through the **real Daml engine** +(`InMemoryLedger`-driven), but going through the real Daml engine on a Canton participant via the JSON Ledger API: - `JsonApiLedger.submit` correctly serializes `submit-and-wait` @@ -75,7 +75,7 @@ CANTON_E2E=1 \ npm test --prefix services/operator-backend ``` -Expected output — the three Canton E2E cases (enabled by `CANTON_E2E=1`) +Expected output: the three Canton E2E cases (enabled by `CANTON_E2E=1`) within the full backend suite: ``` @@ -122,7 +122,7 @@ retryable so `retryOnContention` recovers automatically. - The token is passed as `Authorization: Bearer ...` on every request. -## What this test does NOT cover +## Out of scope - Pool initialization + add liquidity + swap end-to-end on a live ledger. The `testPoolFullLifecycle` and `testPoolSwapEndToEnd`