#![allow(clippy::exhaustive_enums, reason = "Generated by sol! macro")] #![allow(clippy::exhaustive_structs, reason = "Generated by sol! macro")] #![allow(clippy::unwrap_used, reason = "Examples use unwrap for brevity")] //! Token approval example for Polymarket CLOB trading. //! //! This example demonstrates how to set the required token allowances for trading on Polymarket. //! You must approve three contracts: //! //! 1. **CTF Exchange** (`config.exchange`) - Standard market trading //! 2. **Neg Risk CTF Exchange** (`neg_risk_config.exchange`) - Neg-risk market trading //! 3. **Neg Risk Adapter** (`neg_risk_config.neg_risk_adapter`) - Token minting/splitting for neg-risk //! //! Each contract needs two approvals: //! - ERC-20 approval for USDC (collateral token) //! - ERC-1155 approval for Conditional Tokens (outcome tokens) //! //! You only need to run these approvals once per wallet. //! //! Run with tracing enabled: //! ```sh //! RUST_LOG=info,hyper_util=off,hyper=off,reqwest=off,h2=off,rustls=off cargo run --example approvals --all-features //! ``` //! //! Dry run (no transactions executed): //! ```sh //! RUST_LOG=info cargo run --example approvals --all-features -- --dry-run //! ``` //! //! Optionally log to a file: //! ```sh //! LOG_FILE=approvals.log RUST_LOG=info,hyper_util=off,hyper=off,reqwest=off,h2=off,rustls=off cargo run --example approvals --all-features //! ``` use std::env; use std::fs::File; use std::str::FromStr as _; use alloy::primitives::U256; use alloy::providers::ProviderBuilder; use alloy::signers::Signer as _; use alloy::signers::local::LocalSigner; use alloy::sol; use polymarket_client_sdk_v2::types::{Address, address}; use polymarket_client_sdk_v2::{POLYGON, PRIVATE_KEY_VAR, contract_config}; use tracing::{error, 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"); const TOKEN_TO_APPROVE: Address = USDC_ADDRESS; sol! { #[sol(rpc)] interface IERC20 { function approve(address spender, uint256 value) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); } #[sol(rpc)] interface IERC1155 { function setApprovalForAll(address operator, bool approved) external; function isApprovedForAll(address account, address operator) external view returns (bool); } } #[tokio::main] async fn main() -> anyhow::Result<()> { if let Ok(path) = 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 = env::args().collect(); let dry_run = args.iter().any(|arg| arg == "--dry-run"); let chain = POLYGON; let config = contract_config(chain, false).unwrap(); let neg_risk_config = contract_config(chain, true).unwrap(); // Collect all contracts that need approval let mut targets: Vec<(&str, Address)> = vec![ ("CTF Exchange", config.exchange), ("Neg Risk CTF Exchange", neg_risk_config.exchange), ]; // Add the Neg Risk Adapter if available if let Some(adapter) = neg_risk_config.neg_risk_adapter { targets.push(("Neg Risk Adapter", adapter)); } if dry_run { info!(mode = "dry_run", "showing approvals without executing"); for (name, target) in &targets { info!(contract = name, address = %target, "would receive approval"); } info!(total = targets.len(), "contracts would be approved"); return Ok(()); } let private_key = env::var(PRIVATE_KEY_VAR).expect("Need a private key"); let signer = LocalSigner::from_str(&private_key)?.with_chain_id(Some(chain)); let provider = ProviderBuilder::new() .wallet(signer.clone()) .connect(RPC_URL) .await?; let owner = signer.address(); info!(address = %owner, "wallet loaded"); let token = IERC20::new(TOKEN_TO_APPROVE, provider.clone()); let ctf = IERC1155::new(config.conditional_tokens, provider.clone()); info!(phase = "checking", "querying current allowances"); for (name, target) in &targets { match check_allowance(&token, owner, *target).await { Ok(allowance) => info!(contract = name, usdc_allowance = %allowance), Err(e) => error!(contract = name, error = ?e, "failed to check USDC allowance"), } match check_approval_for_all(&ctf, owner, *target).await { Ok(approved) => info!(contract = name, ctf_approved = approved), Err(e) => error!(contract = name, error = ?e, "failed to check CTF approval"), } } info!(phase = "approving", "setting approvals"); for (name, target) in &targets { info!(contract = name, address = %target, "approving"); match approve(&token, *target, U256::MAX).await { Ok(tx_hash) => info!(contract = name, tx = %tx_hash, "USDC approved"), Err(e) => error!(contract = name, error = ?e, "USDC approve failed"), } match set_approval_for_all(&ctf, *target, true).await { Ok(tx_hash) => info!(contract = name, tx = %tx_hash, "CTF approved"), Err(e) => error!(contract = name, error = ?e, "CTF setApprovalForAll failed"), } } info!(phase = "verifying", "confirming approvals"); for (name, target) in &targets { match check_allowance(&token, owner, *target).await { Ok(allowance) => info!(contract = name, usdc_allowance = %allowance, "verified"), Err(e) => error!(contract = name, error = ?e, "verification failed"), } match check_approval_for_all(&ctf, owner, *target).await { Ok(approved) => info!(contract = name, ctf_approved = approved, "verified"), Err(e) => error!(contract = name, error = ?e, "verification failed"), } } info!("all approvals complete"); Ok(()) } async fn check_allowance( token: &IERC20::IERC20Instance

, owner: Address, spender: Address, ) -> anyhow::Result { let allowance = token.allowance(owner, spender).call().await?; Ok(allowance) } async fn check_approval_for_all( ctf: &IERC1155::IERC1155Instance

, account: Address, operator: Address, ) -> anyhow::Result { let approved = ctf.isApprovedForAll(account, operator).call().await?; Ok(approved) } async fn approve( usdc: &IERC20::IERC20Instance

, spender: Address, amount: U256, ) -> anyhow::Result> { let tx_hash = usdc.approve(spender, amount).send().await?.watch().await?; Ok(tx_hash) } async fn set_approval_for_all( ctf: &IERC1155::IERC1155Instance

, operator: Address, approved: bool, ) -> anyhow::Result> { let tx_hash = ctf .setApprovalForAll(operator, approved) .send() .await? .watch() .await?; Ok(tx_hash) }