Skip to content

Commit

Permalink
Merge pull request #155 from propeller-heads/quickstart-polish
Browse files Browse the repository at this point in the history
fix(quickstart): Usability Fixes
  • Loading branch information
dianacarvalho1 authored Feb 28, 2025
2 parents 7e19921 + 0398fb6 commit 21d19ef
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 74 deletions.
15 changes: 14 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ revm-inspectors = { version = "0.10", features = ["serde"], optional = true }
num-bigint = "0.4.6"
tokio-stream = "0.1.16"

# Dialoguer
dialoguer = "0.10.4"

[dev-dependencies]
tokio-test = "0.4.4"
approx = "0.5.1"
Expand Down
251 changes: 178 additions & 73 deletions examples/quickstart/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use std::{
collections::{HashMap, HashSet},
default::Default,
env, io,
io::Write,
env,
str::FromStr,
};

Expand All @@ -23,8 +22,10 @@ use alloy::{
use alloy_primitives::{Address, Bytes as AlloyBytes, B256, U256};
use alloy_sol_types::SolValue;
use clap::Parser;
use dialoguer::{theme::ColorfulTheme, Select};
use futures::StreamExt;
use num_bigint::BigUint;
use num_traits::ToPrimitive;
use tracing_subscriber::EnvFilter;
use tycho_core::Bytes;
use tycho_execution::encoding::{
Expand Down Expand Up @@ -111,7 +112,7 @@ async fn main() {
BigUint::from((cli.sell_amount * 10f64.powi(sell_token.decimals as i32)) as u128);

println!(
"Looking for the best swap for {} {} -> {}",
"Looking for pool with best price for {} {} -> {}",
cli.sell_amount, sell_token.symbol, buy_token.symbol
);
let mut pairs: HashMap<String, ProtocolComponent> = HashMap::new();
Expand Down Expand Up @@ -161,8 +162,15 @@ async fn main() {
.expect("Failed to parse ETH_RPC_URL"),
);

while let Some(message) = protocol_stream.next().await {
let message = message.expect("Could not receive message");
while let Some(message_result) = protocol_stream.next().await {
let message = match message_result {
Ok(msg) => msg,
Err(e) => {
eprintln!("Error receiving message: {:?}. Continuing to next message...", e);
continue;
}
};

let best_swap = get_best_swap(
message,
&mut pairs,
Expand Down Expand Up @@ -192,27 +200,33 @@ async fn main() {
println!("Signer private key was not provided. Skipping simulation/execution...");
continue
}
println!("Do you want to simulate, execute or skip this swap?");
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.");
print!("(simulate/execute/skip): ");
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read input");

let input = input.trim().to_lowercase();

match input.as_str() {
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); // Default to skip if error

let choice = match selection {
0 => "simulate",
1 => "execute",
_ => "skip",
};

match choice {
"simulate" => {
println!("Simulating by performing an approval (for permit2) and a swap transaction...");

let (approval_request, swap_request) = get_tx_requests(
provider.clone(),
biguint_to_u256(&amount_in),
wallet.address(),
Address::from_slice(&sell_token_address),
tx,
tx.clone(),
)
.await;

Expand All @@ -227,67 +241,72 @@ async fn main() {
return_full_transactions: true,
};

let output = provider
.simulate(&payload)
.await
.expect("Failed to simulate transaction");

for block in output.iter() {
println!("Simulated Block {}:", block.inner.header.number);
for (j, transaction) in block.calls.iter().enumerate() {
println!(
" Transaction {}: Status: {:?}, Gas Used: {}",
j + 1,
transaction.status,
transaction.gas_used
);
match provider.simulate(&payload).await {
Ok(output) => {
for block in output.iter() {
println!("Simulated Block {}:", block.inner.header.number);
for (j, transaction) in block.calls.iter().enumerate() {
println!(
" Transaction {}: Status: {:?}, Gas Used: {}",
j + 1,
transaction.status,
transaction.gas_used
);
}
}
}
Err(e) => {
eprintln!("Simulation failed: {:?}", e);
println!("Your RPC provider does not support transaction simulation.");
println!("Do you want to proceed with execution instead?");
let yes_no_options = vec!["Yes", "No"];
let yes_no_selection = Select::with_theme(&ColorfulTheme::default())
.with_prompt("Do you want to proceed with execution instead?")
.default(1) // Default to No
.items(&yes_no_options)
.interact()
.unwrap_or(1); // Default to No if error

if yes_no_selection == 0 {
match execute_swap_transaction(
provider.clone(),
&amount_in,
wallet.address(),
&sell_token_address,
tx.clone(),
)
.await
{
Ok(_) => return,
Err(e) => {
eprintln!("Failed to execute transaction: {:?}", e);
continue;
}
}
} else {
println!("Skipping this swap...");
continue;
}
}
}
// println!("Full simulation logs: {:?}", output);
return;
}
"execute" => {
println!("Executing by performing an approval (for permit2) and a swap transaction...");
let (approval_request, swap_request) = get_tx_requests(
match execute_swap_transaction(
provider.clone(),
biguint_to_u256(&amount_in),
&amount_in,
wallet.address(),
Address::from_slice(&sell_token_address),
&sell_token_address,
tx,
)
.await;

let approval_receipt = provider
.send_transaction(approval_request)
.await
.expect("Failed to send transaction");

let approval_result = approval_receipt
.get_receipt()
.await
.expect("Failed to get approval receipt");
println!(
"Approval transaction sent with hash: {:?} and status: {:?}",
approval_result.transaction_hash,
approval_result.status()
);

let swap_receipt = provider
.send_transaction(swap_request)
.await
.expect("Failed to send transaction");

let swap_result = swap_receipt
.get_receipt()
.await
.expect("Failed to get swap receipt");
println!(
"Swap transaction sent with hash: {:?} and status: {:?}",
swap_result.transaction_hash,
swap_result.status()
);

return;
.await
{
Ok(_) => return,
Err(e) => {
eprintln!("Failed to execute transaction: {:?}", e);
continue;
}
}
}
"skip" => {
println!("Skipping this swap...");
Expand Down Expand Up @@ -346,16 +365,30 @@ fn get_best_swap(
{
println!("The best swap (out of {} possible pools) is:", amounts_out.len());
println!(
"protocol: {:?}",
"Protocol: {:?}",
pairs
.get(key)
.expect("Failed to get best pool")
.protocol_system
);
println!("id: {:?}", key);
println!("Pool address: {:?}", key);
let formatted_in = format_token_amount(&amount_in, &sell_token);
let formatted_out = format_token_amount(amount_out, &buy_token);
let (forward_price, reverse_price) =
format_price_ratios(&amount_in, amount_out, &sell_token, &buy_token);

println!(
"swap: {:?} {:} -> {:?} {:}",
amount_in, sell_token.symbol, amount_out, buy_token.symbol
"Swap: {} {} -> {} {} \nPrice: {:.6} {} per {}, {:.6} {} per {}",
formatted_in,
sell_token.symbol,
formatted_out,
buy_token.symbol,
forward_price,
buy_token.symbol,
sell_token.symbol,
reverse_price,
sell_token.symbol,
buy_token.symbol
);
Some((key.to_string(), amount_out.clone()))
} else {
Expand Down Expand Up @@ -465,3 +498,75 @@ async fn get_tx_requests(

(approval_request, swap_request)
}

// Format token amounts to human-readable values
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!("{:.6}", decimal_amount)
}

// Calculate price ratios in both directions
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)
}
}

async fn execute_swap_transaction(
provider: FillProvider<
JoinFill<Identity, WalletFiller<EthereumWallet>>,
ReqwestProvider,
Http<Client>,
Ethereum,
>,
amount_in: &BigUint,
wallet_address: Address,
sell_token_address: &Bytes,
tx: Transaction,
) -> Result<(), Box<dyn std::error::Error>> {
println!("Executing by performing an approval (for permit2) and a swap transaction...");
let (approval_request, swap_request) = get_tx_requests(
provider.clone(),
biguint_to_u256(amount_in),
wallet_address,
Address::from_slice(sell_token_address),
tx.clone(),
)
.await;

let approval_receipt = provider
.send_transaction(approval_request)
.await?;

let approval_result = approval_receipt.get_receipt().await?;
println!(
"Approval transaction sent with hash: {:?} and status: {:?}",
approval_result.transaction_hash,
approval_result.status()
);

let swap_receipt = provider
.send_transaction(swap_request)
.await?;

let swap_result = swap_receipt.get_receipt().await?;
println!(
"Swap transaction sent with hash: {:?} and status: {:?}",
swap_result.transaction_hash,
swap_result.status()
);

Ok(())
}

0 comments on commit 21d19ef

Please sign in to comment.