use alloy_primitives::{address, Address, B256, I256, U256};
use alloy_rpc_types::Log;
use alloy_sol_types::{sol, SolEvent};
use semioscan::price::{PriceSource, PriceSourceError, SwapData};
sol! {
#[derive(Debug)]
event UniswapV3Swap(
address indexed sender,
address indexed recipient,
int256 amount0,
int256 amount1,
uint160 sqrtPriceX96,
uint128 liquidity,
int24 tick
);
}
pub struct UniswapV3PriceSource {
pool_address: Address,
token0: Address,
token1: Address,
allowed_sender: Option<Address>,
}
impl UniswapV3PriceSource {
pub fn new(pool_address: Address, token0: Address, token1: Address) -> Self {
Self {
pool_address,
token0,
token1,
allowed_sender: None,
}
}
pub fn with_sender_filter(mut self, sender: Address) -> Self {
self.allowed_sender = Some(sender);
self
}
fn to_unsigned(amount: I256) -> U256 {
if amount.is_negative() {
amount.unsigned_abs()
} else {
amount.into_raw()
}
}
}
impl PriceSource for UniswapV3PriceSource {
fn router_address(&self) -> Address {
self.pool_address
}
fn event_topics(&self) -> Vec<B256> {
vec![UniswapV3Swap::SIGNATURE_HASH]
}
fn extract_swap_from_log(&self, log: &Log) -> Result<Option<SwapData>, PriceSourceError> {
let event = UniswapV3Swap::decode_log(&log.clone().into())?;
let (token_in, token_in_amount, token_out, token_out_amount) =
if event.amount0.is_negative() {
(
self.token1,
Self::to_unsigned(event.amount1),
self.token0,
Self::to_unsigned(event.amount0),
)
} else {
(
self.token0,
Self::to_unsigned(event.amount0),
self.token1,
Self::to_unsigned(event.amount1),
)
};
if token_in_amount.is_zero() || token_out_amount.is_zero() {
return Err(PriceSourceError::invalid_swap_data("Zero amount in swap"));
}
Ok(Some(SwapData {
token_in,
token_in_amount,
token_out,
token_out_amount,
sender: Some(event.sender),
tx_hash: log.transaction_hash,
block_number: log.block_number,
}))
}
fn should_include_swap(&self, swap: &SwapData) -> bool {
match self.allowed_sender {
Some(allowed) => swap.sender == Some(allowed),
None => true, }
}
}
fn main() {
println!("\n=== Custom DEX Integration Template ===\n");
println!("This example shows how to integrate any DEX with semioscan's");
println!("PriceSource trait. Follow these steps:\n");
println!("1. Define Your DEX Events");
println!(" ├─ Use alloy_sol_types::sol! macro");
println!(" ├─ Copy event signatures from your DEX contract");
println!(" └─ Example: UniswapV3Swap event above\n");
println!("2. Create a Price Source Struct");
println!(" ├─ Store contract address(es)");
println!(" ├─ Store token addresses (if needed)");
println!(" └─ Add optional filters (sender, etc.)\n");
println!("3. Implement PriceSource Trait");
println!(" ├─ router_address() → contract to monitor");
println!(" ├─ event_topics() → event signatures");
println!(" ├─ extract_swap_from_log() → parse events");
println!(" └─ should_include_swap() → optional filtering\n");
println!("4. Use with PriceCalculator");
println!(" ├─ Create your PriceSource impl");
println!(" ├─ Wrap in Box<dyn PriceSource>");
println!(" └─ Pass to PriceCalculator::with_price_source()\n");
let pool = address!("88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"); let usdc = address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
let weth = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
let _price_source = UniswapV3PriceSource::new(pool, usdc, weth);
println!("Example: Uniswap V3 USDC/WETH Pool");
println!(" Pool: {}", pool);
println!(" Token0 (USDC): {}", usdc);
println!(" Token1 (WETH): {}", weth);
println!("\n Price source created successfully!\n");
println!("=== Real Usage Example ===\n");
println!("```rust");
println!("use semioscan::{{PriceCalculator, price::PriceSource}};");
println!("use alloy_provider::ProviderBuilder;");
println!();
println!("// Create your custom price source");
println!("let price_source = UniswapV3PriceSource::new(pool, usdc, weth);");
println!();
println!("// Create provider");
println!("let provider = ProviderBuilder::new()");
println!(" .connect_http(rpc_url.parse()?);");
println!();
println!("// Create calculator with your price source");
println!("let calculator = PriceCalculator::with_price_source(");
println!(" provider.root().clone(),");
println!(" Box::new(price_source),");
println!(");");
println!();
println!("// Extract prices");
println!("let result = calculator.get_price_for_token_pair(");
println!(" chain_id,");
println!(" weth, // token_in");
println!(" usdc, // token_out");
println!(" start_block,");
println!(" end_block,");
println!(").await?;");
println!();
println!("println!(\"Average price: {{}}\", result.average_price_display());");
println!("```\n");
println!("=== DEX-Specific Considerations ===\n");
println!("Uniswap V2/V3:");
println!(" - Pool address is the router");
println!(" - Handle signed amounts (V3)");
println!(" - Determine direction from amount signs\n");
println!("Curve:");
println!(" - Pool address is the router");
println!(" - Multiple event types (TokenExchange, etc.)");
println!(" - May need to track pool indices\n");
println!("Balancer:");
println!(" - Vault address is the router");
println!(" - PoolId identifies specific pools");
println!(" - Multi-token swaps possible\n");
println!("Aggregators (1inch, Odos, etc.):");
println!(" - Router address for all swaps");
println!(" - Filter by specific tokens");
println!(" - May have multiple event types\n");
println!("=== Testing Your Implementation ===\n");
println!("1. Start with a known swap transaction hash");
println!("2. Verify event decoding works correctly");
println!("3. Check token direction is correct");
println!("4. Validate amounts match blockchain data");
println!("5. Test filtering logic (if implemented)\n");
println!("=== Additional Resources ===\n");
println!("- Alloy docs: https://alloy.rs");
println!("- Semioscan examples: crates/semioscan/examples/");
println!("- Odos implementation: crates/semioscan/src/price/odos.rs");
}