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.
), stable pairs (i.e.
) and any linear combination in-between (i.e. Curve).
Trading Pool is parameterised with a single parameter
can be between 0 and 1, with
being equivalent of constant product formula (i.e. Uniswap V2) and
being equivalent of constant sum formula (i.e. mStable).
then gives a Curve-like formula.
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-poolwith the parameters including the traits of the two tokens (
token-y), the factor
, the governance address (
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-ownerto govern the pool. The
pool-owneraddress 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-owneraddress. Therefore, you can view this as ALEX DAO delegating the governance of each pool to its respective
set-fee-rate-y: set the swap fee (% of swap amount) of
token-y, respectively. Both
fee-rate-yare zero by default;
set-end-block: set the block heights before and after, respectively, which the pool is not available. Both
end-blockis set to
set-threshold-y: set the amount of
token-y, respectively, below which a minimum % slippage is applied. Both
threshold-yare 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 the
oracle-resilient. Please note this call will reset the existing
oracle-averageis zero by default. We recommend 0.99e8.
Liquidity can be added to or removed from the pool any time by calling
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-y-for-x. As the names imply,
swap-x-for-yswaps token-x into token-y and
swap-y-for-xswaps token-y into token-x.
In both cases you can specify your slippage limit (
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-yand token-y for
swap-y-for-x. Fee is set at the pool creation and may be updated through the governance.
It may not be reasonable to expect developers or users to remember the correct order of token pairs. Therefore we provide
swap-helperfunction that helps choose between
swap-y-for-xand 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-cthat 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-x-given-ywill 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
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
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