Skip to content
Draft
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
2 changes: 2 additions & 0 deletions frontend/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
mod app;
mod config;
mod components;
mod token;
mod pool;
mod metamask;
mod vanillaswap;
mod wrapper;
Expand Down
111 changes: 109 additions & 2 deletions frontend/src/metamask.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ const uniswapV2RouterAbi = [
"function factory() view returns (address)",
"function swapExactTokensForTokensSupportingFeeOnTransferTokens(uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline) returns (uint256[])",
"function swapExactTokensForETHSupportingFeeOnTransferTokens(uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline)",
"function swapExactETHForTokensSupportingFeeOnTransferTokens(uint256 amountOutMin, address[] calldata path, address to, uint256 deadline) payable"
"function swapExactETHForTokensSupportingFeeOnTransferTokens(uint256 amountOutMin, address[] calldata path, address to, uint256 deadline) payable",
"function addLiquidity(address tokenA, address tokenB, uint amountADesired, uint amountBDesired, uint amountAMin, uint amountBMin, address to, uint deadline) external returns (uint amountA, uint amountB, uint liquidity)",
"function addLiquidityETH(address token, uint amountTokenDesired, uint amountTokenMin, uint amountETHMin, address to, uint deadline) payable returns (uint amountToken, uint amountETH, uint liquidity)",
"function removeLiquidity(address tokenA, address tokenB, uint liquidity, uint amountAMin, uint amountBMin, address to, uint deadline) external returns (uint amountA, uint amountB)",
"function removeLiquidityETH(address token, uint liquidity, uint amountTokenMin, uint amountETHMin, address to, uint deadline) external returns (uint amountToken, uint amountETH)"
];
const uniswapV2FactoryAbi = [
"function allPairsLength() view returns (uint256)",
Expand Down Expand Up @@ -133,6 +137,40 @@ export async function js_get_token_balance(user, token, isNative) {
}
}

export async function js_get_tokens_balances(user, tokens) {
try {
if (!window.ethereum) throw new Error("MetaMask not installed");
await window.ethereum.request({ method: 'eth_requestAccounts' });
let provider = new ethers.BrowserProvider(window.ethereum);

const calls = tokens.map(async token => {
const c = new ethers.Contract(token, erc20Abi, provider);
const bal = await c.balanceOf(user);
return [token, bal.toString()];
});
const results = await Promise.all(calls);
const bal = await provider.getBalance(user);
// convert to object mapping
const map = {};
map['native'] = bal;
for (const [addr, value] of results) {
map[addr] = value;
}

return {
ok: true,
value: safeStringify(map)
};
} catch (err) {
console.error(err);
return {
ok: false,
value: err.reason || err.message || "Unknown error"
};
}
}


// FACTORY
export async function js_get_all_wrappers(factoryAddress) {
try {
Expand Down Expand Up @@ -269,7 +307,7 @@ export async function js_unwrap_tokens(contractAddress, cAsset, amount, dToken)
}


//UniSwap
//UniSwap V2
export async function js_get_uniswap_v2_pairs(routerAddr) {
try {
if (!window.ethereum) throw new Error("MetaMask not installed");
Expand Down Expand Up @@ -414,6 +452,75 @@ export async function js_uniswap_v2_swap_tokens(tokenIn, tokenOut, amountIn, amo
}
}

export async function js_uniswap_v2_add_liquidity(tokenA, tokenB, amountA, amountB, routerAddress, isNativeA, isNativeB) {
try {
if (!window.ethereum) throw new Error("MetaMask not installed");
await window.ethereum.request({ method: 'eth_requestAccounts' });
let provider = new ethers.BrowserProvider(window.ethereum);
let signer = await provider.getSigner();

const router = new ethers.Contract(routerAddress, uniswapV2RouterAbi, signer);

if (!isNativeA){
const tokenAContract = new ethers.Contract(tokenA, erc20Abi, signer);
// Approve
const allowance = await tokenAContract.allowance(signer, routerAddress);
if (allowance < amountA){
const approveATx = await tokenAContract.approve(routerAddress, amountA);
await approveATx.wait();
}
}
if (!isNativeB){
const tokenBContract = new ethers.Contract(tokenB, erc20Abi, signer);
// Approve
const allowance = await tokenBContract.allowance(signer, routerAddress);
if (allowance < amountB){
const approveBTx = await tokenBContract.approve(routerAddress, amountB);
await approveBTx.wait();
}
}

const deadline = Math.floor(Date.now() / 1000) + 60 * 10;

let tx;
if (isNativeA || isNativeB){
const tokenAddress = isNativeA ? tokenB : tokenA;
const tokenAmount = isNativeA ? amountB : amountA;
const nativeAmount = isNativeA ? amountA : amountB;
tx = await router.addLiquidityETH(
tokenAddress,
tokenAmount,
0,
await signer.getAddress(), // TODO: use just signer
deadline,
{
value: nativeAmount
}
);
}else{
tx = await router.addLiquidity(
tokenA,
tokenB,
amountA,
amountB,
0,
0,
await signer.getAddress(),
deadline,
);
}
const receipt = await tx.wait();

return {
ok: true,
value: JSON.stringify(`${receipt.hash}`)
};
} catch (err) {
console.error(err);
return { ok: false, value: err?.reason || err?.message || String(err) };
}
}


/// UniSwap V3
export async function js_get_uniswap_v3_pool_states(poolAddrs) {
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/metamask.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::error::Error;
use std::collections::BTreeMap;
use wasm_bindgen::prelude::*;
use serde::Deserialize;
use serde::de::DeserializeOwned;
Expand All @@ -16,6 +17,7 @@ extern "C" {
pub fn js_on_chain_changed(callback: &Closure<dyn FnMut(u32)>);
pub fn js_on_accounts_changed(callback: &Closure<dyn FnMut(Vec<JsValue>)>);
async fn js_get_token_balance(user: &str, token: &str, is_native: bool) -> JsValue;
async fn js_get_tokens_balances(user: &str, token: Vec<JsValue>) -> JsValue;
}

pub fn js_parse<T: DeserializeOwned>(js: JsValue) -> Result<T, String> {
Expand Down Expand Up @@ -56,3 +58,11 @@ pub async fn connect_metamask() -> Result<MetamaskInfo, Box<dyn Error>>{
pub async fn get_token_balance(user: &str, token: &str, is_native: bool) -> Result<String,Box<dyn Error>>{
js_try!(js_get_token_balance(user, token, is_native) => String)
}

pub async fn get_tokens_balances(user: &str, tokens: Vec<&str>) -> Result<BTreeMap<String,String>,Box<dyn Error>>{
let js_array: Vec<JsValue> = tokens.iter()
.map(|s| JsValue::from_str(s))
.collect();

js_try!(js_get_tokens_balances(user, js_array) => BTreeMap<String,String>)
}
17 changes: 15 additions & 2 deletions frontend/src/metamask/uniswap_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ extern "C" {
// uniswap v2
async fn js_get_uniswap_v2_pairs(router_address: &str) -> JsValue;
async fn js_uniswap_v2_swap_tokens(token_in: &str, token_out: &str, amount_in: &str,
amount_out_min: &str, router_address: &str, is_native_in: bool, is_native_out: bool) -> JsValue;
amount_out_min: &str, router_address: &str, is_native_in: bool, is_native_out: bool) -> JsValue;
async fn js_uniswap_v2_add_liquidity(token_a: &str, token_b: &str, amount_a: &str, amount_b: &str, router_address: &str, is_native_a: bool, is_native_b: bool) -> JsValue;
}


// UniSwap
#[derive(Deserialize, Clone, Debug)]
#[derive(Deserialize, Clone, Debug, PartialEq)]
pub struct V2PairInfo {
pub token0: String,
pub token1: String,
Expand Down Expand Up @@ -48,3 +49,15 @@ pub async fn uniswap_v2_swap_tokens(
) -> Result<String, String> {
js_try!(js_uniswap_v2_swap_tokens(token_in,token_out,amount_in,amount_out_min,router_address, is_native_in, is_native_out) => String)
}

pub async fn uniswap_v2_add_liquidity(
token_a: &str,
token_b: &str,
amount_a: &str,
amount_b: &str,
router_address: &str,
is_native_a: bool,
is_native_b: bool,
)-> Result<String, String> {
js_try!(js_uniswap_v2_add_liquidity(token_a,token_b,amount_a,amount_b,router_address, is_native_a, is_native_b) => String)
}
43 changes: 43 additions & 0 deletions frontend/src/pool.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use std::collections::BTreeMap;
use dioxus::prelude::Signal;
use crate::metamask::uniswap_v2::{V2PairInfo as PoolInfo};
use crate::vanillaswap::v2::is_zero_or_empty;

pub mod liquidity;

pub fn use_filtered_pairs(
wrapped_native: String,
pairs: Signal<Vec<PoolInfo>>,
balances: Signal<BTreeMap<String, String>>,
show_zero_liq: Signal<bool>,
show_balanced: Signal<bool>,
) -> Vec<PoolInfo> {
let balances = balances();
pairs()
.iter()
.filter(|p| {
// Zero liquidity filter
(show_zero_liq()
|| (!is_zero_or_empty(p.reserve0.as_deref())
&& !is_zero_or_empty(p.reserve1.as_deref())))
&&
// Balanced filter
(!show_balanced()
|| !is_zero_or_empty(balances.get(&p.pair_address).map(String::as_str))
|| (
(!is_zero_or_empty(balances.get(&p.token0).map(String::as_str))
|| (p.token0.to_lowercase() == wrapped_native
&& !is_zero_or_empty(balances.get("native").map(String::as_str))))
&&
(!is_zero_or_empty(balances.get(&p.token1).map(String::as_str))
|| (p.token1.to_lowercase() == wrapped_native
&& !is_zero_or_empty(balances.get("native").map(String::as_str))))
))
})
.cloned()
.collect::<Vec<PoolInfo>>()
}

pub fn get_pool(address_a: &String, address_b: &String, pools: &Vec<PoolInfo>) -> Option<PoolInfo>{
pools.iter().find(|p| (p.token0 == *address_a && p.token1 == *address_b) || (p.token1 == *address_a && p.token0 == *address_b)).cloned()
}
Loading