# Road to Web3 第九周 创建一个Swap Dapp

By [SoullessL](https://paragraph.com/@nftneverdie) · 2022-08-17

---

原文地址 ：[https://docs.alchemy.com/docs/how-to-build-a-token-swap-dapp-with-0x-api](https://docs.alchemy.com/docs/how-to-build-a-token-swap-dapp-with-0x-api)

请大家关注我的推特([twitter.com/SoullessL](http://twitter.com/SoullessL))和Link3([link3.to/caishen](http://link3.to/caishen))，获取最新的Alchemy小白教程。教程汇总链接([jayjiang.gitbook.io/web3book/alchemy-road-to-web3](http://jayjiang.gitbook.io/web3book/alchemy-road-to-web3))。

准备工作
----

![](https://storage.googleapis.com/papyrus_images/b3379804cd6278d8911bb5a5193ea739a51f4bb119e3e120ddd0ba4a7edabe51.png)

![](https://storage.googleapis.com/papyrus_images/983a0438c35265f764d690f97af11959c42e5015529d39fd96308da112db3d5b.png)

进入 [https://github.com/0xProject/swap-demo-tutorial](https://github.com/0xProject/swap-demo-tutorial) ，点击fork，把代码复制到我们自己的Github。

![](https://storage.googleapis.com/papyrus_images/37f0c18943432eb1b1c168203dd62a08fc46ad5ef1864044971f3062aeeb874b.png)

然后会转跳到我们自己的Github地址，选择 swap-demo-tutorial-part-9 进入。

![](https://storage.googleapis.com/papyrus_images/76fdb905e016dd0955fccac3e13799647d8b862b0858e79aafc5ab11ab74aaa3.png)

![](https://storage.googleapis.com/papyrus_images/646df9da04d3cb6893e4b1d4c1a64867ac5a634c04566c0d2ffe569d5a7842ba.png)

进入以后，我们复制这个链接地址。然后登录 [https://gitpod.io/workspaces，点击](https://gitpod.io/workspaces%EF%BC%8C%E7%82%B9%E5%87%BB) “New workspace”，然后把我们刚才拿到的地址复制进去。复制进去以后，点击下面那块灰色区域，进行加载编辑项目。

![](https://storage.googleapis.com/papyrus_images/c7112877361bafe4d92dae2c2185adc0ee08c7fb34ddbf3c41815bab7a607803.png)

进入项目以后，我们在Terminal里面输入下面的命令，切换我们的工作目录。

    cd swap-demo-tutorial-part-9
    

![](https://storage.googleapis.com/papyrus_images/2e06525e9684dbbe92ceab14f7e373dfb35362afcd83a55b37eda385fab7d0c5.png)

然后我们找到 Extensions菜单点击，在搜索框里输入Live Server，记得是图中红框里的那个，然后点击Install安装。

![](https://storage.googleapis.com/papyrus_images/2e076c7b3bb12b08b6f55a9a347f1e1fc1e37bf87b3b40400d305d2645ce9d56.png)

然后我们在Terminal里面输入下面的代码安装browserify。

    npm install -g browserify
    

![](https://storage.googleapis.com/papyrus_images/095f570c7d66156c1ade736e2173a9673e7aadd4275b6637321ea8ba65db1d76.png)

![](https://storage.googleapis.com/papyrus_images/6a794ef23cf930a34153e66ebc2d6a16b24706fb077f52df8be2684e0bc54f04.png)

然后在Termianl里面分别输入(不要一起输入)下面的每一行代码，分别安装 qs，bignumber，web3

    npm i qs
    npm i bignumber
    npm i web3
    

![](https://storage.googleapis.com/papyrus_images/a8de84fbcac93a219846b161ed7014594bfb601397c217110a050ef03a449cbf.png)

找到index.html文件夹，如图所示的地方，添加“Select A Token” 文字。

![](https://storage.googleapis.com/papyrus_images/ab9f45fb4519e18b09aaffc32d62ea1f1032193d0132184e47a15f140461fd17.png)

找到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;
    

![](https://storage.googleapis.com/papyrus_images/7ba91e4391006963d91714af08b18b30cc45bfeb1bed486b8d223c7fc8e91d21.png)

![](https://storage.googleapis.com/papyrus_images/e138589d85ac085fdb4548dcf662078dfaa0cf45c5b880060ee885dc284b5cd1.png)

然后我们在Terminal里面输入下面的代码，之后我们会看到创建了一个新的 bundle.js文件。

    browserify index.js --standalone bundle -o bundle.js
    

![](https://storage.googleapis.com/papyrus_images/29429c7d89c34c1ecc53c01a2df4b91282c1a8ba7f25f97824169fc9612d9cb5.png)

然后我们鼠标放到index.html页面，右键点击“Open with Live Server”，过一会就会进入到我们做的swap页面，选择Token的时候应该只有bnb和busd两个token选择，则说明代码没问题了。

![](https://storage.googleapis.com/papyrus_images/00d2f5671a6bd21fc8c4ff7cb9d0f0eaf10fbf3aaeb15c3c299ff912fe9703c5.png)

然后我们把Metamask切换到币安链，然后点击Sign in with MetaMask登录。选择用BNB交换BUSD，这里需要在你的钱包里有一些币安币，用的是正式网，不是测试网。

![](https://storage.googleapis.com/papyrus_images/47a016b23934325803890203cb0debeebc264561ade208ed2e7a8a23e4acb80b.jpg)

然后我们点击swap，会出来一个授权，让我们授权程序可以交换BNB，我们点击确定，等待主网确定。等确定完，会自动在Metamask弹出交易窗口，然后我们点击确定，就可以成功把0.001bnb交换成Busd了。

![](https://storage.googleapis.com/papyrus_images/8b93fbfb4161b49480faf66a8121ac5a830022c9e6ee558807669720ae039fa6.png)

![](https://storage.googleapis.com/papyrus_images/c8c0aac4831500e7181221132deca0d40a0091ca5548721ecc21e599d871c8c4.png)

然后我们在Metamask里面找到这个交易记录，把交易记录的地址复制出来，到时候填到提交表单里。

交易成功以后，为了安全，我们可以去（[https://bscscan.com/tokenapprovalchecker），找到我们刚才授权的应用，取消授权。](https://bscscan.com/tokenapprovalchecker%EF%BC%89%EF%BC%8C%E6%89%BE%E5%88%B0%E6%88%91%E4%BB%AC%E5%88%9A%E6%89%8D%E6%8E%88%E6%9D%83%E7%9A%84%E5%BA%94%E7%94%A8%EF%BC%8C%E5%8F%96%E6%B6%88%E6%8E%88%E6%9D%83%E3%80%82)

![](https://storage.googleapis.com/papyrus_images/f2337ba09b3bb9e6b7628120b8fece729fb5eea242cd23da72794453d4237aa5.png)

![](https://storage.googleapis.com/papyrus_images/7b4aac3712f229b483533b0cfd493fce7cfe569410ea788d23d0ad85d1fdd1f9.png)

然后我们在 swap-demo-tutorial-part-9 文件夹下面，新建一个 .gitignore的文件，添加内容 node\_modules，这样我们就不用上传无用的node\_modules文件夹了。

![](https://storage.googleapis.com/papyrus_images/1180ef5bd15dc7c05ce03ea76b7cf065682a3db96d4fcb48ee6c12b565a78796.png)

然后我们在Source Control 里填写备注信息，点击Commit按钮右边的小三角，点击 Commit&Push 把代码提交到我们的Github。

提交表单
----

链接：[https://alchemyapi.typeform.com/roadtoweek9](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）。](https://github.com/%E4%BD%A0%E7%9A%84Github%E5%90%8D%E5%AD%97/swap-demo-tutorial/tree/main/swap-demo-tutorial-part-9%EF%BC%89%E5%92%8C%E4%BD%A0%E5%9C%A8%E5%89%8D%E9%9D%A2%E5%A4%8D%E5%88%B6%E7%9A%84%E4%BA%A4%E6%98%93%E8%AE%B0%E5%BD%95%EF%BC%88https://bscscan.com/tx/%E4%BD%A0%E7%9A%84%E4%BA%A4%E6%98%93%E8%AE%B0%E5%BD%95tx%EF%BC%89%E3%80%82)

---

*Originally published on [SoullessL](https://paragraph.com/@nftneverdie/road-to-web3-swap-dapp)*
