From 3409e1db8e602fdcd134051b8a9f271c130d0fa7 Mon Sep 17 00:00:00 2001 From: srikanth-bitdynamics <259878899+srikanth-bitdynamics@users.noreply.github.com> Date: Mon, 17 Aug 2026 08:00:42 +0530 Subject: [PATCH] docs: onboarding aids, diagrams, and SDK version fix Add an EVM/Uniswap comparison table, a numeric worked example, an on-ledger assertion table, a first-change recipe, order and pool state-machine diagrams, and a module-dependency graph across the concept, guide, and reference docs, plus a one-command smoke-test callout in getting started. Correct the README Daml SDK version to 3.5.2 to match the daml.yaml files. --- README.md | 4 ++-- docs/concepts/architecture.md | 31 +++++++++++++++++++++++++ docs/concepts/liquidity-and-custody.md | 24 +++++++++++++++++++ docs/concepts/overview.md | 15 ++++++++++++ docs/concepts/workflows.md | 32 ++++++++++++++++++++++++++ docs/getting-started.md | 5 ++++ docs/guides/builder-guide.md | 18 +++++++++++++++ docs/reference/http-api.md | 17 ++++++++++++++ 8 files changed, 144 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4f9ed7d6..543730ec 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ and runbooks for RFQs, prefunded orders, pools, swaps, and LP tokens.
@@ -134,7 +134,7 @@ an in-memory ledger and seeded demo data. - Node.js 24 or newer. - npm. -- Daml SDK 3.4.11 for Daml builds and tests. +- Daml SDK 3.5.2 (managed by `dpm`) for Daml builds and tests. ### 1. Install diff --git a/docs/concepts/architecture.md b/docs/concepts/architecture.md index bf4a4668..0cf42da6 100644 --- a/docs/concepts/architecture.md +++ b/docs/concepts/architecture.md @@ -319,6 +319,37 @@ asset stays a standard holding even when its lifecycle is rich. separate `DexRules` governance contract for pair admission yet, leaving room for forks to add governance or a decentralized rules layer. +The internal module dependencies (`CantonDex.*` imports) — `Trading.Utils` is the +shared base, and the pool, order, and RFQ/OTC clusters are otherwise independent: + +```mermaid +flowchart TD + Utils["Trading.Utils"] + WC["Trading.WorkflowConstructors"] --> Utils + Registry["Registry.V2"] --> Utils + + Pool["Dex.Pool"] + PoolState["Dex.PoolState"] --> Pool + PoolSlice["Dex.PoolSlice"] --> Pool + PoolModel["Dex.PoolModel"] --> Pool + PoolExecution["Dex.PoolExecution"] --> PoolModel + PoolRules["Dex.PoolRules"] --> PoolExecution + PoolRules --> Utils + PoolLiquidityRules["Dex.PoolLiquidityRules"] --> PoolExecution + PoolLiquidityRules --> LpPolicy["Lp.Policy"] + PoolLiquidityRules --> LpInstrument["Lp.Instrument"] + + Order["Dex.Order"] --> WC + OrderFundingRequest["Dex.OrderFundingRequest"] --> Order + OrderMatchExecution["Dex.OrderMatchExecution"] --> Order + OrderMatchExecution --> MatchedTrade + + MatchedTrade["Dex.MatchedTrade"] --> DexPair["Dex.DexPair"] + MatchedTrade --> PolicyReceipt["Dex.PolicyReceipt"] + MatchedTrade --> Utils + Rfq["Dex.Rfq"] --> MatchedTrade +``` + ### Reference: repository shape ```text diff --git a/docs/concepts/liquidity-and-custody.md b/docs/concepts/liquidity-and-custody.md index 27ab74c2..e749daa5 100644 --- a/docs/concepts/liquidity-and-custody.md +++ b/docs/concepts/liquidity-and-custody.md @@ -124,6 +124,30 @@ give a settle the authority to touch both the operator-signed slice state and the registrar-controlled LP tokens at once; that machinery is covered in [LP Tokens](lp-tokens.md). +## Worked example: one full cycle + +Concrete numbers make the flow easier to hold. Take a fresh `BTC/USDC` pool with +a 30 bps fee (`feeBps = 30`); every figure is what the on-ledger `Decimal` math +(scale 10, floored) produces. + +1. **Alice funds the pool** with `10.0 BTC` and `200,000.0 USDC`. That creates + two slices (one per side) and mints her the first LP supply, + `sqrt(10 · 200,000) = 1,414.2135623730` LP. Reserves: `10.0 BTC` / + `200,000.0 USDC`. +2. **Bob swaps `1.0 BTC`.** The fee is taken on the input, so `0.997 BTC` drives + the curve: `Δout = floor(0.997 · 200,000 / (10 + 0.997)) = 18,132.2178776029 + USDC`. The full `1.0 BTC`, fee included, stays in the pool. +3. **Reserves after the swap:** `11.0 BTC` / `181,867.7821223971 USDC`. The + product `x · y` has grown, and that growth is the fee — now owned by the LPs. +4. **Alice redeems.** She is the only LP, so burning all `1,414.2135623730` LP + returns the entire current reserves: `11.0 BTC` + `181,867.7821223971 USDC`. + She deposited `10 BTC + 200,000 USDC` and withdrew `11 BTC + 181,867.78 USDC`; + the difference is Bob's fee. + +[`PoolRoundingTests.daml`](../../trading-tests/CantonDex/Tests/PoolRoundingTests.daml) +guarantees the pool never pays out more than the exact floored amount, so these +figures are reproducible on-ledger. + --- ### Reference / details diff --git a/docs/concepts/overview.md b/docs/concepts/overview.md index fc983b36..616b3eba 100644 --- a/docs/concepts/overview.md +++ b/docs/concepts/overview.md @@ -82,6 +82,21 @@ results <- forA (Map.toList batchesByAdmin) $ \(batchAdmin, batch) -> do pure (batchAdmin, result) ``` +## Coming from EVM or Uniswap? + +If your mental model is Uniswap on an EVM chain, the table below covers most of +the surprises. The core shift: a token is not a balance in a shared contract but +an individual holding contract you own, and a trade is an atomic multi-party +settlement rather than a call into a router. + +| Uniswap / EVM | Canton DEX / Token Standard V2 | Key difference | +|---|---|---| +| `IERC20.approve(router, amount)` | `AllocationFactory_Allocate` | Locks specific holding contracts for one named settlement, not an open-ended balance allowance. | +| Router `swapExactTokensForTokens` | `PoolRules_Swap` + `SettlementFactory_SettleBatch` | The swap settles as one atomic multi-party batch: the input holding and the pool's reserve slice move in a single transaction. | +| LP balance in the pair contract | `LPTokenPolicy` + fungible LP `Holding`s | LP tokens are first-class V2 holdings in the provider's wallet, minted and burned by DvP, not a mapping entry. | +| `reserve0` / `reserve1` in the pair | `PoolState` (pricing) + `PoolSlice`s (custody) | Reserves are a `Decimal` accounting figure; the assets live in sharded committed `PoolSlice` allocations, which cut contention between concurrent operations. | +| Public mempool + global state | Per-party projection | There is no shared readable pool; each party sees only its own legs, and the operator drives settlement without ever holding custody. | + ## The authority boundary The one idea to carry into every other doc: **the operator never moves a diff --git a/docs/concepts/workflows.md b/docs/concepts/workflows.md index d3381156..8ae97f6b 100644 --- a/docs/concepts/workflows.md +++ b/docs/concepts/workflows.md @@ -264,6 +264,38 @@ exactly as expected, no locks stranded) and `testExpiryBetweenAcceptAndSettleBlocksTheSettle` (past the inherited deadline the settle fails and the funds stay locked). +## Contract lifecycles + +Two contracts carry an explicit status through their life; the others are created +and archived in a single step. These are their state machines. + +An **order** rests prefunded, matches (possibly in parts), and ends either filled +or cancelled: + +```mermaid +stateDiagram-v2 + [*] --> Pending: place (OrderFundingRequest_Bind) + Pending --> Funded: Order_Fund (allocation locked) + Funded --> PartiallyFilled: OrderMatchExecution_Execute (partial) + PartiallyFilled --> PartiallyFilled: further partial fills + Funded --> [*]: full fill, SettledTrade + PartiallyFilled --> [*]: full fill, SettledTrade + Funded --> [*]: Order_Cancel (allocation released) + PartiallyFilled --> [*]: Order_Cancel +``` + +A **pool** starts empty, becomes tradable once funded, and can be paused for an +emergency stop: + +```mermaid +stateDiagram-v2 + [*] --> Unfunded: pool created + Unfunded --> Active: first add-liquidity settles + Active --> Active: swap / add / remove + Active --> Paused: PoolRules_Pause + Paused --> Active: PoolRules_Resume +``` + --- ## Reference diff --git a/docs/getting-started.md b/docs/getting-started.md index f8ca5aac..7ab3866d 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -26,6 +26,11 @@ in-memory ledger, so you can have the full stack up in a few minutes. | `vendor/splice/dars/` | canonical Splice 0.6.12 Token Standard release DARs (committed build inputs) | Daml | | `docs/` | architecture, workflows, operator runbook, deployment, this page | — | +> **One-command sanity check.** After installing (below), `bash +> scripts/e2e-smoke.sh` boots the in-memory backend, exercises every key +> endpoint, verifies the responses, and exits non-zero on any failure — no Canton +> participant needed. + ## Prerequisites | Tool | Version | For | |---|---|---| diff --git a/docs/guides/builder-guide.md b/docs/guides/builder-guide.md index b34e25cb..8b577f26 100644 --- a/docs/guides/builder-guide.md +++ b/docs/guides/builder-guide.md @@ -183,6 +183,24 @@ Whatever you change, keep the layer boundary above intact: DEX contracts own mar structure, Token Standard contracts own reservation and settlement, registry contracts own asset semantics. +## Your first change + +A concrete loop for extending a choice — say, adding an optional referral party +to a swap: + +1. **Edit the Daml.** Append an `Optional` field (e.g. `referral : Optional + Party`) to `Pool`, or add a new `PoolRules_SwapWithReferral` choice — see + [Upgrade discipline](#upgrade-discipline) for why additions go at the end of + the record. +2. **Build the DAR:** `(cd trading && dpm build)`. +3. **Run the tests:** `(cd trading-tests && dpm test)`. The suite includes + `EndToEndTests.daml::testPoolSwapEndToEnd`, which exercises the full swap path + your change touches. +4. **See it consumed by an external project:** + [`examples/stable-pool/`](../../examples/stable-pool/) is a separate Daml + package that takes `canton-dex-trading-0.1.4.dar` as a data-dependency, so it + shows how a fork builds on the templates without editing them. + ## Upgrade discipline Keep the templates as small as possible; do not carry compatibility choices "just in diff --git a/docs/reference/http-api.md b/docs/reference/http-api.md index 7e3363a6..412b8778 100644 --- a/docs/reference/http-api.md +++ b/docs/reference/http-api.md @@ -449,6 +449,23 @@ partial / total settlement failure, the wallet relay returns **502** indexer- or config-gated routes return a bare-`{ error }` **503** when their store is absent. +### On-ledger assertions + +The codes above wrap the backend's own validation. A write can also fail inside +the Daml settlement, where the ledger returns the choice's assertion message. +The most common ones and how a client should handle them: + +| On-ledger assertion | Triggering condition | Suggested handling | +|---|---|---| +| `Output below slippage minimum` | the pool price moved against the taker between quote and settle, below the swap's `minOutputAmount` | re-quote and retry, or widen slippage tolerance | +| `expectedPoolId mismatch (pool config swapped?)` | the referenced pool contract is no longer the active one for the pair | refresh the client's pool cache and rebuild the request | +| `add: base reserve delta must equal created base slice amount` | reserve and slice arithmetic disagree during a liquidity settle (should be unreachable) | operator alert; run `PoolRules_ReconcileState` | +| `Allocation_Settle: settlement deadline has passed` | an order or RFQ allocation was settled after its deadline | release the reserved funds with the cancel / withdraw choice | +| `LP tokens below minimum` | ratio drift on add left the minted LP below the caller's floor | re-quote the deposit at the current pool ratio | + +These are the on-ledger messages; the backend surfaces them under a +`bad_request` or `internal_error` envelope depending on the route. + --- ## Examples