├── README.md ├── bundle.js ├── index.html ├── index.js └── style.css /README.md: -------------------------------------------------------------------------------- 1 | "# WeekNineSwap" 2 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Swap Demo Tutorial 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 23 |
24 |
25 |
26 |

Swap

27 |
28 |
29 |
30 | 31 | 32 |
33 |
34 | 35 |
36 |
37 |
38 |
39 | 40 | 41 |
42 |
43 | 44 |
45 |
46 |
Estimated Gas:
47 | 48 |
49 |
50 |
51 |
52 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const BigNumber = require('bignumber.js'); 2 | const qs = require('qs'); 3 | const web3 = require('web3'); 4 | 5 | let currentTrade = {}; 6 | let currentSelectSide; 7 | let tokens; 8 | 9 | async function init() { 10 | await listAvailableTokens(); 11 | } 12 | 13 | async function listAvailableTokens(){ 14 | console.log("initializing"); 15 | let response = await fetch('https://tokens.coingecko.com/uniswap/all.json'); 16 | let tokenListJSON = await response.json(); 17 | console.log("listing available tokens: ", tokenListJSON); 18 | tokens = tokenListJSON.tokens; 19 | console.log("tokens: ", tokens); 20 | 21 | // Create token list for modal 22 | let parent = document.getElementById("token_list"); 23 | for (const i in tokens){ 24 | // Token row in the modal token list 25 | let div = document.createElement("div"); 26 | div.className = "token_row"; 27 | let html = ` 28 | 29 | ${tokens[i].symbol} 30 | `; 31 | div.innerHTML = html; 32 | div.onclick = () => { 33 | selectToken(tokens[i]); 34 | }; 35 | parent.appendChild(div); 36 | }; 37 | } 38 | 39 | async function selectToken(token){ 40 | closeModal(); 41 | currentTrade[currentSelectSide] = token; 42 | console.log("currentTrade: ", currentTrade); 43 | renderInterface(); 44 | } 45 | 46 | function renderInterface(){ 47 | if (currentTrade.from){ 48 | console.log(currentTrade.from) 49 | document.getElementById("from_token_img").src = currentTrade.from.logoURI; 50 | document.getElementById("from_token_text").innerHTML = currentTrade.from.symbol; 51 | } 52 | if (currentTrade.to){ 53 | console.log(currentTrade.to) 54 | document.getElementById("to_token_img").src = currentTrade.to.logoURI; 55 | document.getElementById("to_token_text").innerHTML = currentTrade.to.symbol; 56 | } 57 | } 58 | 59 | async function connect() { 60 | if (typeof window.ethereum !== "undefined") { 61 | try { 62 | console.log("connecting"); 63 | await ethereum.request({ method: "eth_requestAccounts" }); 64 | } catch (error) { 65 | console.log(error); 66 | } 67 | document.getElementById("login_button").innerHTML = "Connected"; 68 | // const accounts = await ethereum.request({ method: "eth_accounts" }); 69 | document.getElementById("swap_button").disabled = false; 70 | } else { 71 | document.getElementById("login_button").innerHTML = "Please install MetaMask"; 72 | } 73 | } 74 | 75 | function openModal(side){ 76 | currentSelectSide = side; 77 | document.getElementById("token_modal").style.display = "block"; 78 | } 79 | 80 | function closeModal(){ 81 | document.getElementById("token_modal").style.display = "none"; 82 | } 83 | 84 | async function getPrice(){ 85 | console.log("Getting Price"); 86 | 87 | if (!currentTrade.from || !currentTrade.to || !document.getElementById("from_amount").value) return; 88 | let amount = Number(document.getElementById("from_amount").value * 10 ** currentTrade.from.decimals); 89 | 90 | const params = { 91 | sellToken: currentTrade.from.address, 92 | buyToken: currentTrade.to.address, 93 | sellAmount: amount, 94 | } 95 | 96 | // Fetch the swap price. 97 | const response = await fetch(`https://api.0x.org/swap/v1/price?${qs.stringify(params)}`); 98 | 99 | swapPriceJSON = await response.json(); 100 | console.log("Price: ", swapPriceJSON); 101 | 102 | document.getElementById("to_amount").value = swapPriceJSON.buyAmount / (10 ** currentTrade.to.decimals); 103 | document.getElementById("gas_estimate").innerHTML = swapPriceJSON.estimatedGas; 104 | } 105 | 106 | async function getQuote(account){ 107 | console.log("Getting Quote"); 108 | 109 | if (!currentTrade.from || !currentTrade.to || !document.getElementById("from_amount").value) return; 110 | let amount = Number(document.getElementById("from_amount").value * 10 ** currentTrade.from.decimals); 111 | 112 | const params = { 113 | sellToken: currentTrade.from.address, 114 | buyToken: currentTrade.to.address, 115 | sellAmount: amount, 116 | takerAddress: account, 117 | } 118 | 119 | // Fetch the swap quote. 120 | const response = await fetch(`https://api.0x.org/swap/v1/quote?${qs.stringify(params)}`); 121 | 122 | swapQuoteJSON = await response.json(); 123 | console.log("Quote: ", swapQuoteJSON); 124 | 125 | document.getElementById("to_amount").value = swapQuoteJSON.buyAmount / (10 ** currentTrade.to.decimals); 126 | document.getElementById("gas_estimate").innerHTML = swapQuoteJSON.estimatedGas; 127 | 128 | return swapQuoteJSON; 129 | } 130 | 131 | async function trySwap(){ 132 | const erc20abi= [{ "inputs": [ { "internalType": "string", "name": "name", "type": "string" }, { "internalType": "string", "name": "symbol", "type": "string" }, { "internalType": "uint256", "name": "max_supply", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "constructor" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, { "indexed": true, "internalType": "address", "name": "spender", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "Approval", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "Transfer", "type": "event" }, { "inputs": [ { "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "spender", "type": "address" } ], "name": "allowance", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "approve", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "account", "type": "address" } ], "name": "balanceOf", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "burn", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "account", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "burnFrom", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "decimals", "outputs": [ { "internalType": "uint8", "name": "", "type": "uint8" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "subtractedValue", "type": "uint256" } ], "name": "decreaseAllowance", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "addedValue", "type": "uint256" } ], "name": "increaseAllowance", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "name", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "symbol", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "totalSupply", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "recipient", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "transfer", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "recipient", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "transferFrom", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }] 133 | console.log("trying swap"); 134 | 135 | // Only work if MetaMask is connect 136 | // Connecting to Ethereum: Metamask 137 | const web3 = new Web3(Web3.givenProvider); 138 | 139 | // The address, if any, of the most recently used account that the caller is permitted to access 140 | let accounts = await ethereum.request({ method: "eth_accounts" }); 141 | let takerAddress = accounts[0]; 142 | console.log("takerAddress: ", takerAddress); 143 | 144 | const swapQuoteJSON = await getQuote(takerAddress); 145 | 146 | // Set Token Allowance 147 | // Set up approval amount 148 | const fromTokenAddress = currentTrade.from.address; 149 | const maxApproval = new BigNumber(2).pow(256).minus(1); 150 | console.log("approval amount: ", maxApproval); 151 | const ERC20TokenContract = new web3.eth.Contract(erc20abi, fromTokenAddress); 152 | console.log("setup ERC20TokenContract: ", ERC20TokenContract); 153 | 154 | // Grant the allowance target an allowance to spend our tokens. 155 | const tx = await ERC20TokenContract.methods.approve( 156 | swapQuoteJSON.allowanceTarget, 157 | maxApproval, 158 | ) 159 | .send({ from: takerAddress }) 160 | .then(tx => { 161 | console.log("tx: ", tx) 162 | }); 163 | 164 | // Perform the swap 165 | const receipt = await web3.eth.sendTransaction(swapQuoteJSON); 166 | console.log("receipt: ", receipt); 167 | } 168 | 169 | init(); 170 | 171 | document.getElementById("login_button").onclick = connect; 172 | document.getElementById("from_token_select").onclick = () => { 173 | openModal("from"); 174 | }; 175 | document.getElementById("to_token_select").onclick = () => { 176 | openModal("to"); 177 | }; 178 | document.getElementById("modal_close").onclick = closeModal; 179 | document.getElementById("from_amount").onblur = getPrice; 180 | document.getElementById("swap_button").onclick = trySwap; -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | #window{ 2 | margin-top: 50px; 3 | background-color: #000; 4 | color: #fff; 5 | padding: 15px; 6 | border-radius: 20px; 7 | box-shadow: 0 0 5px black; 8 | } 9 | .swapbox_select { 10 | width: 50%; 11 | float: left; 12 | } 13 | .swapbox{ 14 | overflow: auto; 15 | margin: 20px 0; 16 | padding: 20px; 17 | background-color: #2f2f2f; 18 | border-radius: 20px; 19 | border: 1px solid #565656; 20 | } 21 | .token_select{ 22 | padding:5px 0; 23 | } 24 | .token_select:hover{ 25 | background-color: #464646; 26 | cursor: pointer; 27 | 28 | } 29 | .token_row{ 30 | padding: 5px 10px; 31 | } 32 | .token_row:hover{ 33 | background-color: #e4e4e4; 34 | cursor: pointer; 35 | } 36 | .gas_estimate_label{ 37 | padding:5px; 38 | } 39 | .modal-body{ 40 | height: 500px; 41 | overflow: scroll; 42 | } 43 | --------------------------------------------------------------------------------