Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ and runbooks for RFQs, prefunded orders, pools, swaps, and LP tokens.

<p>
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache_2.0-blue.svg" alt="License: Apache 2.0" /></a>
<a href="https://daml.com"><img src="https://img.shields.io/badge/Daml-3.4.11-orange.svg" alt="Daml SDK 3.4.11" /></a>
<a href="https://daml.com"><img src="https://img.shields.io/badge/Daml-3.5.2-orange.svg" alt="Daml SDK 3.5.2" /></a>
<a href="https://github.com/canton-network/splice"><img src="https://img.shields.io/badge/Canton-Token_Standard_V2-blueviolet.svg" alt="Canton Token Standard V2" /></a>
</p>

Expand Down Expand Up @@ -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

Expand Down
31 changes: 31 additions & 0 deletions docs/concepts/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions docs/concepts/liquidity-and-custody.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions docs/concepts/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 32 additions & 0 deletions docs/concepts/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
|---|---|---|
Expand Down
18 changes: 18 additions & 0 deletions docs/guides/builder-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions docs/reference/http-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading