---
title: PEPA DEX
---
###### tags: `Decentralized Exchange` `Fuel Network` `Sway Language`
<font size="8"> PEPA Decentralized Exchange </font>
Design & Spec
===
> Copyright © 2023 by PEPA Market. All rights reserved.
> Feb 16, 2023
> by **PEPA Market**
<!-- <span style="position:fixed;
top:200px;
right:400px;
opacity:0.5;
font-size: 20px;
z-index:99;">watermark</span> -->
<!-- inser new picture here -->

This report presents our PEPA dev team's engineering design & spec for PEPA Decentralized Exchange.
---
## Table of Content
[TOC]
---
## Project Summary
A decentralized exchange powered by the Automated Market Making plus the Proactive Market Making algorithm: an algorithm-driven exchange offers both the AMM and PMM options.
Our DEX creates high capital-efficient liquidity pools that support single token provision, lowers slippage, and decreases impermanent loss for our swappers and liquidity providers. We offer our users with options to choose either AMM or PMM model.
---
## Protocol Overview
### Business Logic
PEPA DEX V1 is an Automated Market Making (AMM) algorithm-based decentralized exchange that allows users to trade cryptocurrencies without the need for intermediaries. Here are some of the key features and business logic of the platform:
+ **AMM-based trading**: The PEPA DEX uses an automated market maker (AMM) algorithm to facilitate trading. This means that users can trade cryptocurrencies directly with the liquidity pool, which is created by users depositing funds into the pool in exchange for Liquidity Provider (LP) tokens.
+ **Liquidity privision and liquidity mining**: Users can provide liquidity to PEPA DEX by depositing token into the liquidity pool. In exchange, they receive LP tokens that represent their share of the liquidity pool which generates rewards from user trading actions. They can withdraw their funds with LP tokens at any time.

<!-- PWCEIVL.png)

### Core Elements
PEPA DEX is
-->a Fuel-based contract suite enabling on-chain exchange of tokens on Fuel with security guarantee. The functionality of PEPA DEX is achieved collectively by a collection of core contracts.
#### Pool Contract
Pool contract wraps up logic of single pair token exchange. It stores token resovier and calculate token exchange and liquidity providing logic per request.
#### Factory Contract
Factory contract serves as singleton service and information center for all services PEPA DEX provides. It handles exchange pool registration and information request, as well as connecting pool contract with fee model.
#### Router Contract
Router contract encapsulate more complex token exchange logic involving multi-hop swap. Router contract keeps no token reserve and purely serves as handler of token swap logic.
#### Token Contract
We keeps a compact token contract providing necessary logic simulating a Fuel version of ERC20 token. We try minimizing usage of this token contract for lower migration cost to a more standarlized ERC20 standard in Fuel.
#### Fee Contract
This contract provides fee collection logic for a customizable fee mechanism.
---
## PEPA DEX Architecture
<!-- todo: add an system level overview / graph to explain each component -->

### Factory Contract
Factory Contract has been designed to record the list of the pool contract, where there are 4 important functions that users/admin can interact with:
- initialize: init the factory contract
- get_swap: get address for a swap pair
- exist_map: check if a swap pair exist on certain address
- create_swap: create swap for a token pair
#### initialize - storage(read,write)
```solidity
// Initialize factory, set pool code hash for validation
#[storage(read,write)]
fn initialize(template_swap_id: b256){
storage.initialized = true;
let root = bytecode_root(ContractId::from(template_swap_id));
storage.template_root = root;
}
```
| | Veriable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| Input - 1 | template_swap_id | b256 | `ContractId` of swap contract template |
**Notes:** Upon function returning, the proper `teamplate_root` will be setup and `initialized` variable will be set to `true`.
#### get_swap - storage(read)
```solidity
// Get address for a swap pair
fn get_swap(token_0_address: b256, token_1_address: b256) -> Option<b256> {
assert(storage.initialized);
let mut token_0 = token_0_address;
let mut token_1 = token_1_address;
// keep order
if token_1 > token_0 {
token_0 = token_1_address;
token_1 = token_0_address;
}
// return pool address found
let swap = storage.swap_pair.get((token_0, token_1));
if swap == b256::min() {
Option::None
}else{
Option::Some(swap)
}
}
```
| | Veriable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| Input - 1 | token_0_address | b256 | `Address` of token0|
| Input - 2 | token_1_address | b256 | `Address` of token1|
| Output - 1| swap |Option<b256>| `Address` of the swap pair if it exists else `Option::None`|
**Notes:** Return an ordered pair of swap token contract id in pool.
#### exist_swap - storage(read)
```solidity
// Check if a swap pair exist on address, return Option::None if key not exist
fn exist_swap(address: b256) -> bool{
assert(storage.initialized);
let addr = storage.swap_address.get(address);
addr
}
```
| | Variable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| Input - 1 | address | b256 | `Address` of a swap pair|
| Output - 1| addr |Option<b256>| `true` if the swap pair exists else `Option::None`|
**Notes:** Return `true` when a pool exist on an `ContractId` address and otherwise false.
#### create_swap - storage(read, write)
```solidity
// Create swap for token pair
fn create_swap(swap_id: b256){
assert(storage.initialized);
let root = bytecode_root(ContractId::from(swap_id));
require(root == storage.template_root, FactoryError::PlaceholderError);
let swap = abi(Swap, swap_id);
// revert if not initialized
let (token_0,token_1) = swap.get_pair();
// pair not exist already
assert(storage.swap_pair.get((token_0,token_1)) != b256::min());
storage.swap_pair.insert((token_0, token_1),swap_id);
storage.swap_address.insert(swap_id,true);
}
```
| | Variable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| Input - 1 | swap_id | b256 | `ContractId` of a swap contract|
**Notes:** The storage has to be initialized. The input contract ID has to be compatible to the `template_root`. The corresponding token pair cannot exist in the storage already. Upon successful running, the `swap_pair` and `swap_address` varible in storage will be updated.
### Pool Contract
<!-- todo: Pool Contract Functional Level Explaination -->
#### initialize - storage(read,write)
| | Variable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| Input - 1 | address_0 | b256 | contract id of first token in swap pair|
| Input - 2 | address_1 | b256 | contract id of second token in swap pair|
**Notes:** Initialize pool contract with token contract id of the swap pair.
#### deposit - storage(read,write)
| | Variable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
**Notes:** Helper function for depositing initial liquidity into the pool.
#### withdraw - storage(read,write)
| | Variable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| input - 1 | amount | u64 | amount to withdraw |
| input - 2 | asset_id | ContractId | asset id of token to withdraw |
**Notes:** For testing purpose only. Helper function for withdraw token. Will remove from testnet version.
#### add_liquidity - storage(read,write)
| | Variable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
**Notes:** When initial liquidity is empty, calling `add_liquidity` will lock current token balance into liquidity. Otherwise `add_liquidity` will add input balance into liquidity pool and mint corresponding LP Token to caller.
#### remove_liquidity - storage(read,write)
| | Variable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
**Notes:** This function removes liquidity according to current LP Token balance and send corresponding base and quote token to caller.
#### swap - storage(read,write)
| | Variable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| input - 1 | amount_0 | u64 | input amount of the first token in swap pair |
| input - 2 | amount_1 | u64 | input amount of the second token in swap pair |
**Notes:** This function will take the first token in swap pair with non-zero balance and swap to the other token at maximum exchange rate achievable given current reserve and fee rate.
#### get_pair - storage(read,write)
| | Variable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
**Notes:** Return an ordered pair of swap token contract id in pool.
### Router Contract
#### initialize - storage(read,write)
```solidity
// initializer, initialize factory
fn initialize(factory:b256){
assert(!storage.initialized);
storage.factory = factory;
storage.initialized = true;
}
```
| | Variable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| input - 1 | factory | b256 | Address of factory. |
#### add_liquidity - storage(read,write)
```solidity
// add liquidity
fn add_liquidity(swap_address:b256, amount_0:u64, amount_1:u64, amount_0_min:u64, amount_1_min:u64) -> (u64,u64){
assert(storage.initialized);
// check swap_address in factory?
let swap = abi(Swap,swap_address);
//todo: get token amount from received liquidity
let (reserve_0, reserve_1) = swap.get_reserve();
let (token_0_address,token_1_address) = swap.get_pair();
if (reserve_0 == 0 && reserve_1 == 0){
//TODO transfer liquidity
(reserve_0, reserve_1)
} else {
// calculate/quote
let amount_1_out = maximum_output_given_exact_input(amount_0, reserve_0, reserve_1);
if(amount_1_out <= amount_1) {
assert(amount_1_out >= amount_1_min); // check for insufficient token 1
//TODO: transfer liquidity
(amount_0, amount_1_out)
}else{
let amount_0_out = maximum_output_given_exact_input(amount_1, reserve_0, reserve_1);
assert(amount_0_out <= amount_0); //prevent amount 0 over desired amount
assert(amount_0_out >= amount_0_min); // ensure enough token 0
//TODO: transfer liquidity
(amount_0_out, amount_1)
}
}
}
```
| | Variable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| input - 1 | swap_address | b256 | contract id of a swap pool. |
| input - 2 | amount_0 | u64 | amount of token 0. |
| input - 3 | amount_1 | u64 | amount of token 1. |
| input - 4 | amount_0_min | u64 | minimun amount of token 0. |
| input - 5 | amount_1_min | u64 | minimun amount of token 1. |
**Notes:** Requires the provided pool address recorded in PEPA factory, and balance of token pair deposit into router contract. If token amount deposit is greater than minimum amount provided, `add_liquidity` will add input balance into provided liquidity pool and mint corresponding LP Token to caller-provided address. Otherwise, `add_liquidity` will revert.
#### remove_liquidity - storage(read,write)
```solidity
// remove liquidity
fn remove_liquidity(swap_address:b256, token_0_address:b256, token_1_address:b256 , amount_0:u64, amount_1:u64, amount_0_min:u64, amount_1_min:u64) -> (u64,u64){
assert(storage.initialized);
// check swap_address in factory?
let swap = abi(Swap,swap_address);
//todo: get token amount from received liquidity
let (reserve_0, reserve_1) = swap.get_reserve();
let (token_0_address,token_1_address) = swap.get_pair();
// check lp token received
let lp_address = msg_asset_id();
let lp_amount = msg_amount();
let sender = msg_sender().unwrap();
assert(lp_address.into() == swap_address);
// send lp token and burn at swap and retrieve token pair
transfer(lp_amount,lp_address,Identity::ContractId(ContractId::from(swap_address)));
// TODO: change api
let (token_0_amount, token_1_amount) = swap.remove_liquidity{
asset_id: lp_address.into(),
coins: lp_amount,
}(Identity::ContractId(contract_id()));
// liquidity removed, swap contract send balance to router
// router send back to user
transfer(token_0_amount, ContractId::from(token_0_address), sender);
transfer(token_1_amount, ContractId::from(token_1_address), sender);
// return an output structure
(token_0_amount, token_1_amount)
}
```
| | Variable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| input - 1 | swap_address | b256 | contract id of a swap. |
| input - 2 | token_0_address | b256 | address of token 0. |
| input - 3 | token_1_address | b256 | address of token 1. |
| input - 4 | amount_0 | u64 | amount of token 0. |
| input - 5 | amount_1 | u64 | amount of token 1. |
| input - 6 | amount_0_min | u64 | minimun amount of token 0. |
| input - 7 | amount_1_min | u64 | minimun amount of token 1. |
**Notes:** Requires the provided pool address recorded in PEPA factory, and balance of LP Token deposit into router contract. This function removes liquidity according to current LP Token balance and send corresponding base and quote token to caller.
#### swap_exact_input_for_output - storage(read,write)
```solidity
// swap exact input to output
#[storage(read,write)]
fn swap_exact_input_for_output(swap_address:b256, asset_0_amount:u64, asset_1_amount:u64 ,amount_out_min:u64, to:Identity) -> SwapResult{
swap_exact_input_for_output(swap_address, asset_0_amount, asset_1_amount,amount_out_min, to)
}
```
| | Variable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| input - 1 | swap_address | b256 | contract id of a swap. |
| input - 2 | asset_0_amount | u64 | amount of token 0. |
| input - 3 | asset_1_amount | u64 | amount of token 1. |
| input - 4 | amount_out_min | u64 | minimum amount that can swap out. |
| input - 5 | to | Identity | Identity of the liquidity output. |
**Note:** Requires the provided pool address recorded in PEPA factory, and balance of token for exchange deposit into router contract. This function will take the first token in swap pair with non-zero balance and swap to the other token at maximum exchange rate achievable given current reserve and fee rate in pool.
#### swap_input_for_exact_output - storage(read,write)
```solidity
// swap input for exact output
fn swap_input_for_exact_output(swap_address:b256, asset_0_amount:u64, asset_1_amount:u64 , amount_in_max:u64, amount_out:u64, to:Identity) -> SwapResult{
assert(storage.initialized);
// check swap_address in factory?
let swap = abi(Swap,swap_address);
//todo: get token amount from received liquidity
let (reserve_0, reserve_1) = swap.get_reserve();
let (token_0_address,token_1_address) = swap.get_pair();
let mut asset_0_in = 0;
let mut asset_1_in = 0;
if (asset_0_amount > 0) {
// input asset 0
asset_0_in = minimum_input_given_exact_output(amount_out,reserve_0,reserve_1);
assert (asset_0_in <= amount_in_max);
transfer(asset_0_amount, ContractId::from(token_0_address), Identity::ContractId(ContractId::from(swap_address)));
asset_1_in = 0;
} else {
// input asset 1
asset_1_in = minimum_input_given_exact_output(amount_out,reserve_1,reserve_0);
assert (asset_1_in <= amount_in_max);
transfer(asset_1_amount, ContractId::from(token_1_address), Identity::ContractId(ContractId::from(swap_address)));
asset_0_in = 0;
}
// send and swap money
let (amount_0_out, amount_1_out) = swap.swap(asset_0_in, asset_1_in);
// transfer swap balance
transfer(amount_0_out, ContractId::from(token_0_address), to);
transfer(amount_1_out, ContractId::from(token_1_address), to);
SwapResult {
amount_0_out:amount_0_out,
amount_1_out:amount_1_out,
}
}
```
| | Variable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| input - 1 | swap_address | b256 | contract id of a swap. |
| input - 2 | asset_0_amount | u64 | amount of token 0. |
| input - 3 | asset_1_amount | u64 | amount of token 1. |
| input - 4 | amount_in_max | u64 | maximum amount that can swap in. |
| input - 5 | amount_out | u64 | amount swap out. |
| input - 6 | to | Identity | Identity of the liquidity output. |
**Note:** Requires the provided pool address recorded in PEPA factory, and balance of token for exchange deposit into router contract. This function will calculate the minimum required input for swapping out specified output token amount and execute swap, and send output token and remaining input balance to specified address.
#### swap_exact_input_for_output_multihop - storage(read,write)
```solidity
fn swap_exact_input_for_output_multihop(swap_factory: b256, path:Vec<b256>, amount_in:u64 , amount_out_min:u64, to:Identity) -> SwapResult {
require(path.len() >= 2, "Path length must be greater than Two!");
let token_out = path.get(path.len()-1).unwrap();
let swap_result = swap_exact_input_for_output_multihop(swap_factory, path, amount_in , amount_out_min, Identity::ContractId(contract_id()));
transfer(swap_result.amount_1_out, ContractId::from(token_out), to);
swap_result
}
```
| | Variable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| input - 1 | swap_factory | b256 | contract id of a swap factory. |
| input - 2 | path | Vec<b256> | |
| input - 3 | amount_out | u64 | amount swap in. |
| input - 4 | amount_out_min | u64 | minimum amount that can swap out. |
| input - 5 | to | Identity | Identity of the liquidity output. |
**Note:** Multihop version of `swap_exact_input_for_output`.
#### swap_input_for_exact_output_multihop - storage(read,write)
```solidity
fn swap_input_for_exact_output_multihop(swap_factory: b256, path:Vec<b256>, amount_out:u64 , amount_in_max:u64, to:Identity) -> SwapResult {
assert(storage.initialized);
require(path.len() >= 2, "Token path length must be greater than Two!");
let factory = abi(Factory, swap_factory);
let mut i = path.len()-1;
// output amount to swap contract
let mut swap_out_amount = amount_out;
// first calculate the minimum required input then swap
while i >0 {
// get pair address with factory
let (address_0,address_1) = (path.get(i).unwrap(), path.get(i+1).unwrap());
let swap_address = factory.get_swap(address_0,address_1).unwrap();
let swap = abi(Swap, swap_address);
let (swap_token_0, swap_token_1) = swap.get_pair();
let (swap_reserve_0, swap_reserve_1) = swap.get_reserve();
// set reserve as well
let (reserve_0, reserve_1) = if address_0 == swap_token_0 {
(swap_reserve_0, swap_reserve_1)
} else {
(swap_reserve_1, swap_reserve_0)
};
// calculate min input
swap_out_amount = minimum_input_given_exact_output(
swap_out_amount,
reserve_0,
reserve_1
);
i=i-1;
}
//TODO: add error output
assert(swap_out_amount <= amount_in_max);
// execute swap, need to change for exact output not exceeding specified amount
let token_out = path.get(path.len()-1).unwrap();
let result = swap_exact_input_for_output_multihop(swap_factory, path, swap_out_amount, amount_out, to);
let (result_in, result_out) = (result.amount_0_out, result.amount_1_out);
transfer(result_out, ContractId::from(token_out), to);
SwapResult {
amount_0_out: swap_out_amount,
amount_1_out: result_out,
}
}
```
| | Variable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| input - 1 | swap_factory | b256 | contract id of a swap factory. |
| input - 2 | path | Vec<b256> | |
| input - 3 | amount_out | u64 | amount swap in. |
| input - 4 | amount_in_max | u64 | maximum amount that can swap in. |
| input - 5 | to | Identity | Identity of the liquidity output. |
**Note:** Multihop version of `swap_input_for_exact_output`.
### Token Contract
#### initialize - storage(read, write)
```solidity
// Initialize contract
fn initialize(mint_amount: u64, address: Address) {
require(storage.owner.into() == ZERO_B256, Error::CannotReinitialize);
// Start the next message to be signed
storage.owner = address;
storage.mint_amount = mint_amount;
}
```
| | Veriable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| Input - 1 | mint_amount | u64 | Amount of tokens to mint.|
| Input - 2 | address | Address | `Address` of the owner of the account|
#### set_mint_amount - storage(read, write)
```solidity
// Set mint amount for each address
fn set_mint_amount(mint_amount: u64) {
validate_owner();
storage.mint_amount = mint_amount;
}
```
| | Veriable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| Input - 1 | mint_amount | u64 | Amount of tokens to mint.|
#### get_balance
```solidity
// Get balance of the contract coins
fn get_balance() -> u64 {
balance_of(contract_id(), contract_id())
}
```
| | Veriable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| Output - 1 | | u64 | The balance of the account.|
#### get_mint_amount - storage(read)
```solidity
fn get_mint_amount() -> u64 {
storage.mint_amount
}
```
| | Veriable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| Output - 1 | | u64 | The `mint_amount` varible in storage.|
#### get_token_balance
```solidity
// Get balance of a specified token on contract
fn get_token_balance(asset_id: ContractId) -> u64 {
balance_of(asset_id, contract_id())
}
```
| | Veriable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| Input - 1 | asset_id | ContractId | `ContractId` of the specified token.|
| Output - 1 | | u64 | Balance of the specified token.|
#### mint_coins - storage(read)
```solidity
// Mint token coins
fn mint_coins(mint_amount: u64) {
validate_owner();
mint(mint_amount);
}
```
| | Veriable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| Input - 1 | mint_amount | u64 | Amount of tokens to mint.|
#### burn_coins - storage(read)
```solidity
// Burn token coins
fn burn_coins(burn_amount: u64) {
validate_owner();
burn(burn_amount);
}
```
| | Veriable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| Input - 1 | burn_amount | u64 | Amount of tokens to burn.|
#### transfer_coins - storage(read)
```solidity
// Transfer a contract coins to a given output
fn transfer_coins(coins: u64, address: Address) {
validate_owner();
transfer_to_address(coins, contract_id(), address);
}
```
| | Veriable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| Input - 1 | coins | u64 | Amount of coins to transfer.|
| Input - 2 | address | Address | Address where the coins transfer to.|
#### transfer_token_to_output - storage(read)
```solidity
// Transfer a specified token from the contract to a given output
fn transfer_token_to_output(coins: u64, asset_id: ContractId, address: Address) {
validate_owner();
transfer_to_address(coins, asset_id, address);
}
```
| | Veriable Name | Type | Description |
| --------- | ---------------- | -----| -------- |
| Input - 1 | coins | u64 | Amount of coins to transfer.|
| Input - 2 | asset_id | ContractId | `ContractId` of the specified token.|
| Input - 3 | address | Address | Address where the coins transfer to.|
#### mint - storage(read, write)
```solidity
// Method called from address to mint coins.
fn mint() {
require(storage.mint_amount > 0, Error::MintIsClosed);
// Enable a address to mint only once
let sender = get_msg_sender_address_or_panic();
require(storage.mint_list.get(sender) == false, Error::AddressAlreadyMint);
storage.mint_list.insert(sender, true);
mint_to_address(storage.mint_amount, sender);
}
```
**Notes:** `mint_amount` varible in storage has to be larger than 0. Value of sender's address in `storage.mint_list` should be `false`.
<!---
### Access Control Description
--->
---
## License | Safety Notice | Disclaimer
<!-- todo: Express current engineering and testing is still undergoing and contract has not been security review yet -->
**DISCLAIMER:** This software is provided "as is" and without any express or implied warranties, including, without limitation, the implied warranties of merchantability and fitness for a particular purpose. The author(s) will not be held liable for any damages arising from the use of this software. This code is provided for educational and informational purposes only. It should not be used for any purpose that may be deemed commercial, without prior written consent from the author(s). The author(s) make no representations or warranties with respect to the accuracy or completeness of the contents of this code and specifically disclaim any implied warranties of merchantability or fitness for a particular purpose. The user assumes all responsibility and risk for the use of this code. Feel free to modify and customize the disclaimer as needed, depending on the specific details of your code and the intended use of the software.