HackMD
  • Beta
    Beta  Get a sneak peek of HackMD’s new design
    Turn on the feature preview and give us feedback.
    Go → Got it
      • Create new note
      • Create a note from template
    • Beta  Get a sneak peek of HackMD’s new design
      Beta  Get a sneak peek of HackMD’s new design
      Turn on the feature preview and give us feedback.
      Go → Got it
      • Sharing Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • More (Comment, Invitee)
      • Publishing
        Please check the box to agree to the Community Guidelines.
        Everyone on the web can find and read all notes of this public team.
        After the note is published, everyone on the web can find and read this note.
        See all published notes on profile page.
      • Commenting Enable
        Disabled Forbidden Owners Signed-in users Everyone
      • Permission
        • Forbidden
        • Owners
        • Signed-in users
        • Everyone
      • Invitee
      • No invitee
      • Options
      • Versions and GitHub Sync
      • Transfer ownership
      • Delete this note
      • Template
      • Save as template
      • Insert from template
      • Export
      • Dropbox
      • Google Drive Export to Google Drive
      • Gist
      • Import
      • Dropbox
      • Google Drive Import from Google Drive
      • Gist
      • Clipboard
      • Download
      • Markdown
      • HTML
      • Raw HTML
    Menu Sharing Create Help
    Create Create new note Create a note from template
    Menu
    Options
    Versions and GitHub Sync Transfer ownership Delete this note
    Export
    Dropbox Google Drive Export to Google Drive Gist
    Import
    Dropbox Google Drive Import from Google Drive Gist Clipboard
    Download
    Markdown HTML Raw HTML
    Back
    Sharing
    Sharing Link copied
    /edit
    View mode
    • Edit mode
    • View mode
    • Book mode
    • Slide mode
    Edit mode View mode Book mode Slide mode
    Note Permission
    Read
    Only me
    • Only me
    • Signed-in users
    • Everyone
    Only me Signed-in users Everyone
    Write
    Only me
    • Only me
    • Signed-in users
    • Everyone
    Only me Signed-in users Everyone
    More (Comment, Invitee)
    Publishing
    Please check the box to agree to the Community Guidelines.
    Everyone on the web can find and read all notes of this public team.
    After the note is published, everyone on the web can find and read this note.
    See all published notes on profile page.
    More (Comment, Invitee)
    Commenting Enable
    Disabled Forbidden Owners Signed-in users Everyone
    Permission
    Owners
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Invitee
    No invitee
       owned this note    owned this note      
    Published Linked with GitHub
    Like BookmarkBookmarked
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # Uniswap Frontend Notes ###### tags: `uniswap` [TOC] # Creating Contract Instances There will be a hardcoded list of token and exchange addresses (also symbol/name/decimals) for each ERC20 listed on the website. It should be as easy as possible to add support for a new ERC20. Below are the addresses for three tokens with exchanges that are live an have been tested on the latest Uniswap testnet. ### Factory Address 0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36 ### Token / Exchange Addresses (Rinkeby) **Symbol:** DAI **Name:** Dai Stablecoin **Decimals:** 18 **Token Address:** 0x2448eE2641d78CC42D7AD76498917359D961A783 **DAI Exchange:** 0x77dB9C915809e7BE439D2AB21032B1b8B58F6891 **Symbol:** MKR **Name:** Maker **Decimals:** 18 **Token Address:** 0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85 **Exchange Address:** 0x93bB63aFe1E0180d0eF100D774B473034fd60C36 **Symbol:** ZRX **Name:** 0x Protocol Token **Decimals:** 18 **Token Address:** 0xF22e3F33768354c9805d046af3C0926f27741B43 **Exchange Address:** 0xaBD44a1D1b9Fb0F39fE1D1ee6b1e2a14916D067D ### Factory ABI ``` [{"name": "NewExchange", "inputs": [{"type": "address", "name": "token", "indexed": true}, {"type": "address", "name": "exchange", "indexed": true}], "anonymous": false, "type": "event"}, {"name": "initializeFactory", "outputs": [], "inputs": [{"type": "address", "name": "template"}], "constant": false, "payable": false, "type": "function", "gas": 35725}, {"name": "createExchange", "outputs": [{"type": "address", "name": "out"}], "inputs": [{"type": "address", "name": "token"}], "constant": false, "payable": false, "type": "function", "gas": 187911}, {"name": "getExchange", "outputs": [{"type": "address", "name": "out"}], "inputs": [{"type": "address", "name": "token"}], "constant": true, "payable": false, "type": "function", "gas": 715}, {"name": "getToken", "outputs": [{"type": "address", "name": "out"}], "inputs": [{"type": "address", "name": "exchange"}], "constant": true, "payable": false, "type": "function", "gas": 745}, {"name": "getTokenWithId", "outputs": [{"type": "address", "name": "out"}], "inputs": [{"type": "uint256", "name": "token_id"}], "constant": true, "payable": false, "type": "function", "gas": 736}, {"name": "exchangeTemplate", "outputs": [{"type": "address", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 633}, {"name": "tokenCount", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 663}] ``` ### Token ABI ``` [{"name": "Transfer", "inputs": [{"type": "address", "name": "_from", "indexed": true}, {"type": "address", "name": "_to", "indexed": true}, {"type": "uint256", "name": "_value", "indexed": false}], "anonymous": false, "type": "event"}, {"name": "Approval", "inputs": [{"type": "address", "name": "_owner", "indexed": true}, {"type": "address", "name": "_spender", "indexed": true}, {"type": "uint256", "name": "_value", "indexed": false}], "anonymous": false, "type": "event"}, {"name": "__init__", "outputs": [], "inputs": [{"type": "bytes32", "name": "_name"}, {"type": "bytes32", "name": "_symbol"}, {"type": "uint256", "name": "_decimals"}, {"type": "uint256", "name": "_supply"}], "constant": false, "payable": false, "type": "constructor"}, {"name": "deposit", "outputs": [], "inputs": [], "constant": false, "payable": true, "type": "function", "gas": 74279}, {"name": "withdraw", "outputs": [{"type": "bool", "name": "out"}], "inputs": [{"type": "uint256", "name": "_value"}], "constant": false, "payable": false, "type": "function", "gas": 108706}, {"name": "totalSupply", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 543}, {"name": "balanceOf", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "address", "name": "_owner"}], "constant": true, "payable": false, "type": "function", "gas": 745}, {"name": "transfer", "outputs": [{"type": "bool", "name": "out"}], "inputs": [{"type": "address", "name": "_to"}, {"type": "uint256", "name": "_value"}], "constant": false, "payable": false, "type": "function", "gas": 74698}, {"name": "transferFrom", "outputs": [{"type": "bool", "name": "out"}], "inputs": [{"type": "address", "name": "_from"}, {"type": "address", "name": "_to"}, {"type": "uint256", "name": "_value"}], "constant": false, "payable": false, "type": "function", "gas": 110600}, {"name": "approve", "outputs": [{"type": "bool", "name": "out"}], "inputs": [{"type": "address", "name": "_spender"}, {"type": "uint256", "name": "_value"}], "constant": false, "payable": false, "type": "function", "gas": 37888}, {"name": "allowance", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "address", "name": "_owner"}, {"type": "address", "name": "_spender"}], "constant": true, "payable": false, "type": "function", "gas": 1025}, {"name": "name", "outputs": [{"type": "bytes32", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 723}, {"name": "symbol", "outputs": [{"type": "bytes32", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 753}, {"name": "decimals", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 783}] ``` ### Exchange ABI ``` [{"name": "TokenPurchase", "inputs": [{"type": "address", "name": "buyer", "indexed": true}, {"type": "uint256", "name": "eth_sold", "indexed": true}, {"type": "uint256", "name": "tokens_bought", "indexed": true}], "anonymous": false, "type": "event"}, {"name": "EthPurchase", "inputs": [{"type": "address", "name": "buyer", "indexed": true}, {"type": "uint256", "name": "tokens_sold", "indexed": true}, {"type": "uint256", "name": "eth_bought", "indexed": true}], "anonymous": false, "type": "event"}, {"name": "AddLiquidity", "inputs": [{"type": "address", "name": "provider", "indexed": true}, {"type": "uint256", "name": "eth_amount", "indexed": true}, {"type": "uint256", "name": "token_amount", "indexed": true}], "anonymous": false, "type": "event"}, {"name": "RemoveLiquidity", "inputs": [{"type": "address", "name": "provider", "indexed": true}, {"type": "uint256", "name": "eth_amount", "indexed": true}, {"type": "uint256", "name": "token_amount", "indexed": true}], "anonymous": false, "type": "event"}, {"name": "Transfer", "inputs": [{"type": "address", "name": "_from", "indexed": true}, {"type": "address", "name": "_to", "indexed": true}, {"type": "uint256", "name": "_value", "indexed": false}], "anonymous": false, "type": "event"}, {"name": "Approval", "inputs": [{"type": "address", "name": "_owner", "indexed": true}, {"type": "address", "name": "_spender", "indexed": true}, {"type": "uint256", "name": "_value", "indexed": false}], "anonymous": false, "type": "event"}, {"name": "setup", "outputs": [], "inputs": [{"type": "address", "name": "token_addr"}], "constant": false, "payable": false, "type": "function", "gas": 175875}, {"name": "addLiquidity", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "min_liquidity"}, {"type": "uint256", "name": "max_tokens"}, {"type": "uint256", "name": "deadline"}], "constant": false, "payable": true, "type": "function", "gas": 82605}, {"name": "removeLiquidity", "outputs": [{"type": "uint256", "name": "out"}, {"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "amount"}, {"type": "uint256", "name": "min_eth"}, {"type": "uint256", "name": "min_tokens"}, {"type": "uint256", "name": "deadline"}], "constant": false, "payable": false, "type": "function", "gas": 116814}, {"name": "__default__", "outputs": [], "inputs": [], "constant": false, "payable": true, "type": "function"}, {"name": "ethToTokenSwapInput", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "min_tokens"}, {"type": "uint256", "name": "deadline"}], "constant": false, "payable": true, "type": "function", "gas": 12757}, {"name": "ethToTokenTransferInput", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "min_tokens"}, {"type": "uint256", "name": "deadline"}, {"type": "address", "name": "recipient"}], "constant": false, "payable": true, "type": "function", "gas": 12965}, {"name": "ethToTokenSwapOutput", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "tokens_bought"}, {"type": "uint256", "name": "deadline"}], "constant": false, "payable": true, "type": "function", "gas": 50463}, {"name": "ethToTokenTransferOutput", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "tokens_bought"}, {"type": "uint256", "name": "deadline"}, {"type": "address", "name": "recipient"}], "constant": false, "payable": true, "type": "function", "gas": 50671}, {"name": "tokenToEthSwapInput", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "tokens_sold"}, {"type": "uint256", "name": "min_eth"}, {"type": "uint256", "name": "deadline"}], "constant": false, "payable": false, "type": "function", "gas": 47503}, {"name": "tokenToEthTransferInput", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "tokens_sold"}, {"type": "uint256", "name": "min_eth"}, {"type": "uint256", "name": "deadline"}, {"type": "address", "name": "recipient"}], "constant": false, "payable": false, "type": "function", "gas": 47712}, {"name": "tokenToEthSwapOutput", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "eth_bought"}, {"type": "uint256", "name": "max_tokens"}, {"type": "uint256", "name": "deadline"}], "constant": false, "payable": false, "type": "function", "gas": 50175}, {"name": "tokenToEthTransferOutput", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "eth_bought"}, {"type": "uint256", "name": "max_tokens"}, {"type": "uint256", "name": "deadline"}, {"type": "address", "name": "recipient"}], "constant": false, "payable": false, "type": "function", "gas": 50384}, {"name": "tokenToTokenSwapInput", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "tokens_sold"}, {"type": "uint256", "name": "min_tokens_bought"}, {"type": "uint256", "name": "min_eth_bought"}, {"type": "uint256", "name": "deadline"}, {"type": "address", "name": "token_addr"}], "constant": false, "payable": false, "type": "function", "gas": 51007}, {"name": "tokenToTokenTransferInput", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "tokens_sold"}, {"type": "uint256", "name": "min_tokens_bought"}, {"type": "uint256", "name": "min_eth_bought"}, {"type": "uint256", "name": "deadline"}, {"type": "address", "name": "recipient"}, {"type": "address", "name": "token_addr"}], "constant": false, "payable": false, "type": "function", "gas": 51098}, {"name": "tokenToTokenSwapOutput", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "tokens_bought"}, {"type": "uint256", "name": "max_tokens_sold"}, {"type": "uint256", "name": "max_eth_sold"}, {"type": "uint256", "name": "deadline"}, {"type": "address", "name": "token_addr"}], "constant": false, "payable": false, "type": "function", "gas": 54928}, {"name": "tokenToTokenTransferOutput", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "tokens_bought"}, {"type": "uint256", "name": "max_tokens_sold"}, {"type": "uint256", "name": "max_eth_sold"}, {"type": "uint256", "name": "deadline"}, {"type": "address", "name": "recipient"}, {"type": "address", "name": "token_addr"}], "constant": false, "payable": false, "type": "function", "gas": 55019}, {"name": "tokenToExchangeSwapInput", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "tokens_sold"}, {"type": "uint256", "name": "min_tokens_bought"}, {"type": "uint256", "name": "min_eth_bought"}, {"type": "uint256", "name": "deadline"}, {"type": "address", "name": "exchange_addr"}], "constant": false, "payable": false, "type": "function", "gas": 49342}, {"name": "tokenToExchangeTransferInput", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "tokens_sold"}, {"type": "uint256", "name": "min_tokens_bought"}, {"type": "uint256", "name": "min_eth_bought"}, {"type": "uint256", "name": "deadline"}, {"type": "address", "name": "recipient"}, {"type": "address", "name": "exchange_addr"}], "constant": false, "payable": false, "type": "function", "gas": 49532}, {"name": "tokenToExchangeSwapOutput", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "tokens_bought"}, {"type": "uint256", "name": "max_tokens_sold"}, {"type": "uint256", "name": "max_eth_sold"}, {"type": "uint256", "name": "deadline"}, {"type": "address", "name": "exchange_addr"}], "constant": false, "payable": false, "type": "function", "gas": 53233}, {"name": "tokenToExchangeTransferOutput", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "tokens_bought"}, {"type": "uint256", "name": "max_tokens_sold"}, {"type": "uint256", "name": "max_eth_sold"}, {"type": "uint256", "name": "deadline"}, {"type": "address", "name": "recipient"}, {"type": "address", "name": "exchange_addr"}], "constant": false, "payable": false, "type": "function", "gas": 53423}, {"name": "getEthToTokenInputPrice", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "eth_sold"}], "constant": true, "payable": false, "type": "function", "gas": 5542}, {"name": "getEthToTokenOutputPrice", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "tokens_bought"}], "constant": true, "payable": false, "type": "function", "gas": 6872}, {"name": "getTokenToEthInputPrice", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "tokens_sold"}], "constant": true, "payable": false, "type": "function", "gas": 5637}, {"name": "getTokenToEthOutputPrice", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "uint256", "name": "eth_bought"}], "constant": true, "payable": false, "type": "function", "gas": 6897}, {"name": "tokenAddress", "outputs": [{"type": "address", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1413}, {"name": "factoryAddress", "outputs": [{"type": "address", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1443}, {"name": "balanceOf", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "address", "name": "_owner"}], "constant": true, "payable": false, "type": "function", "gas": 1645}, {"name": "transfer", "outputs": [{"type": "bool", "name": "out"}], "inputs": [{"type": "address", "name": "_to"}, {"type": "uint256", "name": "_value"}], "constant": false, "payable": false, "type": "function", "gas": 75034}, {"name": "transferFrom", "outputs": [{"type": "bool", "name": "out"}], "inputs": [{"type": "address", "name": "_from"}, {"type": "address", "name": "_to"}, {"type": "uint256", "name": "_value"}], "constant": false, "payable": false, "type": "function", "gas": 110907}, {"name": "approve", "outputs": [{"type": "bool", "name": "out"}], "inputs": [{"type": "address", "name": "_spender"}, {"type": "uint256", "name": "_value"}], "constant": false, "payable": false, "type": "function", "gas": 38769}, {"name": "allowance", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [{"type": "address", "name": "_owner"}, {"type": "address", "name": "_spender"}], "constant": true, "payable": false, "type": "function", "gas": 1925}, {"name": "name", "outputs": [{"type": "bytes32", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1623}, {"name": "symbol", "outputs": [{"type": "bytes32", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1653}, {"name": "decimals", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1683}, {"name": "totalSupply", "outputs": [{"type": "uint256", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1713}] ``` Technically the entire ERC20 ABI is included in the Exchange ABI, so debatably you could use the Exchange ABI for both. I don't know if there are performances issues with that since the Exchange ABI is much longer. ### Web3 Create an instance of an ERC20 or exchange contract only requires the address and ABI. ```javascript contract = new web3.eth.Contract(ABI, address); ``` ERC20 contracts are used in queries of user balances, approval states (allowances), and ERC20 balances of exchanges (used in pricing or trading and pooling). They are also used in `approve` transactions that unlock usage of a token as an input on a per-account basis. Exchange contracts are used in queries of of a users liquidity balances in the `pool` tab. They are also used in all transactions that `swap`, `send`, `add liquidity`, or `remove liquidity`. The token `name`, `symbol`, and `decimals` can also be queried (and should for arbitrary tokens) but this is unnecessary for tokens in the hardcoded list. # Factory ## Get Exchange There is a separate exchange contract for every ERC20 token. This function is used retreive the exchange address associated with an ERC20 token. ```javascript factory = new web3.eth.Contract(factoryABI, factoryAddress) exchangeAddress = factory.methods.getExchange(tokenAddress) ``` :::info If `exchangeAddress == 0x0000000000000000000000000000000000000000` the token does not yet have an exchange. ::: ## Get Token This function is used retreive the ERC20 token address associated with a Uniswap exchange. ```javascript factory = new web3.eth.Contract(factoryABI, factoryAddress) tokenAddress = factory.methods.getToken(exchangeAddress) ``` :::info If `tokenAddress == 0x0000000000000000000000000000000000000000` the address is not a Uniswap exchange. ::: ## Create Exchange This function is used to launch an exchange for an ERC20 token that does not yet have an exchange. ```javascript factory = new web3.eth.Contract(factoryABI, factoryAddress) factory.methods.createExchange(tokenAddress).send({from: userAddress}) ``` # Block Timestamp The block timestamp is needed for calculating transaction deadlines. It should be updated every tick. ```javascript web3.eth.getBlock('latest', (error, block) => { lastTimestamp = block.timestamp }) ``` # User Balances ## ETH Balance A users ETH Balance is shown in token selection screen (defaults to ETH), and should be queried immediately, and then every tick (15 seconds) for as long as the user is connected. ```javascript web3.eth.getBalance(address, (error, result) => { ethBalance = result; }); ``` ## Token Balances Because user balances are shown in the token selection of `swap`and`send` and pool token balances are shown in the token selection of `pool`, these contracts should be instantiated and the balances queried as soon as web3 is availible/unlocked and updated every tick. ### ERC20 balances ```javascript tokenContract = new web3.eth.Contract(tokenABI, tokenAddress); tokenContract.methods.balanceOf(userAddress).call((result, error) => { userBalance = result }); ``` ### Pool token balances ```javascript // You can also use tokenABI here, since balanceOf is the same for both exchangeContract = new web3.eth.Contract(exchangeABI, exchangeAddress); exchangeContract.methods.balanceOf(userAddress).call((result, error) => { userPoolBalance = result }); ``` # Exchange Balances (reserves) Exchange reserves are needed for determining exchange rate and pool size, and should be queried immediately when the ERC20 token traded on that exchange is selected (in both input and output), and updated every tick for as long as its selected. ## ETH Reserves The ETH reserve associated with an ERC20 token is found by checking the ETH balance of the exchange contract assoicated with that token. ```javascript web3.eth.getBalance(exchangeAddress, (error, result) => { ethReserve = result; }); ``` ## ERC20 Reserves The ERC20 reserve of an ERC20 token is found by checking the ERC20 balance of the exchange contract assoicated with that token. ```javascript tokenContract = new web3.eth.Contract(tokenABI, tokenAddress); tokenContract.methods.balanceOf(exchangeAddress).call((result, error) => { tokenReserve = result }); ``` # Allowances and Approval (unlock) Allowances are needed to determine if a token can be spent on behalf of the user by the exchange contracts. Allowances should be queried on any ERC20 token selected as an **input**. If the allowance of an exchange spending on behalf of a user is 0, then token sold on that exchange should be marked as locked when selected. ```javascript // tokenContract is the ERC20 sold on the exchange at exchangeAddress tokenContract.methods.allowance(userAddress, exchangeAddress).call((result, error) => { allowanceAmount = result }); ``` In order to approve or "unlock" an input token, a transaction must be made to the approve function. The amount approved (for now) should be `decimals * 10^8` (remember a decimals value will be hardcoded for each token built into the site). ```javascript tokenContract.methods.approve(exchangeAddress, amount).send({from: userAddress}) }); ``` Allowance should be queried once on **INPUT** token selection. If token is "locked", it should be checked again every tick until unlocked or deselected. # ETH <-> ERC20 Calculations :::info **IMPORTANT NOTE:** Smart contracts do not support decimal values, and instead simulate decimals using large integers. One Ether is stored as `1*10**18` since ETH has 18 decimal places. The same applies to all ERC20 tokens, although they do not all have the same decimals value. All values should be stored as large integers and all calculations should be done using integer math to best match smart contract calculations. Values should only be converted to "human readable" format (divided by their number of decimals) at the instant they are displayed on the frontend. **All human input values should immediately be multiplied by the decimals value of that token before it is used for any calculations.** ::: Uniswap allows exchange between ETH and ERC20 tokens in either direction. Users can either set an exact input amount they want to sell, or an exact output amount they want to buy. The amount they are able to buy with a given input (or the amount they have to sell to buy a given output) is determined by only three factors: 1. The user specified input or output amount 3. The reserve size (balance) of the input 4. The reserve size (balance) of the output ### Calculate Output (user specifies input) ```javascript numerator = inputAmount * outputReserve * 997 denominator = inputReserve * 1000 + inputAmount * 997 outputAmount = numerator / denominator ``` ### Calculate Input (user specifies output) ```javascript numerator = outputAmount * inputReserve * 1000 denominator = (outputReserve - outputAmount) * 997 inputAmount = numerator / denominator + 1 ``` ### Calculate Exchange Rate ```javascript exchangeRate = outputAmount/inputAmount ``` ### Calculate Liquidity Provider Fee ```javascript fee = inputAmount * 0.003 ``` # ETH -> ERC20 Example Calculation ### User sells one ETH for DAI tokens ```javascript // ETH has 18 decimals inputDecimals = 10**18 // DAI has 18 decimals outputDecimals = 10**18 // DAI Token tokenAddress = "0x2448eE2641d78CC42D7AD76498917359D961A783" // DAI Exchange exchangeAddress = "0x77dB9C915809e7BE439D2AB21032B1b8B58F6891" // DAI Token Contract token = new web3.eth.Contract(tokenABI, tokenAddress); // User sells 1 ETH userInput = 1 // Formula numerator = inputAmount * outputReserve * 997 denominator = inputReserve * 1000 + inputAmount * 997 outputAmount = numerator / denominator // Multiply user input by input decimals inputAmount = userInput * inputDecimals = 1*10*18 // 50 ETH in reserves inputReserve = web3.eth.getBalance(exchangeAddress) = 50*10**18 // 50000 DAI in reserves outputReserve = token.methods.balanceOf(exchangeAddress) = 10000*10**18 numerator = (1*10**18) * (10000*10**18) * 997 denominator = (50*10**18) * 1000 + (1*10**18) * 997 outputAmount = numerator/denominator = 1.955016*10**20 userOutput = outputAmount/outputDecimals = 195.5016 DAI exchange Rate = outputAmount/inputAmount = 195.5016 DAI/ETH fee = inputAmount*0.003 = 0.003 ETH ``` # ERC20 <-> ERC20 Calculations The exchange rate between TokenA (ERC20) and TokenB (ERC20) on ExchangeA (ETH-TokenA exchange) is determined by calculating TokenA-to-ETH on ExchangeA and then ETH-to-TokenB on ExchangeB. Below is an example of determining the TokenB output for an exact TokenA input. ### Calculate Output (given exact input) ```javascript inputAmountA = userInput inputReserveA = tokenA.methods.balanceOf(exchangeAddressA) outputReserveA = web3.eth.getBalance(exchangeAddressA) numeratorA = inputAmountA * outputReserveA * 997 denominatorA = inputReserveA * 1000 + inputAmountA * 997 outputAmountA = numeratorA / denominatorA inputAmountB = outputAmountA inputReserveB = tokenB.methods.balanceOf(exchangeAddressB) outputReserveB = web3.eth.getBalance(exchangeAddressB) numeratorB = inputAmountB * outputReserveB * 997 denominatorB = inputReserveB * 1000 + inputAmountB * 997 outputAmountB = numeratorB / denominatorB ``` ### Calculate Input (given exact output) Similarily, below is an example of determining the TokenA input needed for an exact TokenB output: ```javascript outputAmountB = userInput inputReserveB = web3.eth.getBalance(exchangeAddressB) outputReserveB = tokenB.methods.balanceOf(exchangeAddressB) numeratorB = outputAmountB * inputReserveB * 1000 denominatorB = (outputReserveB - outputAmountB) * 997 inputAmountB = numeratorB / denominatorB + 1 outputAmountA = inputAmountB inputReserveA = tokenA.methods.balanceOf(exchangeAddressA) outputReserveA = web3.eth.getBalance(exchangeAddressA) numeratorA = outputAmountA * inputReserveA * 1000 denominatorA = (outputReserveA - outputAmountA) * 997 inputAmountA = numeratorA / denominatorA + 1 ``` ### Calculate Exchange Rate ```javascript exchangeRate = outputAmountB/inputAmountA ``` ### Calculate Liquidity Provider Fee ```javascript fee = inputAmountA * 0.006 ``` # ETH <-> ERC20 Parameters :::info All recipient addresses should be passed in as strings. It is recommended that only checksummed addresses be accepted. All ETH and Token values will be large integers. These integer should be convereted to strings before making any web3 calls, since there can be issues with javascript big numbers. If there are any decimals the transactions will fail. Transaction deadlines are smaller numbers and can be passed in as integers or strings. ::: ## ETH Sold For `ethToTokenInput` tranactions, the amount of ETH sold is the ETH value attached to the function call. ## Tokens Bought `tokens_bought` is the output amount of an exact output, max input `ethToTokenOutput` or `tokenToTokenOutput` transaction ## Tokens Sold `tokens_sold` is the the input amount of a `tokenToEthInput` or `tokenToTokenInput` transaction. ## ETH Bought `eth_bought` is the output amount of a `tokenToEthOutput` transaction. ## Minimum and Maximum values Uniswap prices are entirely based on availible liquidity which can change between the time a transaction is signed and when it is included in a block. To prevent users from getting a worse deal than expected, all trade functions have parameters that bound the possible price slippage after which a transaction will fail. For a user given input there is a minimum output, and for a user given output there is a maximum input. To calculate these min/max values, a constant called `ALLOWED_SLIPPAGE` should be set. Eventually this should be changable through user input under an advanced tab, but for now it can be set to `0.025` (2.5%). ### Given Input `minOutput = outputAmount * (1 - ALLOWED_SLIPPAGE)` ### Given Output `maxInput = inputAmount * (1 + ALLOWED_SLIPPAGE)` ### ETH -> ERC20 Example ```javascript inputAmount = 1*10*18 // 1 ETH inputReserve = 50*10**18 // 50 ETH outputReserve = 10000*10**18 // 10000 DAI numerator = inputAmount * outputReserve * 997 denominator = inputReserve * 1000 + inputAmount * 997 outputAmount = numerator / denominator minOutput = outputAmount * (1 - ALLOWED_SLIPPAGE) outputAmount = 1.955016*10**20 // 195.5016 DAI // Trade will fail if the user is going to receive less than 190.6141 DAI minOutput = 1.906141*10**20 ``` ## Deadlines Every trading function has a `deadline` parameter. This is a safety feature that allows users to specify the a time after which a trade will no longer execute. This bounds the "free option" problem, where miners can hold signed transactions and decide to execute them based on market conditions at a future time. Eventually this should be a user selected "advanced" option, but for now it can be set to 5 minutes after the timestamp of the latest block: ```javascript deadline = block.timestamp + 300 ``` ## Recipient `recipient` is the address of the account that will receive tokens in a "trade and transfer" transaction. This feature is called **Send** in the frontend and **Transfer** in the smart contract. # ETH -> ERC20 Trades ## ethToTokenSwapInput ```javascript exchange.methods.ethToTokenSwapInput( min_tokens, deadline ).send({from: userAddress, value: ethInput}) ``` ## ethToTokenTransferInput ```javascript exchange.methods.ethToTokenTransferInput( min_tokens, deadline, recipient ).send({from: userAddress, value: ethInput}) ``` ## ethToTokenSwapOutput ```javascript exchange.methods.ethToTokenSwapOutput( tokens_bought, deadline ).send({from: userAddress, value: maxEth}) ``` ## ethToTokenTransferOutput ```javascript exchange.methods.ethToTokenTransferOutput( tokens_bought, deadline, recipient ).send({from: userAddress, value: maxEth}) ``` # ERC20 -> ETH Trades ## tokenToEthSwapInput ```javascript exchange.methods.tokenToEthSwapInput( tokens_sold, min_eth, deadline ).send({from: userAddress}) ``` ## tokenToEthTransferInput ```javascript exchange.methods.tokenToEthTransferInput( tokens_sold, min_eth, deadline, recipient ).send({from: userAddress}) ``` ## tokenToEthSwapOutput ```javascript exchange.methods.tokenToEthSwapOutput( eth_bought, max_tokens, deadline ).send({from: userAddress}) ``` ## tokenToEthTransferOutput ```javascript exchange.methods.tokenToEthTransferOutput( eth_bought, max_tokens, deadline, recipient ).send({from: userAddress}) ``` # ERC20 -> ERC20 Trades For ERC20 to ERC20 pairs `ALLOWED_SLIPPAGE` should be a higher value, since two exchanges are now fluctuating. For now lets go with `0.04` (4%). ## Parameters ### token_addr The address of the token being purchased. So for a MKR -> DAI trade, the trade would be made on the MKR exchange and the DAI token address would be passed in as `token_addr` ### min_eth_bought For tokenToTokenInput trades, `min_eth_bought` can be used used to bound slippage on a per exchange basis instead of across both exchanges. This is unnecessary for most people who will only care about `min_tokens_bought`. The call will fail if its set to 0, so we can set it to 1. ### max_eth_sold For tokenToTokenOutput trades, `max_eth_sold` can be used used to bound slippage on a per exchange basis instead of across both exchanges. This is unnecessary for most people who will only care about `max_tokens_sold`. The expected value of ETH purchased on exchange 1 and sold to Exchange 2 is represented by `inputAmountB` in the block of code below. ```javascript outputAmountB = userInput inputReserveB = web3.eth.getBalance(exchangeAddressB) outputReserveB = tokenB.methods.balanceOf(exchangeAddressB) numeratorB = outputAmountB * inputReserveB * 1000 denominatorB = (outputReserveB - outputAmountB) * 997 inputAmountB = numeratorB / denominatorB + 1 outputAmountA = inputAmountB inputReserveA = tokenA.methods.balanceOf(exchangeAddressA) outputReserveA = web3.eth.getBalance(exchangeAddressA) numeratorA = outputAmountA * inputReserveA * 1000 denominatorA = (outputReserveA - outputAmountA) * 997 inputAmountA = numeratorA / denominatorA + 1 ``` To make sure the code executes, max_eth_sold can be set to `1.2 * inputAmountB`. ## tokenToTokenSwapInput ```javascript exchange.methods.tokenToTokenSwapInput( tokens_sold, min_tokens_bought, min_eth_bought, deadline, token_addr ).send({from: userAddress}) ``` ## tokenToTokenTransferInput ```javascript exchange.methods.tokenToTokenTransferInput( tokens_sold, min_tokens_bought, min_eth_bought, deadline, recipient, token_addr ).send({from: userAddress}) ``` ## tokenToTokenSwapOutput ```javascript exchange.methods.tokenToTokenSwapOutput( tokens_bought, max_tokens_sold, max_eth_sold, deadline, token_addr ).send({from: userAddress}) ``` ## tokenToTokenTransferOutput ```javascript exchange.methods.tokenToTokenTransferOutput( tokens_bought, max_tokens_sold, max_eth_sold, deadline, recipient, token_addr ).send({from: userAddress}) ``` # Liquidity Pool User Balance A users liquidity pool balance can be found with the following function call (also shown earlier in this document): ```javascript // You can also use tokenABI here since balanceOf is an ERC20 function exchangeContract = new web3.eth.Contract(exchangeABI, exchangeAddress) userPoolBalance = exchangeContract.methods.balanceOf(userAddress) ``` # Liquidity Pool Total Supply The total supply of all liquidity is found with the following function call: ```javascript // You can also use tokenABI here since totalSupply is an ERC20 function exchangeContract = new web3.eth.Contract(exchangeABI, exchangeAddress); totalSupply = exchangeContract.methods.balanceOf(userAddress) ``` # Liquidity Pool Marginal Exchange Rate The exchange rate displayed in the pool section is the rate as if you bought an infintessimally small purchase in either dection. It is calculated by dividing the token reserve over the eth reserve. ```javascript ethReserve = web3.eth.getBalance(exchangeAddress) tokenReserve.methods.balanceOf(exchangeAddressB) exchangeRate = tokenReserve / ethReserve ``` # Add Liquidity Calculations ## Liquidity Amount The amount of ETH sent to the `addLiquidity` function determines the number of liquidity tokens that will be minted for the user. ```javascript ethReserve = web3.eth.getBalance(exchangeAddress) totalLiquidity = exchange.methods.totalSupply() liquidityMinted = totalLiquidity * ethAmount / ethReserve ``` ## Token Amount The amount of ETH sent to the `addLiquidity` function also determines the amount of ERC20 tokens needed. ```javascript ethReserve = web3.eth.getBalance(exchangeAddress) tokenReserve.methods.balanceOf(exchangeAddress) // +1 added to prevent rounding errors from working against // existing liquidity providers tokenAmount = tokenReserve * ethAmount / ethReserve + 1 ``` If a user wants to input a specific amount of tokens, working backwards through this formula will give you the required ETH amount. # Add Liquidity ## Parameters (if total supply is 0) When the total number of liquidity tokens in an exchange is 0 the first liquidity provider to join the pool can choose any exchange rate. ### min_liquidity `min_liqudity` is not used at all when `totalSupply == 0`. It should be set to 0 in this case. ### max_tokens When `totalSupply == 0`, `max_tokens` is the same as the amount of tokens added to the pool (user input). ### ethAmount `ethAmount` is the user input amount of ETH added to the pool. When there is no liqudity in an exchange, `ethAmount` must be greater than `1 gwei` (`0.000000001 ETH`). ### deadline Same as trading functions. ## Parameters (if total supply is greater than 0) When the total number of liquidity tokens is greater than 0, liquidity providers must add liquidity at the current exchange rate. ### min_liquidity Variance in the amount of liquidity minted can be bounded by the `min_liquidity` paramter. For this we will need a `MAX_LIQUIDITY_SLIPPAGE` variable, which we can set to 0.025 or 2.5%. ```javascript min_liquidity = liquidityMinted * (1 - MAX_LIQUIDITY_SLIPPAGE) ``` ### max_tokens `token_amount` can fluctuate and is bounded with the `max_tokens` variable. ```javascript max_tokens = tokenAmount * (1 + MAX_LIQUIDITY_SLIPPAGE) ``` ## Function Call ```javascript exchange.methods.addLiquidity( min_liquidity, max_tokens, deadline ).send({from: userAddress, value: ethAmount}) ``` # Remove Liquidity Calculations ## ETH Withdrawn The amount of ETH withdrawn from the `removeLiquidity` function is determined by the number of liquidity tokens that the user burns (user input). ```javascript ethReserve = web3.eth.getBalance(exchangeAddress) totalLiquidity = exchange.methods.totalSupply() ethWithdrawn = ethReserve * liquidityBurned / totalLiquidity ``` ## Tokens Withdrawn The amount of ERC20 tokens withdrawn from the `removeLiquidity` function is determined by the number of liquidity tokens that the user burns (user input). ```javascript tokenReserve.methods.balanceOf(exchangeAddress) totalLiquidity = exchange.methods.totalSupply() tokensWithdrawn = tokenReserve * liquidityBurned / total_liquidity ``` # Remove Liquidity ## Parameters ### amount The user input amount of liquidity tokens burned. ### min_eth Used to bound the minimum amount of ETH withdrawn for burning `amount` of liquidity tokens. ### min_tokens Used to bound the minimum amount of ERC20 tokens withdrawn for burning `amount` of liquidity tokens. ### deadline Same as trading functions. ### Function Call ```javascript exchange.methods.removeLiquidity( amount, min_eth, min_tokens, deadline ).send({from: userAddress}) ```

    Import from clipboard

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lost their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template is not available.


    Upgrade

    All
    • All
    • Team
    No template found.

    Create custom template


    Upgrade

    Delete template

    Do you really want to delete this template?

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Tutorials

    Book Mode Tutorial

    Slide Mode Tutorial

    YAML Metadata

    Contacts

    Facebook

    Twitter

    Feedback

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions

    Versions and GitHub Sync

    Sign in to link this note to GitHub Learn more
    This note is not linked with GitHub Learn more
     
    Add badge Pull Push GitHub Link Settings
    Upgrade now

    Version named by    

    More Less
    • Edit
    • Delete

    Note content is identical to the latest version.
    Compare with
      Choose a version
      No search result
      Version not found

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub

        Please sign in to GitHub and install the HackMD app on your GitHub repo. Learn more

         Sign in to GitHub

        HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Available push count

        Upgrade

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Upgrade

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully