#![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")]
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());
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 {
let usdc_decimals = U256::from(1_000_000);
let whole = allowance / usdc_decimals;
format!("{whole} USDC")
}
}