use std::{collections::HashSet, env, str::FromStr, sync::Arc, time::Duration};
use alloy::{
eips::BlockNumberOrTag,
network::{Ethereum, EthereumWallet},
primitives::{Address, Bytes as AlloyBytes, Keccak256, Signature, TxKind, B256, U256},
providers::{
fillers::{FillProvider, JoinFill, WalletFiller},
Identity, Provider, ProviderBuilder, RootProvider,
},
rpc::types::{
simulate::{SimBlock, SimulatePayload},
TransactionInput, TransactionRequest,
},
signers::{local::PrivateKeySigner, SignerSync},
sol_types::{eip712_domain, SolStruct, SolValue},
};
use alloy_chains::NamedChain;
use clap::Parser;
use dialoguer::{theme::ColorfulTheme, Select};
use dotenv::dotenv;
use num_bigint::BigUint;
use num_traits::ToPrimitive;
use tokio::sync::mpsc;
use tracing_subscriber::EnvFilter;
use tycho_common::{models::token::Token, simulation::protocol_sim::ProtocolSim, Bytes};
use tycho_execution::encoding::{
errors::EncodingError,
evm::{
approvals::permit2::{Permit2, PermitSingle},
encoder_builders::TychoRouterEncoderBuilder,
swap_encoder::swap_encoder_registry::SwapEncoderRegistry,
utils::biguint_to_u256,
},
models,
models::{EncodedSolution, Solution, Swap, UserTransferType},
};
use tycho_simulation::{
protocol::models::{ProtocolComponent, Update},
rfq::{
protocols::{
bebop::{client_builder::BebopClientBuilder, state::BebopState},
hashflow::{client_builder::HashflowClientBuilder, state::HashflowState},
liquorice::{client_builder::LiquoriceClientBuilder, state::LiquoriceState},
},
stream::RFQStreamBuilder,
},
tycho_common::models::Chain,
utils::{get_default_url, load_all_tokens},
};
#[derive(Clone, Debug)]
pub struct Transaction {
pub to: Bytes,
pub value: BigUint,
pub data: Vec<u8>,
}
#[derive(Parser)]
struct Cli {
#[arg(long)]
sell_token: Option<String>,
#[arg(long)]
buy_token: Option<String>,
#[arg(long, default_value_t = 10.0)]
sell_amount: f64,
#[arg(long, default_value_t = 1000.0)]
tvl_threshold: f64,
#[arg(long, default_value = "ethereum")]
chain: Chain,
}
impl Cli {
fn with_defaults(mut self) -> Self {
if self.buy_token.is_none() {
self.buy_token = Some(match self.chain {
Chain::Ethereum => "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(),
Chain::Base => "0x4200000000000000000000000000000000000006".to_string(),
_ => {
panic!("RFQ quickstart does not yet support chain {chain}", chain = self.chain)
}
});
}
if self.sell_token.is_none() {
self.sell_token = Some(match self.chain {
Chain::Ethereum => "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(),
Chain::Base => "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913".to_string(),
_ => {
panic!("RFQ quickstart does not yet support chain {chain}", chain = self.chain)
}
});
}
self
}
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.with_target(false)
.init();
let cli = Cli::parse().with_defaults();
let chain = cli.chain;
dotenv().expect("Missing .env file");
let tycho_url = env::var("TYCHO_URL").unwrap_or_else(|_| {
get_default_url(&chain)
.unwrap_or_else(|| panic!("Unknown URL for chain {chain}", chain = cli.chain))
});
let tycho_api_key: String =
env::var("TYCHO_API_KEY").unwrap_or_else(|_| "sampletoken".to_string());
let (bebop_user, bebop_key) = (env::var("BEBOP_USER").ok(), env::var("BEBOP_KEY").ok());
let (hashflow_user, hashflow_key) =
(env::var("HASHFLOW_USER").ok(), env::var("HASHFLOW_KEY").ok());
let (liquorice_user, liquorice_key) =
(env::var("LIQUORICE_USER").ok(), env::var("LIQUORICE_KEY").ok());
if (bebop_user.is_none() || bebop_key.is_none()) &&
(hashflow_user.is_none() || hashflow_key.is_none()) &&
(liquorice_user.is_none() || liquorice_key.is_none())
{
panic!("No RFQ credentials found. Please set BEBOP_USER and BEBOP_KEY, HASHFLOW_USER and HASHFLOW_KEY, or LIQUORICE_USER and LIQUORICE_KEY environment variables.");
}
println!("Loading tokens from Tycho... {url}", url = tycho_url.as_str());
let all_tokens = load_all_tokens(
tycho_url.as_str(),
false,
Some(tycho_api_key.as_str()),
true,
chain,
None,
None,
)
.await
.expect("Failed to load tokens");
println!("Tokens loaded: {num}", num = all_tokens.len());
let sell_token_address = Bytes::from_str(
&cli.sell_token
.expect("Sell token not provided"),
)
.expect("Invalid address for sell token");
let buy_token_address = Bytes::from_str(
&cli.buy_token
.expect("Buy token not provided"),
)
.expect("Invalid address for buy token");
let sell_token = all_tokens
.get(&sell_token_address)
.expect("Sell token not found")
.clone();
let buy_token = all_tokens
.get(&buy_token_address)
.expect("Buy token not found")
.clone();
let amount_in =
BigUint::from((cli.sell_amount * 10f64.powi(sell_token.decimals as i32)) as u128);
println!(
"Looking for RFQ quotes for {amount} {sell_symbol} -> {buy_symbol} on {chain:?}",
amount = cli.sell_amount,
sell_symbol = sell_token.symbol,
buy_symbol = buy_token.symbol
);
let swapper_pk = env::var("PRIVATE_KEY").ok();
let swap_encoder_registry = SwapEncoderRegistry::new(chain)
.add_default_encoders(None)
.expect("Failed to get default SwapEncoderRegistry");
let encoder = TychoRouterEncoderBuilder::new()
.chain(chain)
.swap_encoder_registry(swap_encoder_registry)
.build()
.expect("Failed to build encoder");
let mut rfq_tokens = HashSet::new();
rfq_tokens.insert(sell_token_address.clone());
rfq_tokens.insert(buy_token_address.clone());
let mut rfq_stream_builder = RFQStreamBuilder::new()
.set_tokens(all_tokens.clone())
.await;
if let (Some(user), Some(key)) = (bebop_user, bebop_key) {
println!("Setting up Bebop RFQ client...\n");
let bebop_client = BebopClientBuilder::new(chain, user, key)
.tokens(rfq_tokens.clone())
.tvl_threshold(cli.tvl_threshold)
.build()
.expect("Failed to create Bebop RFQ client");
rfq_stream_builder =
rfq_stream_builder.add_client::<BebopState>("bebop", Box::new(bebop_client))
}
if let (Some(user), Some(key)) = (hashflow_user, hashflow_key) {
println!("Setting up Hashflow RFQ client...\n");
let hashflow_client = HashflowClientBuilder::new(chain, user, key)
.tokens(rfq_tokens.clone())
.tvl_threshold(cli.tvl_threshold)
.poll_time(Duration::from_secs(5))
.build()
.expect("Failed to create Hashflow RFQ client");
rfq_stream_builder =
rfq_stream_builder.add_client::<HashflowState>("hashflow", Box::new(hashflow_client))
}
if let (Some(user), Some(key)) = (liquorice_user, liquorice_key) {
println!("Setting up Liquorice RFQ client...\n");
let liquorice_client = LiquoriceClientBuilder::new(chain, user, key)
.tokens(rfq_tokens.clone())
.tvl_threshold(cli.tvl_threshold)
.build()
.expect("Failed to create Liquorice RFQ client");
rfq_stream_builder =
rfq_stream_builder.add_client::<LiquoriceState>("liquorice", Box::new(liquorice_client))
}
let (tx, mut rx) = mpsc::channel::<Update>(100);
tokio::spawn(rfq_stream_builder.build(tx));
println!("Connected to RFQs! Streaming live price levels...\n");
while let Some(update) = rx.recv().await {
let mut latest_update = update;
let mut drained_count = 0;
while let Ok(newer_update) =
tokio::time::timeout(std::time::Duration::from_millis(10), rx.recv()).await
{
if let Some(newer_update) = newer_update {
latest_update = newer_update;
drained_count += 1;
} else {
break;
}
}
if drained_count > 0 {
println!(
"Fast-forwarded through {drained_count} older RFQ updates to get latest prices"
);
}
let update = latest_update;
println!(
"Received RFQ price levels with {} new pairs for block/timestamp {}",
update.states.len(),
update.block_number_or_timestamp
);
for (comp_id, state) in &update.states {
if let Some(component) = update.new_pairs.get(comp_id) {
let tokens = &component.tokens;
if HashSet::from([&sell_token, &buy_token])
.is_subset(&HashSet::from_iter(tokens.iter()))
{
if let Ok(amount_out_result) =
state.get_amount_out(amount_in.clone(), &sell_token, &buy_token)
{
let amount_out = amount_out_result.amount;
println!(
"Best indicative price for swap {}: {} {} -> {} {}",
component.protocol_system,
format_token_amount(&amount_in, &sell_token),
sell_token.symbol,
format_token_amount(&amount_out, &buy_token),
buy_token.symbol
);
let expected_amount_copy = amount_out.clone();
if swapper_pk.is_none() {
println!(
"\nSigner private key was not provided. Skipping simulation/execution. Set PRIVATE_KEY env variable to perform simulation/execution.\n"
);
continue;
}
let pk_str = swapper_pk.as_ref().unwrap();
let pk =
B256::from_str(pk_str).expect("Failed to convert swapper pk to B256");
let signer = PrivateKeySigner::from_bytes(&pk)
.expect("Failed to create PrivateKeySigner");
let tx_signer = EthereumWallet::from(signer.clone());
let provider = ProviderBuilder::default()
.with_chain(NamedChain::try_from(chain.id()).expect("Invalid chain"))
.wallet(tx_signer)
.connect(&env::var("RPC_URL").expect("RPC_URL env var not set"))
.await
.expect("Failed to connect provider");
match get_token_balance(
&provider,
Address::from_slice(&sell_token.address),
signer.address(),
Address::from_slice(&chain.native_token().address),
)
.await
{
Ok(balance) => {
let formatted_balance = format_token_amount(&balance, &sell_token);
println!(
"\nYour balance: {formatted_balance} {sell_symbol}",
sell_symbol = sell_token.symbol
);
if balance < amount_in {
let required = format_token_amount(&amount_in, &sell_token);
println!("⚠️ Warning: Insufficient balance for swap. You have {formatted_balance} {sell_symbol} but need {required} {sell_symbol}",
formatted_balance = formatted_balance,
sell_symbol = sell_token.symbol,
);
continue;
}
}
Err(e) => eprintln!("Failed to get token balance: {e}"),
}
match get_token_balance(
&provider,
Address::from_slice(&buy_token.address),
signer.address(),
Address::from_slice(&chain.native_token().address),
)
.await
{
Ok(balance) => {
let formatted_balance = format_token_amount(&balance, &buy_token);
println!(
"Your {buy_symbol} balance: {formatted_balance} {buy_symbol}",
buy_symbol = buy_token.symbol
);
}
Err(e) => eprintln!(
"Failed to get {buy_symbol} balance: {e}",
buy_symbol = buy_token.symbol
),
}
println!("Would you like to simulate or execute this swap?");
println!("Please be aware that the market might move while you make your decision, which might lead to a revert if you've set a min amount out or slippage.");
println!(
"Warning: slippage is set to 0.25% during execution by default.\n"
);
let options =
vec!["Simulate the swap", "Execute the swap", "Skip this swap"];
let selection = Select::with_theme(&ColorfulTheme::default())
.with_prompt("What would you like to do?")
.default(0)
.items(&options)
.interact()
.unwrap_or(2);
let choice = match selection {
0 => "simulate",
1 => "execute",
_ => "skip",
};
match choice {
"simulate" => {
println!("\nSimulating RFQ swap...");
println!("Step 1: Encoding the permit2 transaction...");
let approve_function_signature = "approve(address,uint256)";
let args = (
Address::from_str("0x000000000022D473030F116dDEE9F6B43aC78BA3")
.expect("Couldn't convert to address"),
biguint_to_u256(&amount_in),
);
let approval_data =
encode_input(approve_function_signature, args.abi_encode());
let nonce = provider
.get_transaction_count(signer.address())
.await
.expect("Failed to get nonce");
let block = provider
.get_block_by_number(BlockNumberOrTag::Latest)
.await
.expect("Failed to fetch latest block")
.expect("Block not found");
let base_fee = block
.header
.base_fee_per_gas
.expect("Base fee not available");
let max_priority_fee_per_gas = 1_000_000_000u64;
let max_fee_per_gas = base_fee + max_priority_fee_per_gas;
let approval_request = TransactionRequest {
to: Some(TxKind::Call(Address::from_slice(
&sell_token_address,
))),
from: Some(signer.address()),
value: None,
input: TransactionInput {
input: Some(AlloyBytes::from(approval_data)),
data: None,
},
gas: Some(100_000u64),
chain_id: Some(chain.id()),
max_fee_per_gas: Some(max_fee_per_gas.into()),
max_priority_fee_per_gas: Some(max_priority_fee_per_gas.into()),
nonce: Some(nonce),
..Default::default()
};
println!("Step 2: Encoding the solution transaction...");
let solution = create_solution(
component.clone(),
Arc::from(state.clone_box()),
sell_token.clone(),
buy_token.clone(),
amount_in.clone(),
Bytes::from(signer.address().to_vec()),
amount_out.clone(),
amount_out_result.gas,
);
let encoded_solution = encoder
.encode_solutions(vec![solution.clone()])
.expect("Failed to encode router calldata")[0]
.clone();
let tx = encode_tycho_router_call(
chain.id(),
encoded_solution.clone(),
&solution,
chain.native_token().address,
signer.clone(),
)
.expect("Failed to encode router call");
let swap_request = TransactionRequest {
to: Some(TxKind::Call(Address::from_slice(&tx.to))),
from: Some(signer.address()),
value: Some(biguint_to_u256(&tx.value)),
input: TransactionInput {
input: Some(AlloyBytes::from(tx.data)),
data: None,
},
gas: Some(1_000_000u64),
chain_id: Some(chain.id()),
max_fee_per_gas: Some(max_fee_per_gas.into()),
max_priority_fee_per_gas: Some(max_priority_fee_per_gas.into()),
nonce: Some(nonce + 1),
..Default::default()
};
println!("Step 3: Simulating approval and solution transactions together...");
let approval_payload = SimulatePayload {
block_state_calls: vec![SimBlock {
block_overrides: None,
state_overrides: None,
calls: vec![approval_request.clone(), swap_request],
}],
trace_transfers: true,
validation: true,
return_full_transactions: true,
};
match provider
.simulate(&approval_payload)
.await
{
Ok(output) => {
let mut all_successful = true;
for block in output.iter() {
println!(
"\nSimulated Block {block_num}:",
block_num = block.inner.header.number
);
for transaction in block.calls.iter() {
println!(
" RFQ Swap: Status: {status:?}, Gas Used: {gas_used}",
status = transaction.status,
gas_used = transaction.gas_used
);
if !transaction.status {
all_successful = false;
}
}
}
if all_successful {
println!("\n✅ Simulation successful!");
} else {
println!("\n❌ Simulation failed! One or more transactions reverted.");
println!("Consider adjusting parameters and re-simulating before execution.");
}
println!();
continue;
}
Err(e) => {
eprintln!("\n❌ Simulation failed: {e:?}");
println!("Your RPC provider does not support transaction simulation. Consider proceeding with execution instead or switching RPC provider.");
}
}
}
"execute" => {
println!("\nExecuting RFQ swap...");
println!("Step 1: Sending permit2 approval...");
let approve_function_signature = "approve(address,uint256)";
let args = (
Address::from_str("0x000000000022D473030F116dDEE9F6B43aC78BA3")
.expect("Couldn't convert to address"),
biguint_to_u256(&amount_in),
);
let approval_data =
encode_input(approve_function_signature, args.abi_encode());
let nonce = provider
.get_transaction_count(signer.address())
.await
.expect("Failed to get nonce");
let block = provider
.get_block_by_number(BlockNumberOrTag::Latest)
.await
.expect("Failed to fetch latest block")
.expect("Block not found");
let base_fee = block
.header
.base_fee_per_gas
.expect("Base fee not available");
let max_priority_fee_per_gas = 1_000_000_000u64;
let max_fee_per_gas = base_fee + max_priority_fee_per_gas;
let approval_request = TransactionRequest {
to: Some(TxKind::Call(Address::from_slice(
&sell_token_address,
))),
from: Some(signer.address()),
value: None,
input: TransactionInput {
input: Some(AlloyBytes::from(approval_data)),
data: None,
},
gas: Some(100_000u64),
chain_id: Some(chain.id()),
max_fee_per_gas: Some(max_fee_per_gas.into()),
max_priority_fee_per_gas: Some(max_priority_fee_per_gas.into()),
nonce: Some(nonce),
..Default::default()
};
let approval_receipt = match provider
.send_transaction(approval_request)
.await
{
Ok(receipt) => receipt,
Err(e) => {
eprintln!("\nFailed to send approval transaction: {e:?}\n");
continue;
}
};
let approval_result = match approval_receipt.get_receipt().await {
Ok(result) => result,
Err(e) => {
eprintln!("\nFailed to get approval receipt: {e:?}\n");
continue;
}
};
println!(
"Approval transaction sent with hash: {hash:?} and status: {status:?}",
hash = approval_result.transaction_hash,
status = approval_result.status()
);
if !approval_result.status() {
eprintln!("\nApproval transaction failed! Cannot proceed with swap.\n");
continue;
}
println!("Step 2: Encoding solution transaction...");
let solution = create_solution(
component.clone(),
Arc::from(state.clone_box()),
sell_token.clone(),
buy_token.clone(),
amount_in.clone(),
Bytes::from(signer.address().to_vec()),
amount_out.clone(),
amount_out_result.gas,
);
let encoded_solution = encoder
.encode_solutions(vec![solution.clone()])
.expect("Failed to encode router calldata")[0]
.clone();
let swap_tx = encode_tycho_router_call(
chain.id(),
encoded_solution.clone(),
&solution,
chain.native_token().address,
signer.clone(),
)
.expect("Failed to encode router call");
let swap_request = TransactionRequest {
to: Some(TxKind::Call(Address::from_slice(&swap_tx.to))),
from: Some(signer.address()),
value: Some(biguint_to_u256(&swap_tx.value)),
input: TransactionInput {
input: Some(AlloyBytes::from(swap_tx.data)),
data: None,
},
gas: Some(1_000_000u64),
chain_id: Some(chain.id()),
max_fee_per_gas: Some(max_fee_per_gas.into()),
max_priority_fee_per_gas: Some(max_priority_fee_per_gas.into()),
nonce: Some(nonce + 1),
..Default::default()
};
let swap_receipt = match provider
.send_transaction(swap_request)
.await
{
Ok(receipt) => receipt,
Err(e) => {
eprintln!("\nFailed to send swap transaction: {e:?}\n");
continue;
}
};
let swap_result = match swap_receipt.get_receipt().await {
Ok(result) => result,
Err(e) => {
eprintln!("\nFailed to get swap receipt: {e:?}\n");
continue;
}
};
println!(
"Swap transaction sent with hash: {hash:?} and status: {status:?}",
hash = swap_result.transaction_hash,
status = swap_result.status()
);
if swap_result.status() {
println!(
"\n✅ Swap executed successfully! Exiting the session...\n"
);
let (forward_price, _reverse_price) = format_price_ratios(
&amount_in,
&expected_amount_copy,
&sell_token,
&buy_token,
);
println!(
"Summary: Swapped {formatted_in} {sell_symbol} → {formatted_out} {buy_symbol} at a price of {forward_price:.6} {buy_symbol} per {sell_symbol}",
formatted_in = format_token_amount(&amount_in, &sell_token),
sell_symbol = sell_token.symbol,
formatted_out = format_token_amount(&expected_amount_copy, &buy_token),
buy_symbol = buy_token.symbol,
);
return; } else {
eprintln!("\nSwap transaction failed!\n");
continue;
}
}
"skip" => {
println!("\nSkipping this swap...\n");
continue;
}
_ => {
println!("\nInvalid input. Please choose 'simulate', 'execute' or 'skip'.\n");
continue;
}
}
}
}
} else {
println!("No matching pair found in update.");
}
}
println!("\nWaiting for more price levels... (Press Ctrl+C to exit)");
}
}
fn format_token_amount(amount: &BigUint, token: &Token) -> String {
let decimal_amount = amount.to_f64().unwrap_or(0.0) / 10f64.powi(token.decimals as i32);
format!("{decimal_amount:.6}")
}
fn format_price_ratios(
amount_in: &BigUint,
amount_out: &BigUint,
token_in: &Token,
token_out: &Token,
) -> (f64, f64) {
let decimal_in = amount_in.to_f64().unwrap_or(0.0) / 10f64.powi(token_in.decimals as i32);
let decimal_out = amount_out.to_f64().unwrap_or(0.0) / 10f64.powi(token_out.decimals as i32);
if decimal_in > 0.0 && decimal_out > 0.0 {
let forward = decimal_out / decimal_in;
let reverse = decimal_in / decimal_out;
(forward, reverse)
} else {
(0.0, 0.0)
}
}
#[allow(clippy::too_many_arguments)]
fn create_solution(
component: ProtocolComponent,
state: Arc<dyn ProtocolSim>,
sell_token: Token,
buy_token: Token,
sell_amount: BigUint,
user_address: Bytes,
expected_amount: BigUint,
gas_usage: BigUint,
) -> Solution {
let simple_swap = Swap::new(component, sell_token.clone(), buy_token.clone(), gas_usage)
.with_protocol_state(state)
.with_estimated_amount_in(sell_amount.clone());
let slippage = 0.0025; let bps = BigUint::from(10_000u32);
let slippage_percent = BigUint::from((slippage * 10000.0) as u32);
let multiplier = &bps - slippage_percent;
let min_amount_out = (expected_amount * &multiplier) / &bps;
Solution::new(
user_address.clone(),
user_address,
sell_token.address,
buy_token.address,
sell_amount,
min_amount_out,
vec![simple_swap],
)
.with_user_transfer_type(UserTransferType::TransferFromPermit2)
}
fn encode_tycho_router_call(
chain_id: u64,
encoded_solution: EncodedSolution,
solution: &Solution,
native_address: Bytes,
signer: PrivateKeySigner,
) -> Result<Transaction, EncodingError> {
let given_amount = biguint_to_u256(solution.amount_in());
let min_amount_out = biguint_to_u256(solution.min_amount_out());
let given_token = Address::from_slice(solution.token_in());
let checked_token = Address::from_slice(solution.token_out());
let receiver = Address::from_slice(solution.receiver());
let permit2 = Permit2::new()?;
let p = permit2.get_permit(
encoded_solution.interacting_with(),
solution.sender(),
solution.token_in(),
solution.amount_in(),
)?;
let permit = PermitSingle::try_from(&p)
.map_err(|_| EncodingError::InvalidInput("Invalid permit".to_string()))?;
let signature = sign_permit(chain_id, &p, signer)?;
let method_calldata = (
given_amount,
given_token,
checked_token,
min_amount_out,
receiver,
permit,
signature.as_bytes().to_vec(),
encoded_solution.swaps().to_vec(),
)
.abi_encode();
let contract_interaction = encode_input(encoded_solution.function_signature(), method_calldata);
let value = if solution.token_in() == &native_address {
solution.amount_in().clone()
} else {
BigUint::ZERO
};
Ok(Transaction {
to: encoded_solution
.interacting_with()
.clone(),
value,
data: contract_interaction,
})
}
fn sign_permit(
chain_id: u64,
permit_single: &models::PermitSingle,
signer: PrivateKeySigner,
) -> Result<Signature, EncodingError> {
let permit2_address = Address::from_str("0x000000000022D473030F116dDEE9F6B43aC78BA3")
.map_err(|_| EncodingError::FatalError("Permit2 address not valid".to_string()))?;
let domain = eip712_domain! {
name: "Permit2",
chain_id: chain_id,
verifying_contract: permit2_address,
};
let permit_single: PermitSingle = PermitSingle::try_from(permit_single)?;
let hash = permit_single.eip712_signing_hash(&domain);
signer
.sign_hash_sync(&hash)
.map_err(|e| {
EncodingError::FatalError(format!("Failed to sign permit2 approval with error: {e}"))
})
}
pub fn encode_input(selector: &str, mut encoded_args: Vec<u8>) -> Vec<u8> {
let mut hasher = Keccak256::new();
hasher.update(selector.as_bytes());
let selector_bytes = &hasher.finalize()[..4];
let mut call_data = selector_bytes.to_vec();
if encoded_args.len() > 32 &&
encoded_args[..32] ==
[0u8; 31]
.into_iter()
.chain([32].to_vec())
.collect::<Vec<u8>>()
{
encoded_args = encoded_args[32..].to_vec();
}
call_data.extend(encoded_args);
call_data
}
async fn get_token_balance(
provider: &FillProvider<
JoinFill<Identity, WalletFiller<EthereumWallet>>,
RootProvider<Ethereum>,
>,
token_address: Address,
wallet_address: Address,
native_token_address: Address,
) -> Result<BigUint, Box<dyn std::error::Error>> {
let balance = if token_address == native_token_address {
provider
.get_balance(wallet_address)
.await?
} else {
let balance_of_signature = "balanceOf(address)";
let data = encode_input(balance_of_signature, (wallet_address,).abi_encode());
let result = provider
.call(TransactionRequest {
to: Some(TxKind::Call(token_address)),
input: TransactionInput { input: Some(AlloyBytes::from(data)), data: None },
..Default::default()
})
.await?;
U256::from_be_bytes(
result
.to_vec()
.try_into()
.unwrap_or([0u8; 32]),
)
};
Ok(BigUint::from_bytes_be(&balance.to_be_bytes::<32>()))
}