Skip to main content

price_levels/
price_levels.rs

1//! pAMM price levels: Titan's pre-quoted prices for a range of trade sizes,
2//! grouped per pAMM. Served from Titan's latest snapshot over HTTP (no node
3//! RPC, no key), unlike `router.quote`, which runs an on-chain `eth_call`.
4//!
5//! Run:
6//!
7//!   cargo run --example price_levels
8//!
9//! Set PRICE_LEVELS_URL to use a specific HTTP endpoint.
10
11use std::sync::Arc;
12
13use propamm::common::helpers::{format_units, parse_address};
14use propamm::common::pamms::PAMMS;
15use propamm::common::tokens::{USDC, USDT, WETH};
16use propamm::prices::{PriceLevels, PriceLevelsRpcSource};
17use propamm::{Address, U256};
18
19#[tokio::main(flavor = "current_thread")]
20async fn main() -> Result<(), Box<dyn std::error::Error>> {
21    // PriceLevels defaults to a one-shot HTTP snapshot source. Set
22    // PRICE_LEVELS_URL to point snapshots and quotes at a specific endpoint.
23    let prices = match std::env::var("PRICE_LEVELS_URL") {
24        Ok(url) => PriceLevels::with_source(Arc::new(PriceLevelsRpcSource::new(url))),
25        Err(_) => PriceLevels::new(),
26    };
27
28    // 1. Full snapshot: every pAMM's order book.
29    let snapshot = prices.get_price_levels().await?;
30    println!(
31        "snapshot @ block {} — {} pAMM(s)",
32        snapshot
33            .block_number
34            .map_or_else(|| "?".to_string(), |b| b.to_string()),
35        snapshot.pamms.len()
36    );
37
38    // Pick the first pAMM/pair that carries an order book to display.
39    let Some((pamm, pair)) = snapshot.pamms.iter().find_map(|entry| {
40        entry
41            .pairs
42            .iter()
43            .find(|pair| !pair.order_book.is_empty())
44            .map(|pair| (entry.pamm, pair))
45    }) else {
46        return Err("snapshot carried no order books".into());
47    };
48
49    println!(
50        "\n{} order book: {} -> {}",
51        venue_name(pamm),
52        token_symbol(pair.token_in),
53        token_symbol(pair.token_out),
54    );
55    for level in &pair.order_book {
56        println!(
57            "  {} -> {}  [{:?}]",
58            fmt(level.amount_in, pair.token_in),
59            fmt(level.amount_out, pair.token_out),
60            level.variant,
61        );
62    }
63
64    // 2. Quote helpers, for that pair and a mid-ladder size. `get_quote`
65    // returns the best across all pAMMs; `get_quote_venue` pins to one.
66    let size = pair.order_book[pair.order_book.len() / 2].amount_in;
67
68    let best = prices
69        .get_quote(pair.token_in, pair.token_out, size)
70        .await?;
71    println!(
72        "\nbest quote:  {} -> {} via {}",
73        fmt(best.amount_in, best.token_in),
74        fmt(best.amount_out, best.token_out),
75        venue_name(best.pamm),
76    );
77
78    let pinned = prices
79        .get_quote_venue(pamm, pair.token_in, pair.token_out, size)
80        .await?;
81    println!(
82        "{} quote: {} -> {}",
83        venue_name(pamm),
84        fmt(pinned.amount_in, pinned.token_in),
85        fmt(pinned.amount_out, pinned.token_out),
86    );
87
88    Ok(())
89}
90
91/// Known tokens, for readable amounts. Unknown tokens print raw (0 decimals).
92fn token_info(token: Address) -> (String, u32) {
93    let wbtc = parse_address("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
94    if token == USDC {
95        ("USDC".into(), 6)
96    } else if token == USDT {
97        ("USDT".into(), 6)
98    } else if token == WETH {
99        ("WETH".into(), 18)
100    } else if token == wbtc {
101        ("WBTC".into(), 8)
102    } else {
103        (format!("{token:#x}"), 0)
104    }
105}
106
107fn token_symbol(token: Address) -> String {
108    token_info(token).0
109}
110
111fn fmt(amount: U256, token: Address) -> String {
112    let (symbol, decimals) = token_info(token);
113    format!("{} {}", format_units(amount, decimals), symbol)
114}
115
116/// Map a venue address back to its readable pAMM name, or its raw hex.
117fn venue_name(address: Address) -> String {
118    PAMMS
119        .iter()
120        .find(|(_, a)| *a == address)
121        .map_or_else(|| format!("{address:#x}"), |(name, _)| name.to_string())
122}