use alloy_primitives::{Address, U256};
use alloy_rpc_types::TransactionRequest;
use crate::{
AssemblyRequest, Chain, OdosChain, OdosClient, QuoteRequest, ReferralCode, Result,
SingleQuoteResponse, Slippage,
};
#[derive(Debug)]
pub struct SwapBuilder<'a> {
client: &'a OdosClient,
chain: Option<Chain>,
input_token: Option<Address>,
input_amount: Option<U256>,
output_token: Option<Address>,
slippage: Option<Slippage>,
signer: Option<Address>,
recipient: Option<Address>,
referral: ReferralCode,
compact: bool,
simple: bool,
disable_rfqs: bool,
}
impl<'a> SwapBuilder<'a> {
pub(crate) fn new(client: &'a OdosClient) -> Self {
Self {
client,
chain: None,
input_token: None,
input_amount: None,
output_token: None,
slippage: None,
signer: None,
recipient: None,
referral: ReferralCode::NONE,
compact: false,
simple: false,
disable_rfqs: false,
}
}
pub fn chain(mut self, chain: Chain) -> Self {
self.chain = Some(chain);
self
}
pub fn input(mut self, token: Address, amount: U256) -> Self {
self.input_token = Some(token);
self.input_amount = Some(amount);
self
}
pub fn from_token(self, token: Address, amount: U256) -> Self {
self.input(token, amount)
}
pub fn output(mut self, token: Address) -> Self {
self.output_token = Some(token);
self
}
pub fn to_token(self, token: Address) -> Self {
self.output(token)
}
pub fn slippage(mut self, slippage: Slippage) -> Self {
self.slippage = Some(slippage);
self
}
pub fn signer(mut self, address: Address) -> Self {
self.signer = Some(address);
self
}
pub fn recipient(mut self, address: Address) -> Self {
self.recipient = Some(address);
self
}
pub fn referral(mut self, code: ReferralCode) -> Self {
self.referral = code;
self
}
pub fn compact(mut self, compact: bool) -> Self {
self.compact = compact;
self
}
pub fn simple(mut self, simple: bool) -> Self {
self.simple = simple;
self
}
pub fn disable_rfqs(mut self, disable: bool) -> Self {
self.disable_rfqs = disable;
self
}
pub async fn quote(&self) -> Result<SingleQuoteResponse> {
let chain = self
.chain
.ok_or_else(|| crate::OdosError::missing_data("Chain is required for swap builder"))?;
let input_token = self.input_token.ok_or_else(|| {
crate::OdosError::missing_data("Input token is required for swap builder")
})?;
let input_amount = self.input_amount.ok_or_else(|| {
crate::OdosError::missing_data("Input amount is required for swap builder")
})?;
let output_token = self.output_token.ok_or_else(|| {
crate::OdosError::missing_data("Output token is required for swap builder")
})?;
let slippage = self.slippage.ok_or_else(|| {
crate::OdosError::missing_data("Slippage is required for swap builder")
})?;
let signer = self.signer.ok_or_else(|| {
crate::OdosError::missing_data("Signer address is required for swap builder")
})?;
let quote_request = QuoteRequest::builder()
.chain_id(chain.id())
.input_tokens(vec![(input_token, input_amount).into()])
.output_tokens(vec![(output_token, 1).into()])
.slippage_limit_percent(slippage.as_percent())
.user_addr(signer)
.compact(self.compact)
.simple(self.simple)
.referral_code(self.referral.code())
.disable_rfqs(self.disable_rfqs)
.build();
self.client.quote("e_request).await
}
pub async fn build_transaction(&self) -> Result<TransactionRequest> {
let quote = self.quote().await?;
let chain = self.chain.unwrap(); let signer = self.signer.unwrap(); let recipient = self.recipient.unwrap_or(signer);
let input_token = self.input_token.unwrap(); let input_amount = self.input_amount.unwrap();
let router_address = chain.v3_router_address()?;
let swap_context = AssemblyRequest::builder()
.chain(chain.inner())
.router_address(router_address)
.signer_address(signer)
.output_recipient(recipient)
.token_address(input_token)
.token_amount(input_amount)
.path_id(quote.path_id().to_string())
.build();
self.client.assemble(&swap_context).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::address;
#[test]
fn test_builder_construction() {
let client = OdosClient::new().unwrap();
let builder = client.swap();
assert!(builder.chain.is_none());
assert!(builder.input_token.is_none());
assert!(builder.output_token.is_none());
assert_eq!(builder.referral, ReferralCode::NONE);
}
#[test]
fn test_builder_chain_methods() {
let client = OdosClient::new().unwrap();
let builder = client
.swap()
.chain(Chain::ethereum())
.from_token(
address!("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"),
U256::from(1_000_000),
)
.to_token(address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"))
.slippage(Slippage::standard())
.signer(address!("742d35Cc6634C0532925a3b8D35f3e7a5edD29c0"));
assert_eq!(builder.chain.unwrap(), Chain::ethereum());
assert_eq!(
builder.input_token.unwrap(),
address!("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")
);
assert_eq!(builder.input_amount.unwrap(), U256::from(1_000_000));
assert_eq!(
builder.output_token.unwrap(),
address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")
);
assert_eq!(builder.slippage.unwrap(), Slippage::standard());
assert_eq!(
builder.signer.unwrap(),
address!("742d35Cc6634C0532925a3b8D35f3e7a5edD29c0")
);
}
#[test]
fn test_builder_aliases() {
let client = OdosClient::new().unwrap();
let builder1 = client.swap().input(
address!("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"),
U256::from(1000),
);
let builder2 = client.swap().from_token(
address!("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"),
U256::from(1000),
);
assert_eq!(builder1.input_token, builder2.input_token);
assert_eq!(builder1.input_amount, builder2.input_amount);
let builder1 = client
.swap()
.output(address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"));
let builder2 = client
.swap()
.to_token(address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"));
assert_eq!(builder1.output_token, builder2.output_token);
}
#[test]
fn test_builder_recipient_defaults_to_signer() {
let client = OdosClient::new().unwrap();
let signer_addr = address!("742d35Cc6634C0532925a3b8D35f3e7a5edD29c0");
let builder = client.swap().signer(signer_addr);
assert_eq!(builder.signer.unwrap(), signer_addr);
assert!(builder.recipient.is_none()); }
}