A production-ready, audited reference implementation of a Uniswap v2-style Automated Market Maker (AMM) for the Canton Network, written in Daml.
This project provides a canonical foundation for DeFi developers building on Canton. It is designed to be forked and customized, saving teams from building complex, security-critical exchange logic from scratch.
- Permissioned Pools: A designated
Operatorparty controls which asset pairs can be listed, enabling curated and compliant markets. - Constant Product Formula: Employs the elegant
x * y = kformula for decentralized price discovery and token swaps. - Liquidity Provider Fees: A standard 0.3% fee on all swaps is proportionally distributed to liquidity providers as a reward for their capital.
- Fungible LP Tokens: Liquidity providers receive fungible LP (Liquidity Pool) tokens representing their share in a pool. These tokens can be transferred, traded, or burned to reclaim the underlying assets and accrued fees.
- Audited & Verified: The core logic is designed with security as a priority, including formal verification of critical mathematical invariants to prevent exploits.
- Canton-Native: Built to leverage the unique privacy, auditability, and interoperability features of the Canton Network.
The system is composed of a few key Daml templates that work together to provide the AMM functionality.
PoolFactory: A singleton contract managed by theOperator. Its sole purpose is to create newPoolcontracts for approved token pairs, preventing duplicate pools for the same pair.Pool: The heart of the AMM for a single asset pair (e.g., TokenA/TokenB). It holds the token reserves, enforces the constant product formula, and facilitates all swaps and liquidity management operations.LpToken: A fungible token contract representing a fractional ownership of a specificPool. These are minted to liquidity providers when they add assets and burned when they withdraw them.
- Create Pool: The
Operatorexercises a choice on thePoolFactoryto create a newPoolfor a specific pair of assets. - Add Liquidity: A
LiquidityProviderparty exercises a choice on aPoolcontract. They deposit a proportional amount of both tokens and, in return, mint newLpTokens corresponding to their share. - Remove Liquidity: A
LiquidityProviderexercises a choice to burn theirLpTokens. In exchange, they withdraw their proportional share of the underlying tokens, including any fees that have accrued since they provided liquidity. - Swap: A
Traderparty exchanges a specific amount of one token for another through thePool. The output amount is calculated based on the constant product formula, and a 0.3% fee is left in the pool for liquidity providers.
Follow these steps to get the AMM running in your own Canton environment.
- Daml SDK v3.1.0
- A running Canton Network environment (e.g., from the
canton-quickstartrepository). gitcommand-line tools.
-
Clone the repository:
git clone https://github.com/digital-asset/canton-amm-reference.git cd canton-amm-reference -
Build the project: This command compiles the Daml code into a DAR (Daml Archive) file, which is the deployable artifact.
daml build
-
Run the tests: This script executes the test cases defined in the
daml/Testfolder to verify the correctness of the business logic.daml test
-
Upload the DAR to your Canton Participant: The
daml buildcommand creates a deployable artifact at.daml/dist/canton-amm-reference-0.1.0.dar. Use thedaml ledgercommand to upload this to your target participant node.daml ledger upload-dar --host <participant-host> --port <participant-port> .daml/dist/canton-amm-reference-0.1.0.dar
-
Initialize the Factory Contract: The system needs a single
PoolFactorycontract to get started. Run the provided Daml script to create this contract on the ledger, assigning theOperatorrole to a party you control.First, create
daml/Script.jsonwith the party ID of your operator:{ "operator": "OPERATOR_PARTY_ID::...." }Then, run the script:
daml script \ --ledger-host <participant-host> \ --ledger-port <participant-port> \ --dar .daml/dist/canton-amm-reference-0.1.0.dar \ --script-name Main:setup \ --input-file daml/Script.json
This will leave a single
PoolFactoryactive on the ledger, ready to create pools.
Once deployed, you can interact with the contracts using the Canton participant's JSON API.
- Endpoint:
POST /v1/exercise - Authentication: Include your JWT in the
Authorization: Bearer <token>header. - Request Body:
{ "templateId": "Amm.Pool:Pool", "contractId": "CONTRACT_ID_OF_THE_TARGET_POOL", "choice": "Swap", "argument": { "trader": "TRADER_PARTY_ID", "tokenInCid": "CONTRACT_ID_OF_THE_TOKEN_A_YOU_ARE_SENDING", "amountOutMin": "99.50" } }amountOutMinis a slippage protection parameter. The transaction will fail if the trader would receive less than this amount of the output token.
To guarantee the mathematical integrity and security of the AMM, we formally verify key properties of the system. This process uses mathematical proofs to ensure that certain undesirable states or behaviors are impossible.
Verified Invariants Include:
- Constant Product Preservation: The core
x * y = kinvariant is strictly maintained during swaps (accounting for fees). The product of reserveskis proven to only increase over time as fees accrue, benefiting liquidity providers. - No Value Extraction: It is impossible for a malicious actor to add and immediately remove liquidity to steal value from the pool.
- Fee Accrual Integrity: Fees are always positive, non-zero, and correctly added to the liquidity reserves.
Contributions are welcome! Please feel free to open a GitHub issue to discuss a new feature or bug, or submit a pull request with your proposed changes.