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.
Functional Requirements
Item | Notes |
---|---|
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.
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.
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
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:
- Try using instantwallet.io
- Checkout Balloons contract
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