#![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")]
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::types::{Address, address};
use polymarket_client_sdk::{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<String> = 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();
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));
}
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<P: alloy::providers::Provider>(
token: &IERC20::IERC20Instance<P>,
owner: Address,
spender: Address,
) -> anyhow::Result<U256> {
let allowance = token.allowance(owner, spender).call().await?;
Ok(allowance)
}
async fn check_approval_for_all<P: alloy::providers::Provider>(
ctf: &IERC1155::IERC1155Instance<P>,
account: Address,
operator: Address,
) -> anyhow::Result<bool> {
let approved = ctf.isApprovedForAll(account, operator).call().await?;
Ok(approved)
}
async fn approve<P: alloy::providers::Provider>(
usdc: &IERC20::IERC20Instance<P>,
spender: Address,
amount: U256,
) -> anyhow::Result<alloy::primitives::FixedBytes<32>> {
let tx_hash = usdc.approve(spender, amount).send().await?.watch().await?;
Ok(tx_hash)
}
async fn set_approval_for_all<P: alloy::providers::Provider>(
ctf: &IERC1155::IERC1155Instance<P>,
operator: Address,
approved: bool,
) -> anyhow::Result<alloy::primitives::FixedBytes<32>> {
let tx_hash = ctf
.setApprovalForAll(operator, approved)
.send()
.await?
.watch()
.await?;
Ok(tx_hash)
}