Smart Contracts
The contracts are published in the Liquidswap GitHub repository at https://github.com/pontem-network/liquidswap.
Liquidswap uses smart contracts written in the Move language and executed by Move VM. The core contracts containing the protocol's logic and safety guarantees and are separated from the periphery contracts, allowing for a well-tested, reasonably small core layer and multiple versions of the periphery that can work with the core utilizing their own custom logic.
For example, the basic Router.move
, used by Liquidswap's frontend, proxys all Liquidity Pool module methods with additional safety and price checks. At the same time, traders who want to minimize gas usage can write their own custom routers or call the core contracts directly.
Branches and versions
The current main
branch is the development branch and always contains the latest changes.
Release branches contain the latest changes created for the specific release.
The current release tag v0.4.4
are deployed and released on Aptos mainnet.
You can always find the latest release in the Releases section.
Routers/scripts versions
As router and scripts are part of periphery we can update router/scripts and release new version time to time, so there can be many routers versions and releases. All official routers/scripts are part of Liquidswap core repository.
Router v1
Not recommended to use! ⚠️
Source code - ./sources/swap/router.move
It contains a u64 overflow bug in get_amount_in
and get_amount_out
, so not advised to use, as both functions can return overflow errors in case numbers are too high.
Router v2
Source code - ./liquidswap_router_v2
The Router v2 contains bug fixes for functions in router v1, also scripts are updated too.
Optional routers
Router v3 - contains bug fixes with accuracy (1-2 units) for stable swaps (see Zellic audit): functions to get the amount in or out. Yet, we aren't going to migrate as the solution would eat more gas. The router available in branch and deployed to mainnet/testnet/devnet.
Addresses
All liquidity pools resources and LP coins currently placed at the following resource account:
Liquidswap modules are deployed at the following address:
All smart contracts and dependencies (exclude that placed on 0x1
address) are immutable.
Devnet
For Aptos Devnet support, we used specific different addresses, to be able to deploy contracts each devnet reset.
Use the devnet-addresses branch, look at Move.toml to get details: dependencies also has it's own devnet branch, while test coins has the same addresses from testnet.
Integrations
This document explains how Liquidswap contracts are organized at the top level.
If you are already familiar with this material, you can proceed to the integrations docs.
Liquidity Pool
Source Code: ./sources/swap/liquidity_pool.move.
The Liquidity Pool module implements all swap logic, LP logic, math (constant product formula), and core checks.
The core part of the Liquidity Pool contract is a resource that describes the liquidity pools themselves and contains all the required information:
Reserves of both tokens
X
andY
.Curve type:
phantom Curve
.Cumulative price information.
Mint and burn capabilities for
LP
coins.Token decimal scales (used for stable swaps).
If a pool is locked (needed for the flashloans).
Almost like Uniswap the Liquidswap creates pools on the reserved address:
All pools are unique, so there can't be two pools containing the same coins, and it also works for LP and LP generics.
To avoid confusion, Liquidswap uses a frontend interface with a list of pools already pre-filled and a pools registry to show the users mostly verified pools.
Generics
A set of (X, Y, Curve)
uniquely identifies a liquidity pool on the blockchain.
Because of that, all functions that refer to pools have three generic parameters:
X, Y
- the tokens to be swapped in a pool, for example,0x1::aptos_coin::AptosCoin
andtest_coins::coins::USDT
(wrapped USDT)Curve
- type of the Curve corresponding to the pool, explained below.Note: Generics X and Y must be sorted, see Coin Sorting.
Functions
In most cases, you won't need to use the LiquidityPool
module itself. The Router
module provides high-level wrappers around LiquidityPool
, which simplify most of the tasks.
Operations with liquidity:
register<X, Y, Curve>
- creates fora new liquidity pool on reserved account.mint<X, Y, Curve>
- mints new LP coins in exchange forCoin<X>
andCoin<Y>
.burn<X, Y, Curve>
- burns some LP coins in exchange forCoin<X>
andCoin<Y>
.swap<X, Y, Curve>
- swapsCoin<X>
orCoin<Y>
or both to getCoin<X>
orCoin<Y>
or both in exchange.flashloan<X, Y, Curve>
- flashloan reservesCoin<X>
and/orCoin<Y>
from the pool, also returnsFlashloan
object.pay_flashloan<X, Y, Curve>
- pay flashloan by returningCoin<X>
,Coin<Y>
andFlashloan
object.
Getters:
is_pool_locked<X, Y, Curve>
- returns bool determining if the pool is locked (flashloaned).get_reserves_size<X, Y, Curve>
- returns the current reserves size for bothCoin<X>
andCoin<Y>
.get_curve_type<X, Y, Curve>
- returns the curve type for the pool.get_decimals_scales<X, Y, Curve>
- returns the decimal scales for bothCoin<X>
andCoin<Y> - but
correct values are returned only for stable pools.pool_exists_at<X, Y, Curve>
- returns bool determining if the pool exists.get_cumulative_prices<X, Y, Curve>
- return cumulative prices and the last block's timestamp.get_fees_config<X, Y, Curve>
- returns fees config for the pool (both nominator fee and denominator).get_fee<X, Y, Curve>
- return the fee nominator for pool.get_dao_fees_config<X, Y, Curve>
- returns dao fees config for the pool (both nominator fee and denominator).get_dao_fee<X, Y, Curve>
- return the dao fee nominator for pool.
Coin Sorting
To work with Liquidity Pool module functions, you must sort the coins you pass on as generics.
Sorting is important as it brings in the rules for creating liquidity pools. All functions in the Liquidity Pool module accept only sorted generics; otherwise, it reverts.
For the Router and Scripts modules, only part of the functions requires generics to be sorted.
The current sorting algorithm takes the types of provided coins, e.g., address::module::struct_name
, and compares the struct name of both coins. If it's equal, it continues with the module name and, in the end, with the address.
You always can just use the implementation in Coin Helper module: coin_helper.move.
Other languages:
Implementation in Javascript: Coin Sorting In JS.
Also, it's supported in Typescript SDK.
Curve types
Source code: sources/swap/curves.move
When creating a pool, the curve type can be provided; it's as simple as using a generic to determine the pool type.
The types themself:
liquidswap::curves::Uncorrelated
- Uncorrelated curve type.liquidswap::curves::Curve
- Stable curve type.
The following types can be imported into code and should be used as the last generic argument in almost all modules functions.
Functions:
is_uncorrelated<Curve>
- returnstrue
if provided type isUncorrelated
.is_stable<Curve>
- returns true if provided type isStable
.is_valid_curve<Curve>
- returns true if provided type isStable
orUncorrelated
.assert_valid_curve<Curve>
- abort if provided type is not a curve type.
You can read more about Liquidswap's curve formulas on the Protocol Overview page.
LP coins
Source code: - liquidswap_lp/sources/lp_coin.move
Consider that LiquidswapLP is an independent module inside the liquiswap repository, so it must be imported as a separate dependency.
The LP
coins type for pairs X
and Y
is represented as LP<X, Y, Curve>
and deployed on the following resource account:
The LP coin registers automatically for each new liquidity pool, and generics are always sorted.
For APT/BTC
uncorrelated pool, the LP coin would look so (the coins addresses are testnet) :
Flashloans
The implementation is based on Move's loan concept, where a Move object containing the loan data is issued but cannot be stored, copied, cloned or dropped, the only available action being to return the object back to the pay_flashloan
function, which will verify the resulting constant product function.
The concept was explained early on by the Pontem team in this Medium article.
Read more in the integration section.
Routers
Source code - ./liquidswap_router_v2/sources/router_v2.move
It's recommended to use Router v2 ⚠️ Read about router versions.
The Router module is a periphery layer on top of the Liquidity Pool module.
The Router contains additional checks to verify that the amount that a developer, trader, or liquidity provider wants to exchange/mint/burn is reasonable.
Also, the module sorts tokens or coins automatically for part of functions and has several useful getters that help estimating the swap price.
In most cases, we recommend using a Router if you want to work with the Liquidity Pool module.
Third-party teams can provide their own routers, but Liquidswap's standard Router should be enough for most cases. An added advantage is that it's already audited.
Functions
The router functions accept the resources Coin<X>,
Coin<Y>
, Curve
similar to Liquidity Pool module.
However, the functions cannot be called directly from a transaction; if you need entry points, refer to Scripts.
Developers interacting with swap functions and getters can order generics in any way they wish. For example, if one wants to swap aptos_framework::aptos_coin::AptosCoin
to test_coins::coins::USDT, one
can just use the function:
A reverse swap (USDT
-> APTOS
) can be done by reordering the generics:
Important note: the rest of functions (register_pool
,add_liquidity
, remove_liquidity
) requires sorted generics!
Liquidity operations:
Requires sorted generics:
register_pool<X, Y, Curve>
- register a new pool withCoin<X>
andCoin<Y>
as reserves and provided curve.add_liquidity<X, Y, Curve>
- add liquidity (Coin<X>
andCoin<Y>
) to an existing pool.remove_liquidity<X, Y, Curve>
- burnCoin<LP<X ,Y, Curve>>
and getCoin<X>
andCoin<Y>
back with checks.
Generics can be sorted in any way:
swap_exact_coin_for_coin
- swap an exact amount ofCoin<X>
to get no less than the specified amount ofCoin<Y>
.swap_coin_for_exact_coin
- swap no more than the specified maximum amount ofCoin<X>
to get an exact amount ofCoin<Y>
.swap_coin_for_coin_unchecked
- simply swaps tokens without any checks.
Getters:
get_amount_out<X, Y, Curve>
- estimate the amount ofY
tokens resulting from a swap of a specified amount inX
tokens. Can consume a lot of gas when used for stable pools.get_amount_in<X, Y, Curve>
- estimate the amount ofX
tokens needed to get a specified amount inY
tokens.calc_optimal_coin_values<X, Y, Curve>
- calculates the optimal amounts ofCoin<X>
andCoin<Y>
needed to add liquidity and get a fair amount of LP coins.get_reserves_size<X, Y, Curve>
- returns the reserves for both coin X and coin Y held by the pool.pool_exists_at<X, Y, Curve>
- returns bool determining if the pool exists.get_decimals_scales<X, Y, Curve>
- returns the pool's decimals scale. The resulting values will be correct only for stable pools.get_cumulative_prices<X, Y, Curve>
- returns cumulative prices and the last block's timestamp.get_reserves_for_lp_coins<X, Y, Curve>
- returns the amounts inCoin<X>
andCoin<Y>
that a user will receive after burning LP coins.get_fees_config<X, Y, Curve>
- returns fees config for the pool (both nominator fee and denominator).get_fee<X, Y, Curve>
- return the fee nominator for pool.get_dao_fees_config<X, Y, Curve>
- returns dao fees config for the pool (both nominator fee and denominator).get_dao_fee<X, Y, Curve>
- return the dao fee nominator for pool.
Scripts
Source code - ./liquidswaprouter_v2/sources/scripts_v2.move
The top-level module contains the entry functions that users can execute directly by sending transactions and that third-party modules can use via &signer
.
Summary:
Requires a signer;
Accepts numbers as arguments representing coins' values;
Extracts coins/tokens directly from the signer account;
Registers a LP token on the account if it's not registered;
Works with default Router v2.
This is the optimal way to interact with Liquidswap if you want to call a swap from the CLI or the UI using the standard router.
Functions
All the functions have 'slippage' amount arguments, like coin_x_val_min
, coin_y_val_min
or min_x_out_val
, etc.
Requires sorted generics:
register_pool<X, Y, Curve>
- register a pool with the specified curve type.register_pool_and_add_liquidity<X, Y, Curve>
- register a pool with the specified curve type and add liquidity immediately (extracted from the signer's balance), with the resulting LP tokens deposited to the signer.add_liquidity<X, Y, LP>
- add liquidity to an existing pool, with the resulting LP coins deposited to the signer's address.remove_liquidity<X, Y, Curve>
- burns users LP coins and deposit received coin X and coin Y on user account.
Generics can be sorted in any way:
swap<X, Y, Curve>
- swapCoin<X>
forCoin<Y>;
the developer has to provide the amount ofX
coinscoin_val
to get the minimum amountcoin_out_min_val
of Y.swap_into<X, Y, Curve>
- swap no more than the specified maximum amount ofCoin<X>
for an exact amount inCoin<Y>
. The remainder ofCoin<X>
will be deposited back to the account.swap_unchecked<X, Y, Curve>
- simply swaps tokens without any additional checks (best price, etc).
Helpers
Source Code: .sources/libs
The helpers library improves code readability by extracting parts of the math, token interactions, and other things to separate files.
Coin Helper
Source Code: .sources/libs/coin_helper.move
Mostly helpers on top of aptos_framework::coin
.
Functions
assert_is_coin<CoinType>
- aborts if the providedCoinType
is not a coin/token.compare<X, Y>
- comparesX
coin symbols andY
coin symbols.is_sorted<X, Y>
- returns bool determining ifX
andY
tokens are sorted.supply<CoinType>
- extracts supply fromLP
, ignoringOption
.generate_lp_name<X, Y, Curve>
- generates a name and symbol for theLP
token fromX
andY
symbols andCurve
.
Math
Source Code: .sources/libs/math.move
Implements basic math helpers.
Functions
overflow_add
- adding twou128
and just overflowing the numbers if needed without aborting.mul_div
-x * y / z
foru64
numbers.mul_div_u128
-x * y / z
foru128
numbers but returnsu64
.mul_to_u128
- muls twou64
tou128
.sqrt
- get the square root using the Babylonian method.pow_10
- returns10^degree
.
Deps libraries
The smart contracts utilize the following math libraries created by the Pontem team:
u256 - a pure Move
u256
implementation because some numbers don't fit inu128
.uq64x64 - the Q number format for cumulative price calculations.
Advanced topics
VE(3,3)
The ve(3,3)
logic is currently under active development and can undergo significant changes; stay tuned.
Oracle
Liquidswap's oracle model is based on the cumulative price algorithm used by Uniswap v2. For this reason, token price variables on Liquidswap can overflow, just like on Uniswap.
The following fields in the Liquidity Pool resource are responsible for storing prices:
Meanwhile, the Liquidity Pool module's function get_cumulative_prices<X, Y, Curve>
extracts prices from the pool.
If you are interested in the Oracle implementation, refer to our integration guides.
Treasury
Source Code: .sources/swap/dao_storage.move
The DAO Treasury receives a fee from each swap transaction in every liquidity pool on the protocol.
The treasury is currently managed by Treasury multisig and will be eventually transferred to a full-fledged Pontem DAO.
Config & Dynamic Fees
Source code: ./sources/swap/global_config.move
Most of the functions mostly needed to migrate Treasury accounts from one to another and change default fees.
The liquidity pool module contains functions to change individual pools fees.
Read more fees concept: Fees & Treasury.
Emergency
Source Code: .sources/swap/emergency.move
Can be used to pause/resume all swaps via an emergency account.
The contract itself can be disabled forever once the team can verify that the protocol is fully stable and will not be derailed by any liquidity event, attack etc.
Last updated