Skip to main content

odos_sdk/
v2_router.rs

1// SPDX-FileCopyrightText: 2025 Semiotic AI, Inc.
2//
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{fmt::Debug, marker::PhantomData};
6
7use alloy_contract::CallBuilder;
8use alloy_network::{Ethereum, Network};
9use alloy_primitives::{Address, Bytes, U256};
10use alloy_provider::Provider;
11use alloy_sol_types::{sol, SolInterface};
12
13use crate::SwapInputs;
14
15// Import generated types after sol! macro
16use OdosRouterV2::{inputTokenInfo, outputTokenInfo, swapTokenInfo};
17use OdosV2Router::{swapCall, OdosV2RouterCalls, OdosV2RouterInstance, Swap, SwapMulti};
18
19/// The V2 SOR Router contract.
20///
21/// This router is generic over the network type, allowing it to work with both standard
22/// Ethereum networks and OP-stack networks (Optimism, Base, Fraxtal).
23///
24/// # Type Parameters
25///
26/// - `N`: The network type (defaults to `Ethereum`). Use `op_alloy_network::Optimism` for OP-stack chains.
27/// - `P`: The provider type.
28///
29/// # Example
30///
31/// ```rust,ignore
32/// use odos_sdk::V2Router;
33/// use alloy_network::Ethereum;
34/// use alloy_provider::ProviderBuilder;
35///
36/// // Standard Ethereum usage
37/// let provider = ProviderBuilder::new().connect_http("https://eth.llamarpc.com".parse()?);
38/// let router: V2Router<Ethereum, _> = V2Router::new(address, provider);
39///
40/// // OP-stack usage
41/// use op_alloy_network::Optimism;
42/// let op_provider = ProviderBuilder::new()
43///     .network::<Optimism>()
44///     .connect_http("https://mainnet.base.org".parse()?);
45/// let op_router: V2Router<Optimism, _> = V2Router::new(address, op_provider);
46/// ```
47#[derive(Debug, Clone)]
48pub struct V2Router<N: Network = Ethereum, P: Provider<N> = alloy_provider::RootProvider<N>> {
49    instance: OdosV2RouterInstance<P, N>,
50}
51
52impl<N: Network, P: Provider<N>> V2Router<N, P> {
53    /// Creates a new V2 router instance.
54    pub fn new(address: Address, provider: P) -> Self {
55        Self {
56            instance: OdosV2RouterInstance::new(address, provider),
57        }
58    }
59
60    /// Returns the contract owner address.
61    pub async fn owner(&self) -> Result<Address, alloy_contract::Error> {
62        self.instance.owner().call().await
63    }
64
65    /// Builds a swap call using router funds.
66    pub fn build_swap_router_funds_call(
67        &self,
68        input_token_info: inputTokenInfo,
69        output_token_info: outputTokenInfo,
70        inputs: &SwapInputs,
71        from: Address,
72    ) -> CallBuilder<&P, PhantomData<OdosV2Router::swapRouterFundsCall>, N> {
73        self.instance
74            .swapRouterFunds(
75                vec![input_token_info],
76                vec![output_token_info],
77                inputs.value_out_min(),
78                inputs.path_definition().clone(),
79                inputs.executor(),
80            )
81            .from(from)
82    }
83
84    /// Transfers router funds to a recipient.
85    pub fn transfer_router_funds(
86        &self,
87        from: Address,
88        token: Address,
89        amount: U256,
90        output_recipient: Address,
91    ) -> CallBuilder<&P, PhantomData<OdosV2Router::transferRouterFundsCall>, N> {
92        self.instance
93            .transferRouterFunds(vec![token], vec![amount], output_recipient)
94            .from(from)
95    }
96
97    /// Returns the calldata for a transfer router funds call.
98    pub fn transfer_router_funds_calldata(
99        &self,
100        from: Address,
101        token: Address,
102        amount: U256,
103        output_recipient: Address,
104    ) -> Vec<u8> {
105        self.transfer_router_funds(from, token, amount, output_recipient)
106            .calldata()
107            .to_vec()
108    }
109}
110
111// codegen the odos_v2_router contract
112sol!(
113    #[allow(clippy::too_many_arguments)]
114    #[allow(missing_docs)]
115    #[sol(rpc)]
116    OdosV2Router,
117    "abis/odos_v2_router.json"
118);
119
120impl Debug for OdosV2Router::swapRouterFundsReturn {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        write!(f, "amountsOut: {:?}", self.amountsOut)
123    }
124}
125
126impl Debug for OdosRouterV2::inputTokenInfo {
127    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128        f.debug_struct("inputTokenInfo")
129            .field("tokenAddress", &self.tokenAddress)
130            .field("amountIn", &self.amountIn)
131            .field("receiver", &self.receiver)
132            .finish()
133    }
134}
135
136impl Debug for OdosRouterV2::outputTokenInfo {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        f.debug_struct("outputTokenInfo")
139            .field("tokenAddress", &self.tokenAddress)
140            .field("relativeValue", &self.relativeValue)
141            .field("receiver", &self.receiver)
142            .finish()
143    }
144}
145
146impl Debug for swapCall {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        f.debug_struct("swapCall")
149            .field("executor", &self.executor)
150            .field("pathDefinition", &self.pathDefinition)
151            .field("referralCode", &self.referralCode)
152            .field("tokenInfo", &self.tokenInfo)
153            .finish()
154    }
155}
156
157impl Debug for swapTokenInfo {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        f.debug_struct("swapTokenInfo")
160            .field("inputToken", &self.inputToken)
161            .field("inputAmount", &self.inputAmount)
162            .field("inputReceiver", &self.inputReceiver)
163            .field("outputMin", &self.outputMin)
164            .field("outputQuote", &self.outputQuote)
165            .field("outputReceiver", &self.outputReceiver)
166            .finish()
167    }
168}
169
170impl Debug for SwapMulti {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        f.debug_struct("SwapMulti")
173            .field("sender", &self.sender)
174            .field("amountsIn", &self.amountsIn)
175            .field("tokensIn", &self.tokensIn)
176            .field("amountsOut", &self.amountsOut)
177            .field("tokensOut", &self.tokensOut)
178            .finish()
179    }
180}
181
182impl Debug for Swap {
183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        f.debug_struct("Swap")
185            .field("sender", &self.sender)
186            .field("inputAmount", &self.inputAmount)
187            .field("inputToken", &self.inputToken)
188            .field("amountOut", &self.amountOut)
189            .field("outputToken", &self.outputToken)
190            .field("slippage", &self.slippage)
191            .field("referralCode", &self.referralCode)
192            .finish()
193    }
194}
195
196impl TryFrom<&Bytes> for OdosV2RouterCalls {
197    type Error = alloy_sol_types::Error;
198
199    fn try_from(input: &Bytes) -> Result<Self, Self::Error> {
200        OdosV2RouterCalls::abi_decode(input)
201    }
202}