原文地址 :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,web3
npm 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)。