Skip to main content

Onchain DeFi Dex

Evolve a DEX from basic to more complex as understanding grows.

Analysis of source code of market leading DEX ventures.

Where is value capture in a DEX? Uniswap.

DEX Calculations Spreadsheet

Functional Requirements

ItemNotes
Initiate with reserves of two currencies to trade
Allow a user to trade/swap currencies
Allow a user/contract to add liquidity
Ability to remove liquidity

Build

Use the Speed Run Ethereum challenge as a starting point for building a DEX to swap an arbitrary token for ETH using a liquidity pool anyone can participate in.

Challenge Instructions

tip

Prerequisite challenges of SpeedRunEthereum are at Web3 Fundamentals

Checkpoint: Setup

Contract would not compile initially, needed add liquidity mapping.

  • Read the original challenge to understand objectives
  • Rename DEXTemplate.sol to DEX.sol
  • Added mapping for liquidity
  • 1000 Balloons have been minted

Checkpoint: Reserves

  • Add static variables to hold reserves
  • Create an init function to fund reserves
  • Use deploy script to init contract with reserves
  • Chart appears

Notes:

  • Review vendor approve transferFrom

Checkpoint: Price

  • Copied in price calculation but set to pure function
    function price(
uint256 xInput,
uint256 xReserves,
uint256 yReserves
) public pure returns (uint256 yOutput) {
uint256 xInputWithFee = xInput.mul(997);
uint256 numerator = xInputWithFee.mul(yReserves);
uint256 denominator = (xReserves.mul(1000)).add(xInputWithFee);
return (numerator / denominator);
}

If you put in 1000 ETH you will only receive 996 tokens.

  • If paying a 0.3% fee it should be 997. BUT for slippage as the contract moves away from the original ratio

Notes:

  • 🤔 Do you understand how the x*y=k price curve actually works? Write down a clear explanation for yourself and derive the formula for price.
  • 💃 You should be able to go through the price section of this tutorial with the sample numbers and generate the same output Change variable.

Safe Math

Checkpoint: Trading

  • ethToToken()
  • tokenToEth()

Checkpoint: Liquidity

Impermanent Loss

Setup

// paste in your front-end address here to get 10 balloons on deploy:
await balloons.transfer('0xfE2f43CEd86Bfbd2D2Fb310923267567A39cD1f2', '' + 10 * 10 ** 18);
  • As a User with $BAL tokens (deploy init) Approve the spend of 20 Balloons by the DEX/Liquidity Pool address
  • Understand Impermanent Loss

Task

  • create a deposit() function that receives ETH and also transfers $BAL tokens from the caller
  • create a withdraw() function that lests a user take both ETH and $BAL tokens out at the correct ratio

Deposit Liquidity

function deposit() public payable returns (uint256) {
uint256 eth_reserve = address(this).balance.sub(msg.value);
uint256 token_reserve = token.balanceOf(address(this));
uint256 token_amount = (msg.value.mul(token_reserve) / eth_reserve).add(1);
uint256 liquidity_minted = msg.value.mul(totalLiquidity) / eth_reserve;
liquidity[msg.sender] = liquidity[msg.sender].add(liquidity_minted);
totalLiquidity = totalLiquidity.add(liquidity_minted);
require(token.transferFrom(msg.sender, address(this), token_amount));
return liquidity_minted;
}

Withdraw Liquidity

function withdraw(uint256 amount) public returns (uint256, uint256) {
uint256 token_reserve = token.balanceOf(address(this));
uint256 eth_amount = amount.mul(address(this).balance) / totalLiquidity;
uint256 token_amount = amount.mul(token_reserve) / totalLiquidity;
liquidity[msg.sender] = liquidity[msg.sender].sub(eth_amount);
totalLiquidity = totalLiquidity.sub(eth_amount);
msg.sender.transfer(eth_amount);
require(token.transfer(msg.sender, token_amount));
return (eth_amount, token_amount);
}

Notes

  • 💧 Deposit liquidity, and then check your liquidity amount through the mapping in the debug tab Has it changed properly? Did the right amount of assets get deposited?
  • 🧐 What happens if you deposit() at the beginning of the deployed contract, then another user starts swapping out for most of the balloons, and then you try to withdraw your position as a liquidity provider?

Answer: you should get the amount of liquidity proportional to the ratio of assets within the isolated liquidity pool. It will not be 1:1.

Checkpoint: App UI

note

Unclear instructions: from the repo I started from the job seemed done?

Checkpoint: Deploy

Refer to deploy instructions

Deploy Contract (Backend)

  • Run Tests on localhost
  • Hardhat update defaultNetwork and Key config
  • Generate deployer account
  • Fund deployer account
  • Deploy contract
  • Verify contract is deployed

Deploy React Web App

  • Configure App.jsx: set the network to connect to
  • Build: create an optimised dist for deployment - yarn build
  • Deploy: are per decentralisation requirements - yarn surge

Issues:

Deploy failed with an insufficient funds for transaction error, so reduced init liquidity down to 0.01 eth.

Followup:

Result URLs

https://rinkeby.etherscan.io/address/0x904ECD281189aB572Aa15eB641A8ce6Ae7981235
https://overwrought-chairs.surge.sh/

Thoughts

Followup:

  • Move code to Foundry
  • Experiment with trades
  • Clean up UI
  • Compare typescript implementation
  • Try a different swap formula
  • Investigate yarn build