use alloy::{
primitives::{Address, B256},
signers::{local::PrivateKeySigner, Signer},
};
use bytes::Bytes;
use fynd_client::{
AllowanceCheck, ApprovalParams, ClientFeeParams, EncodingOptions, ExecutionOptions,
FyndClientBuilder, Order, OrderSide, QuoteOptions, QuoteParams, SignedApproval, SignedSwap,
SigningHints,
};
use num_bigint::BigUint;
const DEFAULT_FYND_URL: &str = "http://localhost:3000";
const DEFAULT_RPC_URL: &str = "http://localhost:8545";
const DEV_PRIVATE_KEY: &str = "0x02d483ff876e4d1d55ddc829a22df2707bd2574ba18d0d870ef9c9edd3c0fe29";
const WETH: &str = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
const USDC: &str = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const SELL_AMOUNT: u128 = 1_000_000_000_000_000_000; const SLIPPAGE: f64 = 0.01; const FEE_BPS: u16 = 50;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let fynd_url = std::env::var("FYND_URL").unwrap_or_else(|_| DEFAULT_FYND_URL.to_owned());
let rpc_url = std::env::var("RPC_URL").unwrap_or_else(|_| DEFAULT_RPC_URL.to_owned());
let private_key = std::env::var("PRIVATE_KEY").unwrap_or_else(|_| DEV_PRIVATE_KEY.to_owned());
let sender_signer: PrivateKeySigner = private_key.parse()?;
let sender = sender_signer.address();
let sell_token: Address = WETH.parse()?;
let buy_token: Address = USDC.parse()?;
let fee_signer = PrivateKeySigner::random();
let fee_receiver = fee_signer.address();
let client = FyndClientBuilder::new(&fynd_url)
.with_rpc_url(&rpc_url)
.with_sender(sender)
.build()
.await
.map_err(|e| {
format!(
"{e}\n\nFynd not running at {fynd_url}. \
Start the dev environment:\n ./scripts/run-example.sh {}",
env!("CARGO_BIN_NAME")
)
})?;
let info = client.info().await?;
let router_address = info
.router_address()
.expect("example requires a chain with encoding support")
.clone();
let chain_id = info.chain_id();
if let Some(approval_payload) = client
.approval(
&ApprovalParams::new(
Bytes::copy_from_slice(sell_token.as_slice()),
BigUint::from(SELL_AMOUNT),
AllowanceCheck::AtLeast(BigUint::from(SELL_AMOUNT)),
),
&SigningHints::default(),
)
.await?
{
println!("Approving router to spend WETH...");
let sig = sender_signer
.sign_hash(&approval_payload.signing_hash())
.await?;
client
.execute_approval(SignedApproval::assemble(approval_payload, sig))
.await?
.await?;
println!("Approved.");
}
let fee = ClientFeeParams::new(
FEE_BPS,
Bytes::copy_from_slice(fee_receiver.as_slice()),
BigUint::ZERO,
u64::MAX,
);
let order = Order::new(
Bytes::copy_from_slice(sell_token.as_slice()),
Bytes::copy_from_slice(buy_token.as_slice()),
BigUint::from(SELL_AMOUNT),
OrderSide::Sell,
Bytes::copy_from_slice(sender.as_slice()),
None,
);
let quote = client
.quote(QuoteParams::new(
order,
QuoteOptions::default()
.with_timeout_ms(5_000)
.with_encoding_options(EncodingOptions::new(SLIPPAGE).with_client_fee(fee.clone())),
))
.await?;
let fee_breakdown = quote
.fee_breakdown()
.ok_or("no fee breakdown in quote")?;
let swaps_hash = fee_breakdown
.swaps_hash()
.ok_or("no swaps_hash — server must support client fee signing")?;
let hash = fee.eip712_signing_hash(
chain_id,
&router_address,
quote.amount_in(),
&Bytes::copy_from_slice(sell_token.as_slice()),
&Bytes::copy_from_slice(buy_token.as_slice()),
quote.amount_out(),
fee_breakdown.min_amount_received(),
&Bytes::copy_from_slice(sender.as_slice()),
swaps_hash,
)?;
let sig = fee_signer
.sign_hash(&B256::from(hash))
.await?;
let quote = quote.with_client_fee_signature(&sig.as_bytes()[..])?;
println!("amount_in: {}", quote.amount_in());
println!("amount_out: {}", quote.amount_out());
let payload = client
.swap_payload(quote, &SigningHints::default().with_simulate(true))
.await?;
let tx_sig = sender_signer
.sign_hash(&payload.signing_hash())
.await?;
let result = client
.execute_swap(SignedSwap::assemble(payload, tx_sig), &ExecutionOptions::default())
.await?
.await?;
println!("settled: {:?} USDC", result.settled_amount());
println!("gas cost: {}", result.gas_cost());
Ok(())
}