Please refer to our white paper for a more rigorous treatment on the subject.
At ALEX, we build DeFi primitives targeting developers looking to build ecosystem on Bitcoin, enabled by Stacks. As such, we focus on trading, lending and borrowing of crypto assets with Bitcoin as the settlement layer and Stacks as the smart contract layer. At the core of this focus is the automated market making ("AMM") protocol, which allows users to exchange one crypto asset with another trustlessly.
Trading Pool implements Generalised Mean Equation and, with a suitable parameterisation, supports both risky pairs (i.e.
Trading Pool is parameterised with a single parameter
Please note we use 8-digit fixed notation to represent decimals. If you interact directly with any of our contracts, you must provide all numbers in the correct format.
For example, 1 should be passed as 10,000,000 (= 1e8), i.e. 1.00000000.
A pair can be registered (i.e. a pool can be created) by calling create-pool
with the parameters including the traits of the two tokens (token-x
and token-y
), the factor pool-owner
) and the initial liquidity.
Trading Pool is permission-less in that anyone can register a pair with initial liquidity, so long as the two tokens are pre-approved (this is to prevent introducing malicious tokens to the platform).
Certain privileged functions are available to pool-owner
to govern the pool. The pool-owner
address is set at the time of a pool creation. ALEX DAO, as part of its governance, has the power to update and replace the pool-owner
address. Therefore, you can view this as ALEX DAO delegating the governance of each pool to its respective pool-owner
.
set-fee-rate-x
andset-fee-rate-y
: set the swap fee (% of swap amount) oftoken-x
andtoken-y
, respectively. Bothfee-rate-x
andfee-rate-y
are zero by default;set-start-block
andset-end-block
: set the block heights before and after, respectively, which the pool is not available. Bothstart-block
andend-block
is set tou340282366920938463463374607431768211455
by default;set-threshold-x
andset-threshold-y
: set the amount oftoken-x
andtoken-y
, respectively, below which a minimum % slippage is applied. Boththreshold-x
andthreshold-y
are zero by default;set-oracle-enabled
: add or remove the pool from the on-chain price oracle. Oracle is disabled by default;set-oracle-average
: set the exponential moving average factor for theoracle-resilient
. Please note this call will reset the existingoracle-resilient
value. Theoracle-average
is zero by default. We recommend 0.99e8.
Liquidity can be added to or removed from the pool any time by calling add-to-position
or reduce-position
, respectively.
When adding liquidity to a pool, you need to specify the amount of token-x and have the option of specifying the maximum amount of token-y you are willing to pair with token-x (i.e. slippage control). If adding liquidity requires more token-y than the maximum you specified, then the call will fail. You must also have at least the amount of token-x and the maximum amount of token-y in your wallet - otherwise the call will fail.
Once the liquidity is added, the pool will mint a pool token as a proof of proportional ownership of the pool liquidity. The number of the pool token being minted is proportional to the amount of liquidity you added compared to the existing liquidity at the pool.
The pool token is transferrable and may be used at other protocols (for example, as a collateral).
When removing liquidity from a pool, you need to specify the percentage of your pool tokens that you want to liquidate, i.e. between 0 and 1.
The percentage will be converted to the number of pool tokens to be burnt and the corresponding amount of token-x and token-y will be sent to you.
The pool token implements SIP013. Each pool is mapped to a unique id (pool-id
) with associated liquidity mapped to the balance under that id.
Trading Pool re-invests any fee rebates to the relevant pool liquidity, i.e. the invariant increases slightly after each transaction, similar to Uniswap V2.
Users can swap one token with another by calling swap-x-for-y
or swap-y-for-x
. As the names imply, swap-x-for-y
swaps token-x into token-y and swap-y-for-x
swaps token-y into token-x.
In both cases you can specify your slippage limit (min-dy
and min-dx
, respectively), so that the call fails if the swapped amount does not meet your target.
Fee is deducted from each transaction on the "in" leg, i.e. token-x for swap-x-for-y
and token-y for swap-y-for-x
. Fee is set at the pool creation and may be updated through the governance.
Part of the fee may be rebated to liquidity providers as a reward.
It may not be reasonable to expect developers or users to remember the correct order of token pairs. Therefore we provide swap-helper
function that helps choose between swap-x-for-y
and swap-y-for-x
and swaps token-x into token-y without users having to know the correct order.
It is also useful to be able to combine multiple swaps into one. For example, it will be useful to be able to swap xUSD into xBTC in one transaction, based on two pools of STX/xUSD and STX/xBTC, instead of having to perform two swaps. To that end, we provide three helper functions - swap-helper-a
, swap-helper-b
and swap-helper-c
that facilitates "multi-hop" swaps of two/three/four pools, respectively.
In addition to the swap helpers and routing functions, we provide a few helpful functions.
Trading Pool provides two types of on-chain oracles - instant and resilient. Their implementations are similar to Uniswap V2.
Instant oracle (get-oracle-instant
) gives you the latest pool-implied price (i.e. most up-to-date), but is subject to a higher manipulation risk. Instant oracle may be suitable for, for example, arbitrage or liquidation.
Resilient oracle (get-oracle-resilient
) on the other hand gives you a trade-weighted price that is therefore more resilient to potential manipulation but is less up to date. Resilient oracle may be more suitable for, for example, benchmarking to lending and borrowing.
Sometimes you may want to know the expected amount of token-y if you were to swap certain amount of token-x. Or you may want to know the expected amount of token-x for some token-y. Two read-only functions - get-y-given-x
and get-x-given-y
will do that for you.
If you want to know the amount of token-x you may need to provide to get a target amount of token-y (or vice versa), you can use get-x-in-given-y-out
and get-y-in-give-x-out
, respectively.
If you are an arbitrageur, you may want to know the amount of token-x or token-y you need to provide to rebalance the pool-implied price to a target. In such a case, you can use get-x-given-price
or get-y-given-price
.
Lastly, it will be useful to know, for example, to determine the slippage limit, how many token-x and token-y must be provided to mint certain number of pool tokens, to burn certain number of pool tokens, or how many pool tokens may be minted or burnt if certain number of token-x and token-y are provided. The relevant helper functions are get-position-given-mint
, get-position-given-burn
and get-token-given-position
.