Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

a standalone example that doesn't require Node.js #8

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions swap-demo-tutorial-part-tyler/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

75 changes: 75 additions & 0 deletions swap-demo-tutorial-part-tyler/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<title>Swap Demo Tutorial</title>
<script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>
<script src='https://cdn.jsdelivr.net/npm/[email protected]/bignumber.min.js'></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://unpkg.com/moralis/dist/moralis.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
crossorigin="anonymous"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link rel="stylesheet" href="./style.css">
</head>

<body>
<nav class="navbar navbar-expand-lg">
<a class="navbar-brand" href="#">My DEX Aggregator</a>
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<button id="login_button" class="btn btn-outline-primary my-2 my-sm-0" type="submit">Sign in with MetaMask</button>
</li>
</nav>
<div class="container">
<div class="row">
<div class="col col-md-6 offset-md-3" id="window">
<h4>Swap</h4>
<div id="form">
<div class="swapbox">
<div class="swapbox_select token_select" id="from_token_select">
<img class="token_img" id="from_token_img">
<span id="from_token_text"></span>
</div>
<div class="swapbox_select">
<input class="number form-control" placeholder="amount" id="from_amount">
</div>
</div>
<div class="swapbox">
<div class="swapbox_select token_select" id="to_token_select">
<img class="token_img" id="to_token_img">
<span id="to_token_text"></span>
</div>
<div class="swapbox_select">
<input class="number form-control" placeholder="amount" id="to_amount">
</div>
</div>
<div class="gas_estimate_label">Estimated Gas: <span id="gas_estimate"></span></div>
<button disabled class="btn btn-large btn-primary btn-block" id="swap_button">Swap</button>
</div>
</div>
</div>
</div>
<div class="modal" id="token_modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Select a Token</h5>
<button id="modal_close" type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div id="token_list"></div>
</div>
</div>
</div>
</div>
<script src="./index.js" type="text/javascript"></script>
</body>

</html>
193 changes: 193 additions & 0 deletions swap-demo-tutorial-part-tyler/index.js
Original file line number Diff line number Diff line change
@@ -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 = `
<span class="token_list_text">${tokens[i].symbol}</span>
`;
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;
56 changes: 56 additions & 0 deletions swap-demo-tutorial-part-tyler/style.css
Original file line number Diff line number Diff line change
@@ -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;
}