Skip to content

Instantly share code, notes, and snippets.

@mgild
Created November 28, 2023 01:11
Show Gist options
  • Save mgild/7d78771cb0d81979154da6c0adc010e0 to your computer and use it in GitHub Desktop.
Save mgild/7d78771cb0d81979154da6c0adc010e0 to your computer and use it in GitHub Desktop.
use tokio;
use balancer_sdk;
use balancer_sdk::Web3;
use web3;
use ethcontract;
use balancer_sdk::helpers::build_web3;
use balancer_sdk::helpers::*;
use balancer_sdk::*;
use balancer_sdk::{Address, U256};
use balancer_sdk::helpers::*;
use std::str::FromStr;
use web3::signing::Key;
use balancer_sdk::PoolId;
use switchboard_utils;
use switchboard_utils::Decimal;
use futures::future::join_all;
use std::future::Future;
use web3::futures::TryFutureExt;
use web3::futures::FutureExt;
use switchboard_utils::FromPrimitive;
use switchboard_utils::SbError;
use std::pin::Pin;
use std::cmp::Ordering;
pub const SFRX_ETH: &str = "0xac3e018457b222d93114458476f3e3416abbe38f";
pub const SFRXETH_DECIMALS: u32 = 18;
pub const WST_ETH: &str = "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0";
pub const WSTETH_DECIMALS: u32 = 18;
pub const WETH: &str = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2";
pub const WETH_DECIMALS: u32 = 18;
pub const USDC: &str = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
pub const USDC_DECIMALS: u32 = 6;
fn u256_to_f64(value: U256) -> f64 {
// This function assumes that the value can fit within an f64
// It's a simple way to convert, and more sophisticated methods may be needed for larger values
value.as_u128() as f64
}
fn get_percentage_of_total(part: U256, total: U256) -> f64 {
if total.is_zero() {
panic!("Total must not be zero");
}
// Convert both U256 values to f64
let part = u256_to_f64(part);
let total = u256_to_f64(total);
// Calculate the percentage
(part / total) * 100.0
}
pub async fn fetch_balancer_v2_quote(rpc_url: &str, pool: PoolId, in_token: &str, out_token: &str, amount: U256, slippage: f64) -> Result<U256, u8> {
let private_key = PrivateKey::from_str("00e0000a00aaaa0e0a000e0e0000e00e000a000000000000000aaa00a0aaaaaa").unwrap();
let w3 = build_web3(rpc_url);
let vault_instance = balancer_sdk::vault::Vault::new(w3);
let in_token = addr!(in_token);
let out_token = addr!(out_token);
let pool_info = vault_instance.get_pool_tokens(pool.into()).call().await.unwrap();
// println!("{:#?}", pool_info);
let out_idx = pool_info.0.iter().position(|&x| x == out_token).unwrap();
let in_idx = pool_info.0.iter().position(|&x| x == in_token).unwrap();
let in_pool_total = pool_info.1[in_idx];
let out_pool_total = pool_info.1[out_idx];
let swap_step = BatchSwapStep {
pool_id: pool,
asset_in_index: in_idx,
asset_out_index: out_idx,
amount,
user_data: UserData("0x").into(),
};
let funds = FundManagement {
sender: private_key.public_address(),
from_internal_balance: false,
recipient: private_key.public_address(),
to_internal_balance: false,
};
let deltas = vault_instance
.query_batch_swap(
SwapKind::GivenIn as u8,
vec![swap_step.into()],
pool_info.0,
funds.into(),
)
.from(Account::Offline(private_key, None))
.call()
.await
.unwrap();
// println!("{}", deltas[out_idx].abs());
let in_percent = get_percentage_of_total(amount, in_pool_total);
let out_amount = deltas[out_idx].abs().try_into().unwrap();
let out_percent = get_percentage_of_total(out_amount, out_pool_total);
if in_percent >= slippage {
println!("IN CHANGE: {}%", in_percent);
return Err(0);
}
if out_percent >= slippage {
println!("OUT CHANGE: {}%", out_percent);
return Err(0);
}
Ok(out_amount)
}
fn median(mut numbers: Vec<Decimal>) -> Option<Decimal> {
if numbers.is_empty() {
return None; // Cannot find the median of an empty list
}
// Sort the numbers using `partial_cmp` because `Decimal` doesn't implement `Ord`
numbers.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
let mid = numbers.len() / 2; // Find the middle index
Some(if numbers.len() % 2 == 0 {
// If even number of elements, average the middle two
(numbers[mid - 1] + numbers[mid]) / Decimal::from(2)
} else {
// If odd, return the middle element
numbers[mid]
})
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let SFRXETH_WSTETH_POOL = pool_id!("0x42ed016f826165c2e5976fe5bc3df540c5ad0af700000000000000000000058b");
let WSTETH_WETH_POOL = pool_id!("0x93d199263632a4ef4bb438f1feb99e57b4b5f0bd0000000000000000000005c2");
// let WETH_USDC_POOL_UNSAFE = pool_id!("0x96646936b91d6b9d7d0c47c496afbf3d6ec7b6f8000200000000000000000019");
// https://ethereumnodes.com/
let rpc_url = "https://rpc.flashbots.net/";
let pool_id = SFRXETH_WSTETH_POOL;
let wsteth_amount = fetch_balancer_v2_quote(rpc_url, SFRXETH_WSTETH_POOL, SFRX_ETH, WST_ETH, u256!(10u128.pow(SFRXETH_DECIMALS).to_string()), 0.1).await.unwrap();
let weth_amount = fetch_balancer_v2_quote(rpc_url, WSTETH_WETH_POOL, WST_ETH, WETH, wsteth_amount, 0.1).await.unwrap();
// let usdc_amount = fetch_balancer_v2_quote(rpc_url, WETH_USDC_POOL_UNSAFE, WETH, USDC, weth_amount, 0.1).await?;
let v: Vec<Pin<Box<dyn Future<Output = Result<Decimal, SbError>>>>> = vec![
Box::pin(switchboard_utils::exchanges::BitfinexApi::fetch_ticker("tETHUSD", None).map_ok(|x| x.last_price)),
Box::pin(switchboard_utils::exchanges::CoinbaseApi::fetch_ticker("ETH-USD", None).map_ok(|x| x.price)),
Box::pin(switchboard_utils::exchanges::HuobiApi::fetch_ticker("ethusdt", None).map_ok(|x| Decimal::from_f64(x.close).unwrap())),
Box::pin(switchboard_utils::exchanges::KrakenApi::fetch_ticker("ETHUSD", None).map_ok(|x| x.close[0]))
];
let eth_prices: Vec<Decimal> = join_all(v).await.into_iter().filter_map(|x| x.ok()).collect();
let eth_price = median(eth_prices).unwrap();
let weth_amount = Decimal::from_i128_with_scale(weth_amount.as_u128().try_into().unwrap(), WETH_DECIMALS);
println!("{}", weth_amount * eth_price);
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment