# Road to Web3 第九周 创建一个Swap Dapp **Published by:** [SoullessL](https://paragraph.com/@nftneverdie/) **Published on:** 2022-08-17 **URL:** https://paragraph.com/@nftneverdie/road-to-web3-swap-dapp ## Content 原文地址 :https://docs.alchemy.com/docs/how-to-build-a-token-swap-dapp-with-0x-api 请大家关注我的推特(twitter.com/SoullessL)和Link3(link3.to/caishen),获取最新的Alchemy小白教程。教程汇总链接(jayjiang.gitbook.io/web3book/alchemy-road-to-web3)。准备工作进入 https://github.com/0xProject/swap-demo-tutorial ,点击fork,把代码复制到我们自己的Github。然后会转跳到我们自己的Github地址,选择 swap-demo-tutorial-part-9 进入。进入以后,我们复制这个链接地址。然后登录 https://gitpod.io/workspaces,点击 “New workspace”,然后把我们刚才拿到的地址复制进去。复制进去以后,点击下面那块灰色区域,进行加载编辑项目。进入项目以后,我们在Terminal里面输入下面的命令,切换我们的工作目录。cd swap-demo-tutorial-part-9 然后我们找到 Extensions菜单点击,在搜索框里输入Live Server,记得是图中红框里的那个,然后点击Install安装。然后我们在Terminal里面输入下面的代码安装browserify。npm install -g browserify 然后在Termianl里面分别输入(不要一起输入)下面的每一行代码,分别安装 qs,bignumber,web3npm i qs npm i bignumber npm i web3 找到index.html文件夹,如图所示的地方,添加“Select A Token” 文字。找到index.js文件,用以下的内容替换文件的所有内容const qs = require('qs'); const Web3 = require('web3'); const { default: BigNumber } = require('bignumber.js'); 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(); let response='{"name":"CoinGecko","logoURI":"https://www.coingecko.com/assets/thumbnail-007177f3eca19695592f0b8b0eabbdae282b54154e1be912285c9034ea6cbaf2.png","keywords":["defi"],"timestamp":"2022-08-17T04:08:12.925+00:00","tokens":[{"chainId":56,"address":"0x55d398326f99059fF775485246999027B3197955","name":"busd","symbol":"busd","decimals":18,"logoURI":"https://assets.coingecko.com/coins/images/9956/thumb/4943.png?1636636734"},{"chainId":56,"address":"0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c","name":"bnb","symbol":"bnb","decimals":18,"logoURI":"https://assets.coingecko.com/coins/images/9956/thumb/4943.png?1636636734"}],"version":{"major":975,"minor":1,"patch":0}}'; let tokenListJSON = JSON.parse(response); console.log("Listing available tokens: ", tokenListJSON); tokens = tokenListJSON.tokens; console.log("tokens: ", tokens); let parent = document.getElementById("token_list"); for(const i in tokens) { let div = document.createElement("div"); div.className = "token_row"; let html = `<img class="token_list_img" src="${tokens[i].logoURI}"> <span class="token_list_text">${tokens[i].symbol}</span>`; div.innerHTML = html; div.onclick = () => { selectToken(tokens[i]); } parent.appendChild(div); } } function selectToken(token) { closeModal(); currentTrade[currentSelectSide] = token; console.log("currentTrade: ", currentTrade); renderInterface(); } function renderInterface() { if(currentTrade.from) { document.getElementById("from_token_img").src = currentTrade.from.logoURI; document.getElementById("from_token_text").innerHTML = currentTrade.from.symbol; } if(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"; document.getElementById("swap_button").disabled = false; } else { document.getElementById("login_button").innerHTML = "Please install Metamask"; } } 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, } // Fetch the swap price const response = await fetch(`https://bsc.api.0x.org/swap/v1/price?${qs.stringify(params)}`); 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.symbol, buyToken: currentTrade.to.symbol, sellAmount: amount, takerAddress: account, slippagePercentage: 0.05 }; // Fetch the swap price const response = await fetch(`https://bsc.api.0x.org/swap/v1/quote?${qs.stringify(params)}`); swapQuoteJSON = await response.json(); console.log("Quote: ", swapQuoteJSON); // document.getElementById("to_amount").value = swapQuoteJSON.price; document.getElementById("gas_estimate").innerHTML = swapQuoteJSON.estimatedGas; return swapQuoteJSON; } async function trySwap() { let accounts = await ethereum.request({ method: "eth_accounts" }); let takerAddress = accounts[0]; console.log("takerAddress:", takerAddress); const swapQuoteJSON = await getQuote(takerAddress); // Set Token Allowance // Interact with ERC20TokenContract const web3 = new Web3(Web3.givenProvider); const fromTokenAddress = currentTrade.from.address; 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"); const ERC20TokenContract = new web3.eth.Contract(erc20abi, fromTokenAddress); console.log("setup ERC20TokenContract: ", ERC20TokenContract); const maxApproval = new BigNumber(2).pow(256).minus(1); console.log("approval amount: ", maxApproval); const tx = await ERC20TokenContract.methods .approve(swapQuoteJSON.allowanceTarget, maxApproval) .send({ from: takerAddress }) .then((tx) => { console.log("tx: ", tx) }); const receipt = await web3.eth.sendTransaction(swapQuoteJSON); console.log("receipt: ", receipt); } init(); function openModal(side) { currentSelectSide = side; document.getElementById("token_modal").style.display = "block"; } function closeModal() { document.getElementById("token_modal").style.display = "none"; } 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; 然后我们在Terminal里面输入下面的代码,之后我们会看到创建了一个新的 bundle.js文件。browserify index.js --standalone bundle -o bundle.js 然后我们鼠标放到index.html页面,右键点击“Open with Live Server”,过一会就会进入到我们做的swap页面,选择Token的时候应该只有bnb和busd两个token选择,则说明代码没问题了。然后我们把Metamask切换到币安链,然后点击Sign in with MetaMask登录。选择用BNB交换BUSD,这里需要在你的钱包里有一些币安币,用的是正式网,不是测试网。然后我们点击swap,会出来一个授权,让我们授权程序可以交换BNB,我们点击确定,等待主网确定。等确定完,会自动在Metamask弹出交易窗口,然后我们点击确定,就可以成功把0.001bnb交换成Busd了。然后我们在Metamask里面找到这个交易记录,把交易记录的地址复制出来,到时候填到提交表单里。 交易成功以后,为了安全,我们可以去(https://bscscan.com/tokenapprovalchecker),找到我们刚才授权的应用,取消授权。然后我们在 swap-demo-tutorial-part-9 文件夹下面,新建一个 .gitignore的文件,添加内容 node_modules,这样我们就不用上传无用的node_modules文件夹了。然后我们在Source Control 里填写备注信息,点击Commit按钮右边的小三角,点击 Commit&Push 把代码提交到我们的Github。提交表单链接:https://alchemyapi.typeform.com/roadtoweek9 表单最后填写,项目的Github地址(https://github.com/你的Github名字/swap-demo-tutorial/tree/main/swap-demo-tutorial-part-9)和你在前面复制的交易记录(https://bscscan.com/tx/你的交易记录tx)。 ## Publication Information - [SoullessL](https://paragraph.com/@nftneverdie/): Publication homepage - [All Posts](https://paragraph.com/@nftneverdie/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@nftneverdie): Subscribe to updates - [Twitter](https://twitter.com/SoullessL): Follow on Twitter