├── 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 |
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 |
--------------------------------------------------------------------------------