polymarket-client-sdk 0.4.4

Polymarket CLOB (Central Limit Order Book) API client SDK
Documentation
#![allow(clippy::exhaustive_enums, reason = "Generated by sol! macro")]
#![allow(clippy::exhaustive_structs, reason = "Generated by sol! macro")]
#![allow(clippy::print_stderr, reason = "Usage message to stderr")]
#![allow(clippy::unwrap_used, reason = "Examples use unwrap for brevity")]

//! Read-only example to check current token approvals for Polymarket CLOB trading.
//!
//! This example queries the blockchain to show which contracts are approved
//! for a given wallet address. No private key or gas required.
//!
//! Run with tracing enabled:
//! ```sh
//! RUST_LOG=info,hyper_util=off,hyper=off,reqwest=off,h2=off,rustls=off cargo run --example check_approvals --all-features -- <WALLET_ADDRESS>
//! ```
//!
//! Optionally log to a file:
//! ```sh
//! LOG_FILE=check_approvals.log RUST_LOG=info,hyper_util=off,hyper=off,reqwest=off,h2=off,rustls=off cargo run --example check_approvals --all-features -- <WALLET_ADDRESS>
//! ```
//!
//! Example:
//! ```sh
//! RUST_LOG=info cargo run --example check_approvals --all-features -- 0x1234...abcd
//! ```

use std::env;
use std::fs::File;

use alloy::primitives::U256;
use alloy::providers::ProviderBuilder;
use alloy::sol;
use polymarket_client_sdk::types::{Address, address};
use polymarket_client_sdk::{POLYGON, contract_config};
use tracing::{debug, info};
use tracing_subscriber::EnvFilter;
use tracing_subscriber::layer::SubscriberExt as _;
use tracing_subscriber::util::SubscriberInitExt as _;

const RPC_URL: &str = "https://polygon-rpc.com";

const USDC_ADDRESS: Address = address!("0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174");

sol! {
    #[sol(rpc)]
    interface IERC20 {
        function allowance(address owner, address spender) external view returns (uint256);
    }

    #[sol(rpc)]
    interface IERC1155 {
        function isApprovedForAll(address account, address operator) external view returns (bool);
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    if let Ok(path) = std::env::var("LOG_FILE") {
        let file = File::create(path)?;
        tracing_subscriber::registry()
            .with(EnvFilter::from_default_env())
            .with(
                tracing_subscriber::fmt::layer()
                    .with_writer(file)
                    .with_ansi(false),
            )
            .init();
    } else {
        tracing_subscriber::fmt::init();
    }

    let args: Vec<String> = env::args().collect();

    if args.len() != 2 {
        debug!(
            args = args.len(),
            "invalid arguments - expected wallet address"
        );
        eprintln!("Usage: cargo run --example check_approvals -- <WALLET_ADDRESS>");
        eprintln!(
            "Example: cargo run --example check_approvals -- 0x1234567890abcdef1234567890abcdef12345678"
        );
        std::process::exit(1);
    }

    let wallet_address: Address = args[1].parse()?;

    info!(wallet = %wallet_address, chain = "Polygon Mainnet (137)", "checking approvals");

    let provider = ProviderBuilder::new().connect(RPC_URL).await?;

    let config = contract_config(POLYGON, false).unwrap();
    let neg_risk_config = contract_config(POLYGON, true).unwrap();

    let usdc = IERC20::new(USDC_ADDRESS, provider.clone());
    let ctf = IERC1155::new(config.conditional_tokens, provider.clone());

    // All contracts that need approval for full CLOB trading
    let mut targets: Vec<(&str, Address)> = vec![
        ("CTF Exchange", config.exchange),
        ("Neg Risk CTF Exchange", neg_risk_config.exchange),
    ];

    if let Some(adapter) = neg_risk_config.neg_risk_adapter {
        targets.push(("Neg Risk Adapter", adapter));
    }

    let mut all_approved = true;

    for (name, target) in &targets {
        let usdc_result = usdc.allowance(wallet_address, *target).call().await;
        let ctf_result = ctf.isApprovedForAll(wallet_address, *target).call().await;

        match (&usdc_result, &ctf_result) {
            (Ok(usdc_allowance), Ok(ctf_approved)) => {
                let usdc_ok = *usdc_allowance > U256::ZERO;
                let ctf_ok = *ctf_approved;

                if !usdc_ok || !ctf_ok {
                    all_approved = false;
                }

                info!(
                    contract = name,
                    address = %target,
                    usdc_allowance = %format_allowance(*usdc_allowance),
                    usdc_approved = usdc_ok,
                    ctf_approved = ctf_ok,
                );
            }
            (Err(e), _) => {
                debug!(contract = name, error = %e, "failed to check USDC allowance");
                all_approved = false;
            }
            (_, Err(e)) => {
                debug!(contract = name, error = %e, "failed to check CTF approval");
                all_approved = false;
            }
        }
    }

    if all_approved {
        info!(status = "ready", "all contracts properly approved");
    } else {
        info!(
            status = "incomplete",
            "some approvals missing - run: cargo run --example approvals"
        );
    }

    Ok(())
}

fn format_allowance(allowance: U256) -> String {
    if allowance == U256::MAX {
        "MAX (unlimited)".to_owned()
    } else if allowance == U256::ZERO {
        "0".to_owned()
    } else {
        // USDC has 6 decimals
        let usdc_decimals = U256::from(1_000_000);
        let whole = allowance / usdc_decimals;
        format!("{whole} USDC")
    }
}