From e27cae83cae640b8f800d07def0d67b7681234e8 Mon Sep 17 00:00:00 2001 From: tylerxue Date: Sat, 27 Jan 2024 04:19:40 +0800 Subject: [PATCH 1/4] Create aaa --- aaa | 1 + 1 file changed, 1 insertion(+) create mode 100644 aaa diff --git a/aaa b/aaa new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/aaa @@ -0,0 +1 @@ + From 49e9902bc5ba35c36dcc5b2cbf2dd2df14535221 Mon Sep 17 00:00:00 2001 From: tylerxue Date: Sat, 27 Jan 2024 04:31:10 +0800 Subject: [PATCH 2/4] Delete aaa --- aaa | 1 - 1 file changed, 1 deletion(-) delete mode 100644 aaa diff --git a/aaa b/aaa deleted file mode 100644 index 8b137891..00000000 --- a/aaa +++ /dev/null @@ -1 +0,0 @@ - From e1a60bea1ea47421935a72ba2349209d786e30f8 Mon Sep 17 00:00:00 2001 From: tylerxue Date: Sat, 27 Jan 2024 04:33:29 +0800 Subject: [PATCH 3/4] Create folder --- swap-demo-tutorial-part-tyler/.gitkeep | 1 + 1 file changed, 1 insertion(+) create mode 100644 swap-demo-tutorial-part-tyler/.gitkeep diff --git a/swap-demo-tutorial-part-tyler/.gitkeep b/swap-demo-tutorial-part-tyler/.gitkeep new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/swap-demo-tutorial-part-tyler/.gitkeep @@ -0,0 +1 @@ + From 1c56e84faadef0d77ac43fa11730529b24357167 Mon Sep 17 00:00:00 2001 From: tylerxue Date: Sat, 27 Jan 2024 04:36:37 +0800 Subject: [PATCH 4/4] a standalone example that doesn't require node.js a standalone example that doesn't require Node.js, RequireJs and Browserify and other unnecessary external dependencies. --- swap-demo-tutorial-part-tyler/index.html | 75 +++++++++ swap-demo-tutorial-part-tyler/index.js | 193 +++++++++++++++++++++++ swap-demo-tutorial-part-tyler/style.css | 56 +++++++ 3 files changed, 324 insertions(+) create mode 100644 swap-demo-tutorial-part-tyler/index.html create mode 100644 swap-demo-tutorial-part-tyler/index.js create mode 100644 swap-demo-tutorial-part-tyler/style.css diff --git a/swap-demo-tutorial-part-tyler/index.html b/swap-demo-tutorial-part-tyler/index.html new file mode 100644 index 00000000..4b865aa6 --- /dev/null +++ b/swap-demo-tutorial-part-tyler/index.html @@ -0,0 +1,75 @@ + + + + + + + + Swap Demo Tutorial + + + + + + + + + + + +
+
+
+

Swap

+
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+ +
+
+
Estimated Gas:
+ +
+
+
+
+ + + + + \ No newline at end of file diff --git a/swap-demo-tutorial-part-tyler/index.js b/swap-demo-tutorial-part-tyler/index.js new file mode 100644 index 00000000..d9f382db --- /dev/null +++ b/swap-demo-tutorial-part-tyler/index.js @@ -0,0 +1,193 @@ +let currentTrade = {}; +let currentSelectSide; +let tokens; + +async function init() { + await listAvailableTokens(); +} + +async function listAvailableTokens(){ + console.log("initializing"); + let response = await fetch('https://tokens.coingecko.com/uniswap/all.json'); + let tokenListJSON = await response.json(); + console.log("listing available tokens: ", tokenListJSON); + tokens = tokenListJSON.tokens; + console.log("tokens: ", tokens); + + // Create token list for modal + let parent = document.getElementById("token_list"); + for (const i in tokens){ + // Token row in the modal token list + let div = document.createElement("div"); + div.className = "token_row"; + let html = ` + ${tokens[i].symbol} + `; + div.innerHTML = html; + div.onclick = () => { + selectToken(tokens[i]); + }; + parent.appendChild(div); + }; +} + +async function selectToken(token){ + closeModal(); + currentTrade[currentSelectSide] = token; + console.log("currentTrade: ", currentTrade); + renderInterface(); +} + +function renderInterface(){ + if (currentTrade.from){ + console.log(currentTrade.from) + document.getElementById("from_token_img").src = currentTrade.from.logoURI; + document.getElementById("from_token_text").innerHTML = currentTrade.from.symbol; + } + if (currentTrade.to){ + console.log(currentTrade.to) + document.getElementById("to_token_img").src = currentTrade.to.logoURI; + document.getElementById("to_token_text").innerHTML = currentTrade.to.symbol; + } +} + +async function connect() { + if (typeof window.ethereum !== "undefined") { + try { + console.log("connecting"); + await ethereum.request({ method: "eth_requestAccounts" }); + } catch (error) { + console.log(error); + } + document.getElementById("login_button").innerHTML = "Connected"; + // const accounts = await ethereum.request({ method: "eth_accounts" }); + document.getElementById("swap_button").disabled = false; + } else { + document.getElementById("login_button").innerHTML = "Please install MetaMask"; + } +} + +function openModal(side){ + currentSelectSide = side; + document.getElementById("token_modal").style.display = "block"; +} + +function closeModal(){ + document.getElementById("token_modal").style.display = "none"; +} + +// 替代 qs 功能 +function build_query_string(obj) { + let allKeys = Object.keys(obj); + let allVals = Object.values(obj); + let arr = []; + allKeys.forEach((k, i) => { + arr.push(`${k}=${allVals[i]}`) + }); + let str = arr.join("&"); + return str; +} + +async function getPrice(){ + console.log("Getting Price"); + + if (!currentTrade.from || !currentTrade.to || !document.getElementById("from_amount").value) return; + let amount = Number(document.getElementById("from_amount").value * 10 ** currentTrade.from.decimals); + + const params = { + sellToken: currentTrade.from.address, + buyToken: currentTrade.to.address, + sellAmount: amount, + } + + const str = build_query_string(params); + const headers = { '0x-api-key': '9dab2060-ab34-4e6a-9c20-cfb3263692df' }; // This is a placeholder. Get your live API key from the 0x Dashboard (https://dashboard.0x.org/apps) + + // Fetch the swap price. + const response = await fetch(`https://api.0x.org/swap/v1/price?${str}`, { headers }); + + swapPriceJSON = await response.json(); + console.log("Price: ", swapPriceJSON); + + document.getElementById("to_amount").value = swapPriceJSON.buyAmount / (10 ** currentTrade.to.decimals); + document.getElementById("gas_estimate").innerHTML = swapPriceJSON.estimatedGas; +} + +async function getQuote(account){ + console.log("Getting Quote"); + + if (!currentTrade.from || !currentTrade.to || !document.getElementById("from_amount").value) return; + let amount = Number(document.getElementById("from_amount").value * 10 ** currentTrade.from.decimals); + + const params = { + sellToken: currentTrade.from.address, + buyToken: currentTrade.to.address, + sellAmount: amount, + takerAddress: account, + } + + const str = build_query_string(params); + const headers = { '0x-api-key': '9dab2060-ab34-4e6a-9c20-cfb3263692df' }; // This is a placeholder. Get your live API key from the 0x Dashboard (https://dashboard.0x.org/apps) + + // Fetch the swap quote. + const response = await fetch(`https://api.0x.org/swap/v1/quote?${str}`, { headers }); + + swapQuoteJSON = await response.json(); + console.log("Quote: ", swapQuoteJSON); + + document.getElementById("to_amount").value = swapQuoteJSON.buyAmount / (10 ** currentTrade.to.decimals); + document.getElementById("gas_estimate").innerHTML = swapQuoteJSON.estimatedGas; + + return swapQuoteJSON; +} + +async function trySwap(){ + 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" }] + console.log("trying swap"); + + // Only work if MetaMask is connect + // Connecting to Ethereum: Metamask + const web3 = new Web3(Web3.givenProvider); + + // The address, if any, of the most recently used account that the caller is permitted to access + let accounts = await ethereum.request({ method: "eth_accounts" }); + let takerAddress = accounts[0]; + console.log("takerAddress: ", takerAddress); + + const swapQuoteJSON = await getQuote(takerAddress); + + // Set Token Allowance + // Set up approval amount + const fromTokenAddress = currentTrade.from.address; + const maxApproval = new BigNumber(2).pow(256).minus(1); + console.log("approval amount: ", maxApproval); + const ERC20TokenContract = new web3.eth.Contract(erc20abi, fromTokenAddress); + console.log("setup ERC20TokenContract: ", ERC20TokenContract); + + // Grant the allowance target an allowance to spend our tokens. + const tx = await ERC20TokenContract.methods.approve( + swapQuoteJSON.allowanceTarget, + maxApproval, + ) + .send({ from: takerAddress }) + .then(tx => { + console.log("tx: ", tx) + }); + + // Perform the swap + const receipt = await web3.eth.sendTransaction(swapQuoteJSON); + console.log("receipt: ", receipt); +} + +init(); + +document.getElementById("login_button").onclick = connect; +document.getElementById("from_token_select").onclick = () => { + openModal("from"); +}; +document.getElementById("to_token_select").onclick = () => { + openModal("to"); +}; +document.getElementById("modal_close").onclick = closeModal; +document.getElementById("from_amount").onblur = getPrice; +document.getElementById("swap_button").onclick = trySwap; diff --git a/swap-demo-tutorial-part-tyler/style.css b/swap-demo-tutorial-part-tyler/style.css new file mode 100644 index 00000000..4de32863 --- /dev/null +++ b/swap-demo-tutorial-part-tyler/style.css @@ -0,0 +1,56 @@ +html, nav{ + background: #333; +} +body{ + margin: auto; + background: #333; +} + +.container{ + background: #333; +} + +a, a.navbar-brand {color: antiquewhite;} + +#window{ + margin-top: 50px; + background-color: #000; + color: #fff; + padding: 15px; + border-radius: 20px; + box-shadow: 0 0 5px black; +} +.swapbox_select { + width: 50%; + float: left; +} +.swapbox{ + overflow: auto; + margin: 20px 0; + padding: 20px; + background-color: #2f2f2f; + border-radius: 20px; + border: 1px solid #565656; +} +.token_select{ + padding:5px 0; +} +.token_select:hover{ + background-color: #464646; + cursor: pointer; + +} +.token_row{ + padding: 5px 10px; +} +.token_row:hover{ + background-color: #e4e4e4; + cursor: pointer; +} +.gas_estimate_label{ + padding:5px; +} +.modal-body{ + height: 500px; + overflow: scroll; +}