Registry integration prerequisites
What the DEX assumes from an asset registry. Token Standard V2 standardizes the
holding/allocation/settlement interfaces; it does not standardize a particular
instrument-configuration or lifecycle template. This document therefore
separates hard V2 interface requirements from the reference registry’s optional
InstrumentConfig model in trading/CantonDex/Registry/V2.daml.
The registry boundary
Section titled “The registry boundary”The DEX touches a registry through exactly four surfaces. Everything else about your asset — issuance policy, precision, lifecycle, credential rules — stays behind that line, and the DEX never reaches across it.
flowchart LR
subgraph DEX["DEX (this repo)"]
W["Trader wallet"]
OB["Operator backend"]
end
subgraph REG["Asset registry (yours)"]
AF["AllocationFactory"]
SF["SettlementFactory"]
H[("Holding")]
CC(["Operation-specific V2 endpoints (off-ledger HTTP)"])
end
W -->|"AllocationFactory_Allocate locks holdings into an Allocation"| AF
OB -->|"SettlementFactory_SettleBatch atomic net settlement"| SF
W -.->|"observe / select"| H
AF --> H
SF --> H
OB -.->|"POST exact choiceArguments"| CC
CC -.->|"factory + context + disclosures"| SF
Solid arrows are on-ledger interface choices; dashed arrows are off-ledger reads. The two choices are the whole on-ledger contract the DEX depends on:
-- AllocationInstructionV2.daml -- the trader locks funds under their own authoritynonconsuming choice AllocationFactory_Allocate : AllocationInstructionResult with settlement : SettlementInfo allocation : AllocationSpecification requestedAt : Time inputHoldingCids : [ContractId Holding] extraArgs : ExtraArgs actors : [Party] ...
-- AllocationV2.daml -- the operator settles a batch of allocations atomicallynonconsuming choice SettlementFactory_SettleBatch : SettlementFactory_SettleBatchResult with settlement : SettlementInfo transferLegs : [TransferLeg] allocations : [FinalizedAllocation] actors : [Party] extraArgs : ExtraArgs ...The trader exercises AllocationFactory_Allocate under their own authority to
lock holdings into a V2.Allocation; the operator exercises
SettlementFactory_SettleBatch to move the net amounts atomically. extraArgs
on both choices is where the registry’s choice context —
disclosed config and credential contracts — rides along. The DEX only ever
reads a holding through the V2.Holding interface (account, instrumentId,
amount, lock); the registry alone mints, locks, splits, and merges it. For
the exact allocation-surface fields the DEX sets and reads on these choices, see
Allocation Surface.
What the registry guarantees
Section titled “What the registry guarantees”Those surfaces rest on a small set of guarantees. For every instrument the DEX trades, the registry must provide:
-
A stable
InstrumentIdand admin. The DEX keys orders, pools, RFQs, and matched trades byInstrumentId. In the reference registry this information lives onInstrumentConfig; another registry may expose it through metadata, discovery APIs, or disclosed config contracts. -
Holdings as registry-side templates implementing the V2 holding interface. The DEX never mints or burns its own holdings (except LP tokens, which have their own
lpRegistrar); it observes the registry’s holdings. -
An allocation factory implementing
V2.AllocationFactoryfor the admin’s instruments. The trader exercisesAllocationFactory_Allocateunder their own authority to lock their holdings into aV2.Allocation. -
A settlement factory implementing
V2.SettlementFactoryfor the admin’s instruments. The DEX operator exercisesSettlementFactory_SettleBatchto atomically settle batches of allocations.
What the DEX assumes from those guarantees
Section titled “What the DEX assumes from those guarantees”| Assumption | Where it shows up |
|---|---|
instrumentId is stable across the instrument’s lifetime |
Order, Pool, MatchedTrade, Rfq all key on it |
| Factory and choice-context discovery is admin-controlled | The app performs a fresh operation-specific V2 lookup with the concrete choice arguments; it does not reuse one admin-level cached context across operations |
| Allocation creation can consume one or more holdings and return change | The trader’s wallet selects holdings; the registry factory validates and locks them |
Allocation factory accepts arbitrary AllocationSpecification shapes (prefunded, with-legs, committed or uncommitted, with nextIterationFunding) |
Orders require both deadline-committed and trader-withdrawable GTC shapes; pools require committed inventory |
| Settlement factory enforces transfer-leg consistency with allocations | OTC / matched-trade settlement and PoolRules_Swap rely on the factory to validate, not the DEX |
Allocation lifetime caps
Section titled “Allocation lifetime caps”Registries may bound how long an allocation or instruction can live. This matters because pool slices are long-lived committed allocations and expiring orders may also outlive a registry’s cap. An integration must discover the registry’s current limit, keep requested settlement deadlines inside it, and rotate pool slices before they expire. The reference registry does not impose a TTL; that does not imply another registry will accept the same lifetime.
Registry API surface (Daml + OpenAPI)
Section titled “Registry API surface (Daml + OpenAPI)”Token Standard V2 registries are expected to expose both the Daml interfaces
and the standard OpenAPI endpoints. The specs used here are committed beside
the vendored packages under vendor/splice/token-standard.
The backend client uses the canonical operation-specific POST endpoints for
allocation-factory discovery, settlement-factory discovery, and per-allocation
cancel/withdraw context. Every factory request includes the concrete Daml JSON
choiceArguments; responses are runtime-validated and are not cached. See
Choice context for the exact
paths, bodies, and response shape.
The configured reference self-registry is a deliberate adapter, not a second
HTTP protocol. FixedRegistryClient resolves deployed factory CIDs per admin
and returns empty context. This is also the only backend adapter currently able
to drive atomic add/remove liquidity: those Daml choices create temporary
allocations and settle them in the same transaction, so their future CIDs
cannot appear in an exact HTTP preflight request. Generic HTTP discovery fails
with RegistryError("unsupported", ...) before submission for that workflow.
Swaps, matched trades, order matches, allocation creation, and cancellation use
the canonical operation-specific discovery path.
The DEX’s own flows are exercised against a standard-shaped registry, not only
its reference one. testMatchedTradeViaTokenStandardRegistry in
TokenStandardHarnessTests.daml
drives the matched-trade flow through the upstream RegistryApiV2
factory-discovery path — proving the DEX composes over a standard registry, not
a bespoke one. testRealRegistryDvpAddSettles and testRealRegistryDvpSwapSettles
in RealRegistryDvpTests.daml
settle add-liquidity and swap DvPs against a genuinely context-requiring
registry, and testRealRegistryDvpRejectsMissingContext proves the settle
aborts when that registry’s disclosed context is dropped. These are Daml
composition proofs: the tests already possess the context contracts. They do
not remove the off-ledger future-CID limitation for the backend’s atomic
liquidity HTTP preflight.
Mint / Burn / Transfer prerequisites
Section titled “Mint / Burn / Transfer prerequisites”The active reference registry keeps these surfaces distinct:
Registry_RegisterInstrument,Registry_Mint, andRegistry_Burnare registry-specific administration choices. Token Standard V2 does not define instrument registration or issuance policy.- Peer-to-peer transfers use the standard
V2.TransferFactoryandV2.TransferInstructioninterfaces implemented byRegistry.V2. - DEX trades do not call the mint/burn administration choices for base or quote assets. They consume V2 holdings through allocation and settlement choices.
- LP mint and burn are DvP legs under
lpRegistrar, recorded byLPTokenPolicy; they do not use a parallel holding template.
What the registry MUST enforce for iterated settlement
Section titled “What the registry MUST enforce for iterated settlement”The DEX uses iterated settlement: the authorizer opts in by creating an
allocation with nextIterationFunding = Some ..., and the settlement
executor supplies concrete trade leg-sides in
FinalizedAllocation.extraTransferLegSides when calling
SettlementFactory_SettleBatch.
This pattern places funds under executor control between iterations. To keep the executor from misusing those funds, the registry’s settlement implementation MUST enforce funding conservation in Daml, not in operator code:
- Reject extra settlement leg-sides when the allocation was not iterated-enabled. Iterated settlement is opt-in by the authorizer.
- Every extra leg-side must involve the authorizer as sender or receiver. Legs between unrelated parties cannot be smuggled into the authorizer’s allocation.
- Per-instrument net outflow from the authorizer must not exceed the
current
nextIterationFunding[instrumentId]. Self-transfer legs (sender == receiver == authorizer) net to zero. - Any next-iteration allocation produced by settlement must carry an
updated
nextIterationFundingreduced by the consumed amount per instrument. This is the double-spend guard for follow-on iterations. - The DEX operator (settlement executors) must be able to observe the allocation lifecycle so each settlement iteration is visible to 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 intent.
The reference registry
Registry.V2 enforces all five in
Daml, inside allocation_settleImpl and settlementFactory_settleBatchImpl.
RegistryConservationTests.daml
proves them against that implementation:
testExtraLegBeyondBackingRejected— executor-supplied extra leg-sides cannot draw more than the allocation’s locked backing.testNextFundingBeyondBackingRejected—sent + nextIterationFundingis bounded by that backing.testRollForwardCarriesLockedBacking/testSecondIterationCannotExceedFunding— each roll-forward is backed by freshly locked holdings worth its funding, so a follow-on iteration can spend only what was reserved (the double-spend guard).testBatchRejectsMissingAuthorization/testBatchRejectsSuperfluousAuthorization/testBatchRejectsUnbalancedReceiverLeg— the batch settles every leg-side with exactly one allocation and balances per instrument.
The testing-only
MockRegistry.daml
deliberately skips these checks: it tracks no holdings and exists to exercise
flows that compose the V2 calls, not the authorization model. Production
registries are expected to enforce at least what Registry.V2 does.
Choice-context retrieval the DEX needs
Section titled “Choice-context retrieval the DEX needs”When the operator or trader builds a transaction that touches a registry contract, the registry may require extra disclosed contracts or context. The reference self-registry’s context is empty. External registries may return disclosed configuration, rights, or credential contracts.
The DEX’s registry-client takes the exact operation arguments, calls the
matching standard endpoint, validates the wire response, and returns the
factory CID, context, and disclosures as one value. Settlement arguments come
from non-value-moving Daml previews for swaps and matched trades, and from an
ephemeral create-and-exercise preview for order matches. Cancel/withdraw
context is looked up per allocation ID, not once per admin. See
Choice context for the complete choreography and the
atomic-liquidity exception.
Registry-specific lifecycle changes
Section titled “Registry-specific lifecycle changes”Token Standard V2 does not standardize instrument lifecycle or force-upgrade
workflows. The DEX therefore makes no claim that holdings automatically migrate
when a registry changes an instrument. A registry integration must document
whether it preserves the same InstrumentId, replaces holdings, or requires a
new instrument identity.
The safe DEX behavior is deliberately small: do not persist holding contract ids in UI state, refresh holdings before authoring an allocation, and cancel or relist market objects when the registry says their instrument version is no longer tradable. Any automatic upgrade-on-use or issuer-driven migration is a custom registry feature outside this reference.
Known limitation: one registry admin per pair
Section titled “Known limitation: one registry admin per pair”Both instruments of a pair share a single registry admin. DexPair,
Order, Pool and MatchedTrade each declare one admin : Party, and
baseInstrumentId / quoteInstrumentId are bare Text interpreted under it.
This follows the standard: TransferLeg.instrumentId is Text, so a leg
carries no admin of its own and one cannot be recovered from a settled trade.
MatchedTrade_RequestAllocations emits one AllocationSpecification per
authorizer under that one admin.
MatchedTrade_Settle does take batchesByAdmin : Map Party SettlementBatchV2
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). 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
on those four templates and one specification per (authorizer, admin). That
is a schema change, not a configuration option.
The per-admin batching this describes is load-bearing and tested:
testMatchedTradeSettlesPerAdminLegSubsets in
RealRegistryDvpTests.daml
settles a trade’s legs across two real registries in one transaction when they
are partitioned by admin, and proves a batch handed the full leg list — or no
legs — is rejected rather than settled.
What the DEX does not assume
Section titled “What the DEX does not assume”- It does not require the reference registry for base or quote assets in the
allocation, swap, order, or matched-trade flows. An alternative must
implement the V2 holding, allocation, and settlement APIs and the canonical
operation-specific discovery endpoints. Atomic add/remove liquidity is the
documented exception: the current backend requires the configured
empty-context self-registry adapter until that workflow is redesigned. The
included LP path also uses the concrete
LPTokenPolicycomponent. - It does not assume holding precision is uniform. Each registry may expose its
own display scale or amount constraints; the DEX treats amounts as
Decimaland lets the registry enforce its own limits. - It does not implement deployment-grade credential verification. The
Registry.V2credential record is an explicit placeholder; a production registry must resolve and verify issuer-authorized evidence itself.
Where to read next: Choice Context · Allocation Surface · All docs