This repo contains the contracts to the Superhero decentralized exchange (DEX).
The Superhero DEX consists of multiple parts:
Find a hosted version of the interface over at aepp.dex.superhero.com or feel free to run it on your own machine following these instructions:
In April 2022 QuviQ provided an audit for the contracts. Please find the report in this repo. The audit contains no further recommendations or actions that need to be performed to ensure a secure dex contract.
Under no circumstances, whether in tort (including negligence), contract, or otherwise, unless required by applicable law, shall Aeternity Anstalt be liable for damages, including any direct, indirect, special, incidental, or consequential damages of any nature arising out of the deployment or use of this smart contract, notwithstanding that Aeternity Anstalt may have been advised of the possibility of such damages.
make install
make run-node
make stop-node
make test
You have to set 3 Environment variables
SECRET_KEY
the secret key of the walletNETWORK_NAME
the destination network (local
|testnet
|mainnet
)FEE_TO_SETTER
the initial address for thefee_to_setter
from theAedexV2Factory
. Note: initially thestate.fee_to
is disabled, this is used just to set who is entitled to change thefee_to
mode
and run
make deploy
The official deployed contract addresses
- Factory:
ct_2mfj3FoZxnhkSw5RZMcP8BfPoB1QR4QiYGNCdkAvLZ1zfF6paW
- Router
ct_azbNZ1XrPjXfqBqbAh1ffLNTQ1sbnuUDFvJrXjYz7JQA1saQ3
- Wrapped AE:
ct_J3zBY8xxjsRr3QojETNw48Eb38fjvEuJKkQ6KzECvubvEcvCa
- Factory:
ct_NhbxN8wg8NLkGuzwRNDQhMDKSKBwDAQgxQawK7tkigi2aC7i9
- Router
ct_MLXQEP12MBn99HL6WDaiTqDbG4bJQ3Q9Bzr57oLfvEkghvpFb
- Wrapped AE:
ct_JDp175ruWd7mQggeHewSLS1PFXt9AzThCDaFedxon8mF8xTRF
To calculate the price of your token (token_a
) in terms of another token, such as Wrapped Aethernity (token_b
),
you can use the DEX contracts to fetch the necessary information.
The price calculation for a trading pair token_a
/token_b
involves the following steps:
Firstly, obtain the trading pair contract address from the DEX factory using the get_pair
entrypoint.
You need the addresses of token_a
and token_b
.
The function returns the address of the trading pair’s liquidity pool contract:
AedexV2Factory.get_pair(token_a: IAEX9Minimal, token_b: IAEX9Minimal): option(IAedexV2Pair)
Use the get_reserves
function of the pair’s contract to obtain the current reserves for token_a
and token_b
:
AedexV2Pair.get_reserves(): { reserve_token_a: int, reserve_token_b:, block_timestamp_last: int }
Ensure you know the decimals for token_a
and token_b
. The decimals can be retrieved from the token contract's meta_info
.
If the tokens' decimals vary, normalize the reserves by adjusting for the token decimals:
reserve_token_a_normalized = reserve_token_a * 10^(-decimals_token_a)
reserve_token_b_normalized = reserve_token_b * 10^(-decimals_token_b)
After normalization, calculate the trading prices as follows:
- Price
p
oftoken_a
intoken_b
:p = reserve_token_b_normalized / reserve_token_a_normalized
- → for
x token_a
you gety token_b
, wherey = p * x
- Remember to also account for the 0.3% trading fee, which affects the final received amount:
final_amount = y - (y * 0.003)
In Sophia the price calculation looks like this:
entrypoint calculate_price(factory: IAedexV2Factory, token_a: IAEX9Minimal, token_b: IAEX9Minimal) =
let pair_option = factory.get_pair(token_a, token_b)
let pair_reserves = switch(pair_option)
None => abort("No pair found.")
Some(pair) => pair.get_reserves()
// Normalization. Only needed if token decimals are expected to be different
// Get token decimals
let decimals_token_a = token_a.meta_info().decimals
let decimals_token_b = token_b.meta_info().decimals
// As Sophia doesn't have float numbers, we normalize by scaling with the decimals of the opposite token
let reserve_token_a_normalized = pair_reserves.reserve0 * 10^(decimals_token_b)
let reserve_token_b_normalized = pair_reserves.reserve1 * 10^(decimals_token_a)
// Use precision to get precise result without float numbers.
// As exponent pick a number that reflects your desired level of precision.
let precision = 10^18
// Calculate price with precision
let p = (reserve_token_b_normalized * precision) / reserve_token_a_normalized
p
If no direct trading pair for your tokens exists yet, you should consider creating it.
Another option is using a swap route and making multiple swaps. For example, while there might be no direct trading pair
for token_a
to token_b
, we might have the pairs token_a
/token_c
and token_c
/token_b
.
In this example we can reach the desired token_b
with two swaps. The price has to be calculated step by step using the priorly described calculation:
- for
x token_a
you gety token_c
- for
y token c
you getz token_b
- → for
x token_a
you getz token_b
- The trading fee of 0.3% applies for every swap. In this example:
final_amount = z - (z * 0.003 * #swaps) = z - (z * 0.003 * 2)
If the swap route for a pair is not known, you can use the dex-backend to calculate and fetch a swap route from token_a
to token_b
, if such route exists.
GET {baseURl}/pairs/swap-routes/{from}/{to}
baseUrl
for Mainnet: https://dex-backend-mainnet.prd.aepps.com/baseUrl
for Testnet: https://dex-backend-testnet.prd.aepps.com/from
: address of the token to trade fromto
: address of the token to trade to
The route returns a list of trading pairs for the route:
[
{
"address": "ct_zWrWQLJNwymGiNE5A2J2cYVXFBFdlXb1mI6TvtbsgsxRowdsJ",
"synchronized": true,
"liquidityInfo": {
"totalSupply": "4827358",
"reserve0": "5447795267693766794858383164274815332456551156773585988559756985887324529",
"reserve1": "47881259697979243886731888549326111249911942419926825959439"
},
"token0": "ct_b7FZHQzBcAW4r43ECWpV3qQJMQJp5BxkZUGNKrqqLyjVRN3SC",
"token1": "ct_JDp175ruWd7mQggeHewSLS1PFXt9AzThCDaFedxon8mF8xTRF"
},
...
]
If no swap route is available, an empty array is returned.
In the DEX, a trading pair pool is a collection of funds locked in a smart contract used to facilitate trading between two assets. Liquidity providers (LPs) supply these funds to the pool and, in return, receive liquidity provider tokens (LP tokens). These tokens represent their share of the pool and a claim on a portion of the trading fees.
When providing liquidity, an LP contributes assets to a trading pair like token_a
/token_b
.
The amount of LP tokens received depends on the existing liquidity in the pool.
Initially, LP tokens are minted based on the ratio of the assets provided.
As more liquidity is added or removed, the amount of LP tokens minted or burned varies accordingly.
The total_supply
of a trading pair indicates the total number of LP tokens in circulation for that pool.
It increases when liquidity is added (LP tokens are minted) and decreases when liquidity is withdrawn (LP tokens are burned).
The ownership share of the pool is proportional to an LP's tokens relative to the total_supply
.
For instance, if you hold 10 LP tokens and the total_supply
is 100, you own 10% of the pool,
entitling you to 10% of the trading fees generated.