Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,83 @@
use std::str::FromStr;

use alloy::primitives::{Bytes, U256};
use alloy::providers::ProviderBuilder;
use alloy::sol;
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::types::Address;
use polymarket_client_sdk::{POLYGON, clob, derive_proxy_wallet};

use crate::config;

pub const RPC_URL: &str = "https://polygon.drpc.org";

sol! {
#[allow(clippy::exhaustive_structs)]
#[sol(rpc)]
interface IProxyWallet {
struct ProxyCall {
uint8 typeCode;
address to;
uint256 value;
bytes data;
}

function proxy(ProxyCall[] memory calls) external payable returns (bytes[] memory);
}
}

/// Returns `true` when the resolved signature type is proxy mode.
pub fn is_proxy_mode(signature_type: Option<&str>) -> bool {
config::resolve_signature_type(signature_type) == config::DEFAULT_SIGNATURE_TYPE
}

/// Derives the proxy wallet address for the configured private key.
/// Returns `None` when not in proxy mode or when derivation fails.
pub fn resolve_proxy_address(
private_key: Option<&str>,
signature_type: Option<&str>,
) -> Result<Option<Address>> {
if !is_proxy_mode(signature_type) {
return Ok(None);
}
let signer = resolve_signer(private_key)?;
let eoa = polymarket_client_sdk::auth::Signer::address(&signer);
let proxy = derive_proxy_wallet(eoa, POLYGON)
.ok_or_else(|| anyhow::anyhow!("Could not derive proxy wallet for {eoa}"))?;
Ok(Some(proxy))
}

/// Sends a transaction through the proxy wallet's `proxy` function.
///
/// Wraps the call in a single-element `ProxyCall` array with `typeCode = 1`
/// (CALL) and `value = 0`, matching the on-chain ProxyWallet contract at
/// <https://github.com/Polymarket/proxy-factories>.
pub async fn proxy_exec(
provider: &(impl alloy::providers::Provider + Clone),
proxy_address: Address,
target: Address,
calldata: Bytes,
) -> Result<alloy::primitives::B256> {
let proxy = IProxyWallet::new(proxy_address, provider);
let call = IProxyWallet::ProxyCall {
typeCode: 1, // CallType.CALL
to: target,
value: U256::ZERO,
data: calldata,
};
proxy
.proxy(vec![call])
.send()
.await
.context("Failed to send proxy transaction")?
.watch()
.await
.context("Failed to confirm proxy transaction")
}

fn parse_signature_type(s: &str) -> SignatureType {
match s {
config::DEFAULT_SIGNATURE_TYPE => SignatureType::Proxy,
Expand Down
108 changes: 80 additions & 28 deletions src/commands/approve.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#![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 anyhow::{Context, Result};
use clap::{Args, Subcommand};
use polymarket_client_sdk::auth::Signer as _;
use polymarket_client_sdk::types::{Address, address};
use polymarket_client_sdk::{POLYGON, contract_config};
use polymarket_client_sdk::{POLYGON, contract_config, derive_proxy_wallet};

use crate::auth;
use crate::output::OutputFormat;
Expand Down Expand Up @@ -80,23 +81,33 @@ 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.as_deref(), private_key, output).await,
ApproveCommand::Set => set(private_key, output).await,
ApproveCommand::Check { address } => {
check(address.as_deref(), private_key, signature_type, output).await
}
ApproveCommand::Set => set(private_key, signature_type, output).await,
}
}

async fn check(
address_arg: Option<&str>,
private_key: Option<&str>,
signature_type: Option<&str>,
output: OutputFormat,
) -> Result<()> {
let owner: Address = if let Some(addr) = address_arg {
super::parse_address(addr)?
} else {
let signer = auth::resolve_signer(private_key)?;
polymarket_client_sdk::auth::Signer::address(&signer)
let eoa = signer.address();
if auth::is_proxy_mode(signature_type) {
derive_proxy_wallet(eoa, POLYGON)
.ok_or_else(|| anyhow::anyhow!("Could not derive proxy wallet for {eoa}"))?
} else {
eoa
}
};

let provider = auth::create_readonly_provider().await?;
Expand Down Expand Up @@ -134,18 +145,24 @@ async fn check(
print_approval_status(&statuses, &output)
}

async fn set(private_key: Option<&str>, output: OutputFormat) -> Result<()> {
async fn set(
private_key: Option<&str>,
signature_type: Option<&str>,
output: OutputFormat,
) -> Result<()> {
let proxy_addr = auth::resolve_proxy_address(private_key, signature_type)?;
let provider = auth::create_provider(private_key).await?;
let config = contract_config(POLYGON, false).context("No contract config for Polygon")?;

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

let targets = approval_targets()?;
let total = targets.len() * 2;

if matches!(output, OutputFormat::Table) {
println!("Approving contracts...\n");
if let Some(proxy) = proxy_addr {
println!("Approving contracts via proxy {proxy}...\n");
} else {
println!("Approving contracts...\n");
}
}

let mut results: Vec<serde_json::Value> = Vec::new();
Expand All @@ -154,17 +171,33 @@ async fn set(private_key: Option<&str>, output: OutputFormat) -> Result<()> {
for target in &targets {
step += 1;
let label = format!("USDC \u{2192} {}", target.name);
let tx_hash = usdc
.approve(target.address, U256::MAX)
.send()
.await
.context(format!("Failed to send USDC approval for {}", target.name))?
.watch()

let tx_hash = if let Some(proxy) = proxy_addr {
let calldata = IERC20::approveCall {
spender: target.address,
value: U256::MAX,
};
auth::proxy_exec(
&provider,
proxy,
USDC_ADDRESS,
Bytes::from(alloy::sol_types::SolCall::abi_encode(&calldata)),
)
.await
.context(format!(
"Failed to confirm USDC approval for {}",
target.name
))?;
.context(format!("Failed to approve USDC for {} via proxy", target.name))?
} else {
let usdc = IERC20::new(USDC_ADDRESS, &provider);
usdc.approve(target.address, U256::MAX)
.send()
.await
.context(format!("Failed to send USDC approval for {}", target.name))?
.watch()
.await
.context(format!(
"Failed to confirm USDC approval for {}",
target.name
))?
};

match output {
OutputFormat::Table => print_tx_result(step, total, &label, tx_hash),
Expand All @@ -178,17 +211,36 @@ async fn set(private_key: Option<&str>, output: OutputFormat) -> Result<()> {

step += 1;
let label = format!("CTF \u{2192} {}", target.name);
let tx_hash = ctf
.setApprovalForAll(target.address, true)
.send()
.await
.context(format!("Failed to send CTF approval for {}", target.name))?
.watch()

let tx_hash = if let Some(proxy) = proxy_addr {
let calldata = IERC1155::setApprovalForAllCall {
operator: target.address,
approved: true,
};
auth::proxy_exec(
&provider,
proxy,
config.conditional_tokens,
Bytes::from(alloy::sol_types::SolCall::abi_encode(&calldata)),
)
.await
.context(format!(
"Failed to confirm CTF approval for {}",
"Failed to approve CTF for {} via proxy",
target.name
))?;
))?
} else {
let ctf = IERC1155::new(config.conditional_tokens, &provider);
ctf.setApprovalForAll(target.address, true)
.send()
.await
.context(format!("Failed to send CTF approval for {}", target.name))?
.watch()
.await
.context(format!(
"Failed to confirm CTF approval for {}",
target.name
))?
};

match output {
OutputFormat::Table => print_tx_result(step, total, &label, tx_hash),
Expand Down
Loading