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: 3 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ For the ideas behind the design, read the **[Overview](concepts/overview.md)**.
| **Operate a venue** | [Deployment](guides/deployment.md) → [Operator Guide](guides/operator-guide.md) → [Operator Runbook](guides/operator-runbook.md) |
| **Integrate a registry** | [Registry Integration](guides/registry-integration.md) → [Choice Context](guides/choice-context.md) → [Allocation Surface](reference/allocation-surface.md) |
| **Trade in the dApp** | [Using the dApp](guides/using-the-dapp.md) |
| **Evaluate / review it** | [Overview](concepts/overview.md) → [Architecture](concepts/architecture.md) → [Glossary](concepts/glossary.md) |
| **Evaluate / review it** | [Overview](concepts/overview.md) → [Architecture](concepts/architecture.md) → [Non-goals](concepts/non-goals.md) → [Ecosystem feedback](reference/ecosystem-feedback.md) |

---

Expand All @@ -54,6 +54,7 @@ lookup (reference).
| [LP Tokens](concepts/lp-tokens.md) | Builder, integrator | Why LP tokens are a single, unversioned V2 instrument per pool. |
| [Pricing](concepts/pricing.md) | Operator, integrator | Where prices come from — pool-derived, order book, RFQ — and the (absent) oracle attachment points. |
| [Glossary](concepts/glossary.md) | Everyone | The key terms: allocation, commitment, iterated settlement, DvP, slice, registrar, and more. |
| [Non-goals](concepts/non-goals.md) | Everyone | What the reference intentionally does not include, and why. |

### Guides — do a task
| Page | Audience | Recipe |
Expand All @@ -76,6 +77,7 @@ lookup (reference).
| [HTTP API](reference/http-api.md) | The operator-backend HTTP endpoints, wallet intents, and error codes. |
| [Allocation Surface](reference/allocation-surface.md) | The V2 allocation surface this reference relies on (committed allocations, iterated settlement). |
| [Testing](reference/testing.md) | The real-ledger, JSON Ledger API end-to-end test driver. |
| [Ecosystem feedback](reference/ecosystem-feedback.md) | How the reference was evaluated externally, what was found, and what changed. |

---

Expand Down
5 changes: 3 additions & 2 deletions docs/concepts/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

Canton DEX is a token-standard-native reference DEX for Canton.

It is intentionally not a generic settlement engine. The goal is to show
builders how to build a real exchange directly on top of:
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:

- Token Standard V2 allocations and batch settlement
- registry-backed holdings whose registries implement the V2 holding,
Expand Down
107 changes: 107 additions & 0 deletions docs/concepts/non-goals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# What this reference does not include, and why

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
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
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
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).

## One registry admin per pair

A trading pair carries a single `admin : Party` covering both its base and quote
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
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.

## Not a production matching engine

Order matching is a batch process the operator runs (`runMatching` in
`services/operator-backend/src/order/index.ts`), not a continuous in-ledger
matching loop. It clears crossing orders best-price-then-time, settles each match
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.

## 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
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
one exists for every registry. A guide for issuing a lifecycle-richer instrument
is included ([../guides/add-lp-or-instrument.md](../guides/add-lp-or-instrument.md)),
but the reference itself stays at the minimum the DEX needs.

## The reference registry is one option, not the mechanism

`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
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
work against Amulet or any other conforming registry.

## The hosted testnet is a demo surface, not a wallet

The public deployment lets a visitor with no wallet trade, by minting a hosted
demo party and relaying its signatures through a fixed, allowlisted set of
choices under per-IP and daily caps. This is explicitly a testnet convenience,
not self-custody: the two connect options are marked **DEV**. A real user brings
their own wallet (PartyLayer or the dapp-sdk) and signs for themselves; the hosted
relay exists so the milestone flows can be exercised from a browser without one.
The hosted onboarding routes and their caps are documented in
[../guides/operator-runbook.md](../guides/operator-runbook.md).

**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
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
piece specific to this interim.

## Operational hardening is out of scope

The reference includes an operator runbook covering deployment, recovery and
observability, but it is not a hardened production service. There is no HA, no
rate-limited public gateway beyond the testnet caps, no secrets-management
integration, and the operator's authority is a single party. These are an
operator's deployment decisions, deliberately left to whoever runs an instance
rather than baked into the reference.

## Off-chain services are illustrative

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
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").
7 changes: 7 additions & 0 deletions docs/guides/using-the-dapp.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ local dev builds, never in production or testnet.
Once connected, your party id appears in the top bar. The wallet
provider persists across reloads (session is stored in `localStorage`).

On the public testnet at `testnet-dex.bitdynamics.cc`, testers are onboarded as
hosted parties on the operator's (BitDynamics) validator, and the traded assets
are issued locally by the deployment's own Token Standard V2 registry. This is an
interim arrangement until the general-purpose validator and wallet tooling (DA
Utilities) supports Token Standard V2, at which point users bring their own party
and V2 assets. See [Non-goals](../concepts/non-goals.md#the-hosted-testnet-is-a-demo-surface-not-a-wallet).

---

## Swap (Trade page)
Expand Down
96 changes: 96 additions & 0 deletions docs/reference/ecosystem-feedback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Ecosystem feedback and resulting design changes

This page records how the reference implementation was evaluated by external
parties, what they found, and what changed as a result. It is maintained as the
single summary of that loop.

## External integration (reuse proof point)

The reference DEX is integrated as an adapter in
[**canton-trading-toolkit**](https://github.com/olevasyliev/canton-trading-toolkit),
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
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
path open to a party with no wallet of its own.

The integration is reproducible from outside with no operator credentials:

```
git clone https://github.com/olevasyliev/canton-trading-toolkit
cd canton-trading-toolkit && pip install -e .
PYTHONPATH=src python3 scripts/dexref_testnet_report.py # reads only
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.

## Evaluation and feedback

The integrator ran six rounds against the hosted testnet between 2026-07-27 and
2026-07-29, plus an earlier round against the repository's local demo mode. Each
round is a scripted run of dozens of assertions measured through the public
routes. The reports are public:

- Hosted testnet report:
[srikanth-bitdynamics/Canton-Dex-Reference-Implementation#126](https://github.com/srikanth-bitdynamics/Canton-Dex-Reference-Implementation/issues/126)
- Local demo mode report:
[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.

## 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
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
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
buys (F16); `/v1/trades` includes `counterparty` after the deployment was brought
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
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
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`)
so matching and its atomic settlement can be verified from outside (F24);
`/v1/swaps` gained `?kind=` so liquidity events, not just swaps, are readable
(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:
`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
now applies self-trade prevention and no longer proposes it.

## How this loop is expected to continue

The reference tracks the same standard the ecosystem builds against, and its
hosted testnet is open for exactly this kind of evaluation. New reports open as
issues on the implementation repository; confirmed findings are fixed with a
regression test and this summary is updated.
Loading