diff --git a/src/auth.rs b/src/auth.rs index a5cf34d..1e3cf3f 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,11 +1,12 @@ use std::str::FromStr; +use alloy::primitives::Address; use alloy::providers::ProviderBuilder; use anyhow::{Context, Result}; use polymarket_client_sdk::auth::state::Authenticated; use polymarket_client_sdk::auth::{LocalSigner, Normal, Signer as _}; use polymarket_client_sdk::clob::types::SignatureType; -use polymarket_client_sdk::{POLYGON, clob}; +use polymarket_client_sdk::{POLYGON, clob, derive_proxy_wallet}; use crate::config; @@ -77,6 +78,29 @@ pub async fn create_provider( .context("Failed to connect to Polygon RPC with wallet") } +/// Resolve the wallet address that owns positions and allowances. +/// +/// For `eoa` signature type, returns the EOA address directly. +/// For `proxy`, returns the derived proxy wallet address. +/// For `gnosis-safe`, returns the derived Gnosis Safe address. +pub fn resolve_wallet_address( + private_key: Option<&str>, + signature_type_flag: Option<&str>, +) -> Result
{ + let signer = resolve_signer(private_key)?; + let eoa = polymarket_client_sdk::auth::Signer::address(&signer); + let sig_type = parse_signature_type(&config::resolve_signature_type(signature_type_flag)?); + + match sig_type { + SignatureType::Eoa => Ok(eoa), + SignatureType::Proxy => derive_proxy_wallet(eoa, POLYGON) + .context("Failed to derive proxy wallet address for Polygon"), + SignatureType::GnosisSafe => polymarket_client_sdk::derive_safe_wallet(eoa, POLYGON) + .context("Failed to derive Gnosis Safe wallet address for Polygon"), + _ => Ok(eoa), + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/commands/approve.rs b/src/commands/approve.rs index b52dc0c..67af0fa 100644 --- a/src/commands/approve.rs +++ b/src/commands/approve.rs @@ -1,12 +1,17 @@ #![allow(clippy::exhaustive_enums, reason = "Generated by sol! macro")] #![allow(clippy::exhaustive_structs, reason = "Generated by sol! macro")] -use alloy::primitives::U256; +use alloy::primitives::{Bytes, U256}; use alloy::sol; +use alloy::sol_types::SolCall; use anyhow::{Context, Result}; use clap::{Args, Subcommand}; +use polymarket_client_sdk::auth::Signer as _; +use polymarket_client_sdk::clob::types::SignatureType; use polymarket_client_sdk::types::{Address, address}; -use polymarket_client_sdk::{POLYGON, contract_config}; +use polymarket_client_sdk::{ + POLYGON, contract_config, derive_proxy_wallet, derive_safe_wallet, wallet_contract_config, +}; use crate::auth; use crate::output::OutputFormat; @@ -27,6 +32,17 @@ sol! { function setApprovalForAll(address operator, bool approved) external; function isApprovedForAll(address account, address operator) external view returns (bool); } + + struct ProxyCall { + address to; + uint256 value; + bytes data; + } + + #[sol(rpc)] + interface IProxyWalletFactory { + function proxy(ProxyCall[] memory calls) external payable returns (bytes[] memory); + } } #[derive(Args)] @@ -81,23 +97,26 @@ pub async fn execute( args: ApproveArgs, output: OutputFormat, private_key: Option<&str>, + signature_type: Option<&str>, ) -> Result<()> { match args.command { - ApproveCommand::Check { address } => check(address, private_key, output).await, - ApproveCommand::Set => set(private_key, output).await, + ApproveCommand::Check { address } => { + check(address, private_key, signature_type, output).await + } + ApproveCommand::Set => set(private_key, signature_type, output).await, } } async fn check( address_arg: Option, private_key: Option<&str>, + signature_type: Option<&str>, output: OutputFormat, ) -> Result<()> { let owner: Address = if let Some(addr) = address_arg { addr } else { - let signer = auth::resolve_signer(private_key)?; - polymarket_client_sdk::auth::Signer::address(&signer) + auth::resolve_wallet_address(private_key, signature_type)? }; let provider = auth::create_readonly_provider().await?; @@ -135,70 +154,163 @@ async fn check( print_approval_status(&statuses, &output) } -async fn set(private_key: Option<&str>, output: OutputFormat) -> Result<()> { - let provider = auth::create_provider(private_key).await?; - let config = contract_config(POLYGON, false).context("No contract config for Polygon")?; +async fn set( + private_key: Option<&str>, + signature_type: Option<&str>, + output: OutputFormat, +) -> Result<()> { + let signer = auth::resolve_signer(private_key)?; + let eoa = signer.address(); + let wallet = auth::resolve_wallet_address(private_key, signature_type)?; + let sig_type = if wallet == eoa { + SignatureType::Eoa + } else if derive_proxy_wallet(eoa, POLYGON) == Some(wallet) { + SignatureType::Proxy + } else if derive_safe_wallet(eoa, POLYGON) == Some(wallet) { + SignatureType::GnosisSafe + } else { + anyhow::bail!("Unable to determine approval wallet type for {wallet}"); + }; - let usdc = IERC20::new(USDC_ADDRESS, provider.clone()); - let ctf = IERC1155::new(config.conditional_tokens, provider.clone()); + if sig_type == SignatureType::GnosisSafe { + anyhow::bail!( + "Gnosis Safe approvals must be submitted through your Safe wallet interface.\n\ + Use `approve check --signature-type gnosis-safe` to verify allowances on your Safe address." + ); + } + + let is_proxy = sig_type == SignatureType::Proxy; + + let provider = auth::create_provider(private_key).await?; + let ctf_config = contract_config(POLYGON, false).context("No contract config for Polygon")?; let targets = approval_targets()?; let total = targets.len() * 2; if matches!(output, OutputFormat::Table) { - println!("Approving contracts...\n"); + if is_proxy { + println!("Approving contracts via proxy wallet...\n"); + } else { + println!("Approving contracts...\n"); + } } let mut results: Vec