Skip to main content

Local Cache

Requires the cache feature flag: polynode = { version = "0.7.3", features = ["cache"] }
Local SQLite cache that backfills trade history on startup, streams live updates via WebSocket, and serves all queries locally with zero additional API calls after initialization. Driven by a JSON watchlist file that specifies which wallets, markets, and tokens to track.

Setup

Create a polynode.watch.json file:
{
  "version": 1,
  "wallets": [
    { "address": "0x1a1A27de044faFFCCf68E28F03dCfCf5eB3d3cE6", "label": "whale-1", "backfill": true },
    { "address": "0xBB39C16C3fc54d3C9B1f9f9E8dF4a09Ee25AB7df", "label": "whale-2", "backfill": true }
  ],
  "markets": [
    { "condition_id": "0xabc...", "label": "bitcoin-100k", "backfill": true }
  ],
  "tokens": [],
  "settings": {
    "ttl_days": 30,
    "backfill_rate": 2.0,
    "purge_on_remove": false
  }
}

Start the Cache

use polynode::{PolyNodeClient, cache::PolyNodeCache};
use std::sync::Arc;

#[tokio::main]
async fn main() -> polynode::Result<()> {
    let client = Arc::new(PolyNodeClient::new("pn_live_test_session_tracking_51eca107e9b347b589f5b0a04f98eb1d")?);

    let mut cache = PolyNodeCache::builder(client)
        .db_path("./polynode-cache.db")
        .watchlist_path("./polynode.watch.json")
        .ttl_seconds(30 * 86400)            // 30 days
        .backfill_rate(2.0)                  // 2 requests/sec
        .backfill_pages(3)                   // 3 pages per entity
        .backfill_page_size(500)             // 500 trades per page
        .purge_on_remove(false)              // keep data when removing from watchlist
        .on_backfill_progress(|p| {
            println!("[{}] {}: {} fetched ({})", p.entity_type, p.label, p.fetched, p.status);
        })
        .build()?;

    cache.start().await?;

    // All queries are local, instant, no API calls
    let positions = cache.wallet_positions("0x1a1A27de044faFFCCf68E28F03dCfCf5eB3d3cE6")?;
    for p in &positions {
        println!("{}: {} {} @ {:.4} (pnl: {:?})",
            p.market_title, p.outcome, p.size, p.avg_price, p.cash_pnl);
    }

    cache.stop().await?;
    Ok(())
}

Query Methods

All queries are synchronous and read from local SQLite:
use polynode::cache::{QueryOptions, OrderBy};

# fn example(cache: &polynode::cache::PolyNodeCache) -> polynode::Result<()> {
// Wallet positions
let positions = cache.wallet_positions("0xabc...")?;

// Multi-wallet positions
let multi = cache.multi_wallet_positions(&[
    "0xabc...".into(),
    "0xdef...".into(),
])?;

// Wallet trades with filters
let trades = cache.wallet_trades("0xabc...", &QueryOptions {
    limit: Some(100),
    offset: None,
    since: Some(1711843200.0),
    until: None,
    side: Some("BUY".into()),
    order_by: Some(OrderBy::TimestampDesc),
})?;

// Market trades
let market_trades = cache.market_trades("condition_id", &QueryOptions::default())?;

// Market positions
let market_pos = cache.market_positions("condition_id")?;

// Token trades
let token_trades = cache.token_trades("token_id", &QueryOptions::default())?;

// Settlements
let settlements = cache.wallet_settlements("0xabc...", &QueryOptions::default())?;

// Lookup by tx hash
let tx = cache.trade_by_tx_hash("0xdeadbeef...")?;

// Realized P&L (weighted average cost basis)
let pnl = cache.wallet_realized_pnl("0xabc...")?;
println!("realized: ${:.2}, unrealized: ${:.2}, confidence: {}",
    pnl.total_realized_pnl, pnl.total_unrealized_pnl, pnl.confidence);

// Cache stats
let stats = cache.stats()?;
println!("{} trades, {} settlements, {:.1}MB",
    stats.trade_count, stats.settlement_count, stats.db_size_bytes as f64 / 1_048_576.0);
# Ok(())
# }

Runtime Watchlist Management

Add or remove entities without restarting:
use polynode::cache::EntityType;

# fn example(cache: &polynode::cache::PolyNodeCache) -> polynode::Result<()> {
// Add a wallet at runtime (will backfill automatically)
cache.add_to_watchlist(&[
    (EntityType::Wallet, "0xnew...".into(), "new-whale".into(), true),
])?;

// Remove a wallet
cache.remove_from_watchlist(&[
    (EntityType::Wallet, "0xold...".into()),
])?;
# Ok(())
# }
The cache also watches the polynode.watch.json file for changes. Edit the file and the cache picks up additions and removals automatically.

Manual Pruning

# fn example(cache: &polynode::cache::PolyNodeCache) -> polynode::Result<()> {
let pruned = cache.prune()?;
println!("pruned {} expired records", pruned);
# Ok(())
# }