From cf28587517aba7bfaffac6b6924e7224cfbdbb83 Mon Sep 17 00:00:00 2001 From: Anurag Sharan <34830688+sharananurag998@users.noreply.github.com> Date: Sat, 18 Jul 2026 12:18:22 +0530 Subject: [PATCH 1/3] Add POLY_1271 deposit wallet support --- README.md | 20 +++++++- src/auth.rs | 44 ++++++++++++----- src/commands/approve.rs | 7 ++- src/commands/clob.rs | 73 +++++++++++++++++---------- src/commands/mod.rs | 15 +++++- src/commands/setup.rs | 2 +- src/commands/wallet.rs | 78 ++++++++++++++++++++++------- src/config.rs | 104 +++++++++++++++++++++++++++++++++++++-- src/main.rs | 50 +++++++++++++++++-- tests/cli_integration.rs | 10 ++++ 10 files changed, 333 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 7575d59..7a45a81 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,8 @@ The config file (`~/.config/polymarket/config.json`): { "private_key": "0x...", "chain_id": 137, - "signature_type": "proxy" + "signature_type": "poly-1271", + "funder": "0xYOUR_POLYMARKET_DEPOSIT_WALLET" } ``` @@ -87,8 +88,25 @@ The config file (`~/.config/polymarket/config.json`): - `proxy` (default) — uses Polymarket's proxy wallet system - `eoa` — signs directly with your key - `gnosis-safe` — for multisig wallets +- `poly-1271` — for Polymarket deposit wallets; requires an explicit funder address Override per-command with `--signature-type eoa` or via `POLYMARKET_SIGNATURE_TYPE`. +Override the funding wallet with `--funder 0x...` or `POLYMARKET_FUNDER`. + +For an email/social-login Polymarket account backed by a deposit wallet: + +```bash +polymarket wallet import 0xSIGNER_KEY \ + --signature-type poly-1271 \ + --funder 0xPOLYMARKET_DEPOSIT_WALLET + +polymarket clob balance --asset-type collateral +``` + +`poly-1271` is supported for CLOB V2 authentication, balances, orders, and +trades. Direct on-chain mutations (`approve set` and CTF split/merge/redeem) +are rejected for deposit wallets; perform those operations in the Polymarket +app. `approve check` remains available and checks the configured funder. ### What Needs a Wallet diff --git a/src/auth.rs b/src/auth.rs index 66c1a60..d396f94 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -20,11 +20,13 @@ fn rpc_url() -> String { std::env::var("POLYMARKET_RPC_URL").unwrap_or_else(|_| DEFAULT_RPC_URL.to_string()) } -fn parse_signature_type(s: &str) -> SignatureType { +fn parse_signature_type(s: &str) -> Result { match s { - config::DEFAULT_SIGNATURE_TYPE => SignatureType::Proxy, - "gnosis-safe" => SignatureType::GnosisSafe, - _ => SignatureType::Eoa, + config::DEFAULT_SIGNATURE_TYPE => Ok(SignatureType::Proxy), + "gnosis-safe" => Ok(SignatureType::GnosisSafe), + config::POLY_1271_SIGNATURE_TYPE => Ok(SignatureType::Poly1271), + "eoa" => Ok(SignatureType::Eoa), + _ => Err(anyhow::anyhow!("Unsupported signature type: {s}")), } } @@ -41,20 +43,28 @@ pub fn resolve_signer( pub async fn authenticated_clob_client( private_key: Option<&str>, signature_type_flag: Option<&str>, + funder_flag: Option<&str>, ) -> Result>> { let signer = resolve_signer(private_key)?; - authenticate_with_signer(&signer, signature_type_flag).await + authenticate_with_signer(&signer, signature_type_flag, funder_flag).await } pub async fn authenticate_with_signer( signer: &(impl polymarket_client_sdk_v2::auth::Signer + Sync), signature_type_flag: Option<&str>, + funder_flag: Option<&str>, ) -> Result>> { - let sig_type = parse_signature_type(&config::resolve_signature_type(signature_type_flag)?); + let sig_type = parse_signature_type(&config::resolve_signature_type(signature_type_flag)?)?; + let funder = config::resolve_funder(funder_flag)?; - unauthenticated_clob_client()? + let mut builder = unauthenticated_clob_client()? .authentication_builder(signer) - .signature_type(sig_type) + .signature_type(sig_type); + if let Some(funder) = funder { + builder = builder.funder(funder); + } + + builder .authenticate() .await .context("Failed to authenticate with Polymarket CLOB") @@ -93,24 +103,32 @@ mod tests { #[test] fn parse_signature_type_proxy() { - assert_eq!(parse_signature_type("proxy"), SignatureType::Proxy); + assert_eq!(parse_signature_type("proxy").unwrap(), SignatureType::Proxy); } #[test] fn parse_signature_type_gnosis_safe() { assert_eq!( - parse_signature_type("gnosis-safe"), + parse_signature_type("gnosis-safe").unwrap(), SignatureType::GnosisSafe ); } #[test] fn parse_signature_type_eoa() { - assert_eq!(parse_signature_type("eoa"), SignatureType::Eoa); + assert_eq!(parse_signature_type("eoa").unwrap(), SignatureType::Eoa); + } + + #[test] + fn parse_signature_type_poly_1271() { + assert_eq!( + parse_signature_type("poly-1271").unwrap(), + SignatureType::Poly1271 + ); } #[test] - fn parse_signature_type_unknown_defaults_to_eoa() { - assert_eq!(parse_signature_type("unknown"), SignatureType::Eoa); + fn parse_signature_type_unknown_is_rejected() { + assert!(parse_signature_type("unknown").is_err()); } } diff --git a/src/commands/approve.rs b/src/commands/approve.rs index 6de94be..56d3b70 100644 --- a/src/commands/approve.rs +++ b/src/commands/approve.rs @@ -101,10 +101,11 @@ pub async fn execute( output: OutputFormat, private_key: Option<&str>, signature_type: Option<&str>, + funder: Option<&str>, ) -> Result<()> { match args.command { ApproveCommand::Check { address } => { - check(address, private_key, signature_type, output).await + check(address, private_key, signature_type, funder, output).await } ApproveCommand::Set => set(private_key, signature_type, output).await, } @@ -114,10 +115,14 @@ async fn check( address_arg: Option
, private_key: Option<&str>, signature_type: Option<&str>, + funder: Option<&str>, output: OutputFormat, ) -> Result<()> { let owner: Address = if let Some(addr) = address_arg { addr + } else if proxy::is_poly_1271_mode(signature_type)? { + crate::config::resolve_funder(funder)? + .context("--funder is required when using signature type poly-1271")? } else if proxy::is_proxy_mode(signature_type)? { proxy::derive_proxy_address(private_key)? } else { diff --git a/src/commands/clob.rs b/src/commands/clob.rs index b073f88..7af92bc 100644 --- a/src/commands/clob.rs +++ b/src/commands/clob.rs @@ -490,6 +490,7 @@ pub async fn execute( output: OutputFormat, private_key: Option<&str>, signature_type: Option<&str>, + funder: Option<&str>, ) -> Result<()> { // Unauthenticated client — cheap to construct, used by read commands and CreateApiKey. let unauth = auth::unauthenticated_clob_client()?; @@ -668,7 +669,7 @@ pub async fn execute( post_only, } => { let signer = auth::resolve_signer(private_key)?; - let client = auth::authenticate_with_signer(&signer, signature_type).await?; + let client = auth::authenticate_with_signer(&signer, signature_type, funder).await?; let price_dec = Decimal::from_str(&price).map_err(|_| anyhow::anyhow!("Invalid price: {price}"))?; @@ -701,7 +702,7 @@ pub async fn execute( order_type, } => { let signer = auth::resolve_signer(private_key)?; - let client = auth::authenticate_with_signer(&signer, signature_type).await?; + let client = auth::authenticate_with_signer(&signer, signature_type, funder).await?; let token_ids = parse_token_ids(&tokens)?; let price_strs: Vec<&str> = prices.split(',').map(str::trim).collect(); @@ -748,7 +749,7 @@ pub async fn execute( order_type, } => { let signer = auth::resolve_signer(private_key)?; - let client = auth::authenticate_with_signer(&signer, signature_type).await?; + let client = auth::authenticate_with_signer(&signer, signature_type, funder).await?; let amount_dec = Decimal::from_str(&amount) .map_err(|_| anyhow::anyhow!("Invalid amount: {amount}"))?; @@ -781,7 +782,8 @@ pub async fn execute( asset, cursor, } => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let request = OrdersRequest::builder() .maybe_market(market) .maybe_asset_id(asset) @@ -791,32 +793,37 @@ pub async fn execute( } ClobCommand::Order { order_id } => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let result = client.order(&order_id).await?; print_order_detail(&result, output)?; } ClobCommand::Cancel { order_id } => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let result = client.cancel_order(&order_id).await?; print_cancel_result(&result, output)?; } ClobCommand::CancelOrders { order_ids } => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let ids: Vec<&str> = order_ids.split(',').map(str::trim).collect(); let result = client.cancel_orders(&ids).await?; print_cancel_result(&result, output)?; } ClobCommand::CancelAll => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let result = client.cancel_all_orders().await?; print_cancel_result(&result, output)?; } ClobCommand::CancelMarket { market, asset } => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let request = CancelMarketOrderRequest::builder() .maybe_market(market) .maybe_asset_id(asset) @@ -830,7 +837,8 @@ pub async fn execute( asset, cursor, } => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let request = TradesRequest::builder() .maybe_market(market) .maybe_asset_id(asset) @@ -840,7 +848,8 @@ pub async fn execute( } ClobCommand::Balance { asset_type, token } => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let is_collateral = matches!(asset_type, CliAssetType::Collateral); let request = BalanceAllowanceRequest::builder() .asset_type(AssetType::from(asset_type)) @@ -851,7 +860,8 @@ pub async fn execute( } ClobCommand::UpdateBalance { asset_type, token } => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let request = BalanceAllowanceRequest::builder() .asset_type(AssetType::from(asset_type)) .maybe_token_id(token) @@ -866,13 +876,15 @@ pub async fn execute( } ClobCommand::Notifications => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let result = client.notifications().await?; print_notifications(&result, output)?; } ClobCommand::DeleteNotifications { ids } => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let notification_ids: Vec = ids.split(',').map(|s| s.trim().to_string()).collect(); let request = DeleteNotificationsRequest::builder() @@ -889,7 +901,8 @@ pub async fn execute( // ── Authenticated reward commands ──────────────────────────────── ClobCommand::Rewards { date, cursor } => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let result = client .earnings_for_user_for_day(parse_date(&date)?, cursor) .await?; @@ -897,7 +910,8 @@ pub async fn execute( } ClobCommand::Earnings { date } => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let result = client .total_earnings_for_user_for_day(parse_date(&date)?) .await?; @@ -905,7 +919,8 @@ pub async fn execute( } ClobCommand::EarningsMarkets { date, cursor } => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let request = UserRewardsEarningRequest::builder() .date(parse_date(&date)?) .build(); @@ -916,13 +931,15 @@ pub async fn execute( } ClobCommand::RewardPercentages => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let result = client.reward_percentages().await?; print_reward_percentages(&result, output)?; } ClobCommand::CurrentRewards { cursor } => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let result = client.current_rewards(cursor).await?; print_current_rewards(&result, output)?; } @@ -931,19 +948,22 @@ pub async fn execute( condition_id, cursor, } => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let result = client.raw_rewards_for_market(&condition_id, cursor).await?; print_market_reward(&result, output)?; } ClobCommand::OrderScoring { order_id } => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let result = client.is_order_scoring(&order_id).await?; print_order_scoring(&result, output)?; } ClobCommand::OrdersScoring { order_ids } => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let ids: Vec<&str> = order_ids.split(',').map(str::trim).collect(); let result = client.are_orders_scoring(&ids).await?; print_orders_scoring(&result, output)?; @@ -957,19 +977,22 @@ pub async fn execute( } ClobCommand::ApiKeys => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let result = client.api_keys().await?; print_api_keys(&result, output)?; } ClobCommand::DeleteApiKey => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let result = client.delete_api_key().await?; print_delete_api_key(&result, output)?; } ClobCommand::AccountStatus => { - let client = auth::authenticated_clob_client(private_key, signature_type).await?; + let client = + auth::authenticated_clob_client(private_key, signature_type, funder).await?; let result = client.closed_only_mode().await?; print_account_status(&result, output)?; } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 9885365..5f371ef 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -19,7 +19,7 @@ pub(crate) const NEG_RISK_CTF_COLLATERAL_ADAPTER: Address = pub(crate) mod proxy { use alloy::primitives::U256; use alloy::sol; - use anyhow::{Context, Result}; + use anyhow::{Context, Result, bail}; use polymarket_client_sdk_v2::POLYGON; use polymarket_client_sdk_v2::types::{Address, B256}; @@ -45,7 +45,18 @@ pub(crate) mod proxy { polymarket_client_sdk_v2::types::address!("0xaB45c5A4B0c941a2F231C04C3f49182e1A254052"); pub(crate) fn is_proxy_mode(signature_type: Option<&str>) -> Result { - Ok(crate::config::resolve_signature_type(signature_type)? == "proxy") + let signature_type = crate::config::resolve_signature_type(signature_type)?; + if signature_type == crate::config::POLY_1271_SIGNATURE_TYPE { + bail!( + "On-chain mutations are not supported for poly-1271 deposit wallets; use the Polymarket app for approvals, splits, merges, and redemptions" + ); + } + Ok(signature_type == "proxy") + } + + pub(crate) fn is_poly_1271_mode(signature_type: Option<&str>) -> Result { + Ok(crate::config::resolve_signature_type(signature_type)? + == crate::config::POLY_1271_SIGNATURE_TYPE) } pub(crate) fn derive_proxy_address(private_key: Option<&str>) -> Result
{ diff --git a/src/commands/setup.rs b/src/commands/setup.rs index 0f43ae0..7c811cc 100644 --- a/src/commands/setup.rs +++ b/src/commands/setup.rs @@ -125,7 +125,7 @@ fn setup_wallet() -> Result
{ (address, hex) }; - config::save_wallet(&key_hex, POLYGON, config::DEFAULT_SIGNATURE_TYPE)?; + config::save_wallet(&key_hex, POLYGON, config::DEFAULT_SIGNATURE_TYPE, None)?; if has_key { println!(" ✓ Wallet imported"); diff --git a/src/commands/wallet.rs b/src/commands/wallet.rs index 9be088e..692e9fa 100644 --- a/src/commands/wallet.rs +++ b/src/commands/wallet.rs @@ -22,7 +22,7 @@ pub enum WalletCommand { /// Overwrite existing wallet #[arg(long)] force: bool, - /// Signature type: eoa, proxy (default), or gnosis-safe + /// Signature type: eoa, proxy (default), gnosis-safe, or poly-1271 #[arg(long, default_value = "proxy")] signature_type: String, }, @@ -33,7 +33,7 @@ pub enum WalletCommand { /// Overwrite existing wallet #[arg(long)] force: bool, - /// Signature type: eoa, proxy (default), or gnosis-safe + /// Signature type: eoa, proxy (default), gnosis-safe, or poly-1271 #[arg(long, default_value = "proxy")] signature_type: String, }, @@ -53,19 +53,21 @@ pub fn execute( args: WalletArgs, output: OutputFormat, private_key_flag: Option<&str>, + signature_type_flag: Option<&str>, + funder_flag: Option<&str>, ) -> Result<()> { match args.command { WalletCommand::Create { force, signature_type, - } => cmd_create(output, force, &signature_type), + } => cmd_create(output, force, &signature_type, funder_flag), WalletCommand::Import { key, force, signature_type, - } => cmd_import(&key, output, force, &signature_type), + } => cmd_import(&key, output, force, &signature_type, funder_flag), WalletCommand::Address => cmd_address(output, private_key_flag), - WalletCommand::Show => cmd_show(output, private_key_flag), + WalletCommand::Show => cmd_show(output, private_key_flag, signature_type_flag, funder_flag), WalletCommand::Reset { force } => cmd_reset(output, force), } } @@ -80,16 +82,25 @@ fn guard_overwrite(force: bool) -> Result<()> { Ok(()) } -fn cmd_create(output: OutputFormat, force: bool, signature_type: &str) -> Result<()> { +fn cmd_create( + output: OutputFormat, + force: bool, + signature_type: &str, + funder: Option<&str>, +) -> Result<()> { guard_overwrite(force)?; + let signature_type = config::normalize_signature_type(signature_type)?; let signer = LocalSigner::random().with_chain_id(Some(POLYGON)); let address = signer.address(); let key_hex = format!("{:#x}", signer.to_bytes()); - config::save_wallet(&key_hex, POLYGON, signature_type)?; + config::save_wallet(&key_hex, POLYGON, signature_type, funder)?; let config_path = config::config_path()?; - let proxy_addr = derive_proxy_wallet(address, POLYGON); + let proxy_addr = (signature_type == config::DEFAULT_SIGNATURE_TYPE) + .then(|| derive_proxy_wallet(address, POLYGON)) + .flatten(); + let funder_addr = config::resolve_funder(funder)?.map(|address| address.to_string()); match output { OutputFormat::Json => { @@ -98,6 +109,7 @@ fn cmd_create(output: OutputFormat, force: bool, signature_type: &str) -> Result serde_json::json!({ "address": address.to_string(), "proxy_address": proxy_addr.map(|a| a.to_string()), + "funder": funder_addr, "signature_type": signature_type, "config_path": config_path.display().to_string(), }) @@ -109,6 +121,9 @@ fn cmd_create(output: OutputFormat, force: bool, signature_type: &str) -> Result if let Some(proxy) = proxy_addr { println!("Proxy wallet: {proxy}"); } + if let Some(funder) = funder_addr { + println!("Funder wallet: {funder}"); + } println!("Signature type: {signature_type}"); println!("Config: {}", config_path.display()); println!(); @@ -119,8 +134,15 @@ fn cmd_create(output: OutputFormat, force: bool, signature_type: &str) -> Result Ok(()) } -fn cmd_import(key: &str, output: OutputFormat, force: bool, signature_type: &str) -> Result<()> { +fn cmd_import( + key: &str, + output: OutputFormat, + force: bool, + signature_type: &str, + funder: Option<&str>, +) -> Result<()> { guard_overwrite(force)?; + let signature_type = config::normalize_signature_type(signature_type)?; let signer = LocalSigner::from_str(key) .context("Invalid private key")? @@ -128,9 +150,12 @@ fn cmd_import(key: &str, output: OutputFormat, force: bool, signature_type: &str let address = signer.address(); let key_hex = format!("{:#x}", signer.to_bytes()); - config::save_wallet(&key_hex, POLYGON, signature_type)?; + config::save_wallet(&key_hex, POLYGON, signature_type, funder)?; let config_path = config::config_path()?; - let proxy_addr = derive_proxy_wallet(address, POLYGON); + let proxy_addr = (signature_type == config::DEFAULT_SIGNATURE_TYPE) + .then(|| derive_proxy_wallet(address, POLYGON)) + .flatten(); + let funder_addr = config::resolve_funder(funder)?.map(|address| address.to_string()); match output { OutputFormat::Json => { @@ -139,6 +164,7 @@ fn cmd_import(key: &str, output: OutputFormat, force: bool, signature_type: &str serde_json::json!({ "address": address.to_string(), "proxy_address": proxy_addr.map(|a| a.to_string()), + "funder": funder_addr, "signature_type": signature_type, "config_path": config_path.display().to_string(), }) @@ -150,6 +176,9 @@ fn cmd_import(key: &str, output: OutputFormat, force: bool, signature_type: &str if let Some(proxy) = proxy_addr { println!("Proxy wallet: {proxy}"); } + if let Some(funder) = funder_addr { + println!("Funder wallet: {funder}"); + } println!("Signature type: {signature_type}"); println!("Config: {}", config_path.display()); } @@ -175,16 +204,25 @@ fn cmd_address(output: OutputFormat, private_key_flag: Option<&str>) -> Result<( Ok(()) } -fn cmd_show(output: OutputFormat, private_key_flag: Option<&str>) -> Result<()> { +fn cmd_show( + output: OutputFormat, + private_key_flag: Option<&str>, + signature_type_flag: Option<&str>, + funder_flag: Option<&str>, +) -> Result<()> { let (key, source) = config::resolve_key(private_key_flag)?; let signer = key.as_deref().and_then(|k| LocalSigner::from_str(k).ok()); let address = signer.as_ref().map(|s| s.address().to_string()); - let proxy_addr = signer - .as_ref() - .and_then(|s| derive_proxy_wallet(s.address(), POLYGON)) - .map(|a| a.to_string()); - - let sig_type = config::resolve_signature_type(None)?; + let sig_type = config::resolve_signature_type(signature_type_flag)?; + let proxy_addr = (sig_type == config::DEFAULT_SIGNATURE_TYPE) + .then(|| { + signer + .as_ref() + .and_then(|s| derive_proxy_wallet(s.address(), POLYGON)) + .map(|a| a.to_string()) + }) + .flatten(); + let funder = config::resolve_funder(funder_flag)?.map(|address| address.to_string()); let config_path = config::config_path()?; match output { @@ -194,6 +232,7 @@ fn cmd_show(output: OutputFormat, private_key_flag: Option<&str>) -> Result<()> serde_json::json!({ "address": address, "proxy_address": proxy_addr, + "funder": funder, "signature_type": sig_type, "config_path": config_path.display().to_string(), "source": source.label(), @@ -209,6 +248,9 @@ fn cmd_show(output: OutputFormat, private_key_flag: Option<&str>) -> Result<()> if let Some(proxy) = &proxy_addr { println!("Proxy wallet: {proxy}"); } + if let Some(funder) = &funder { + println!("Funder wallet: {funder}"); + } println!("Signature type: {sig_type}"); println!("Config path: {}", config_path.display()); println!("Key source: {}", source.label()); diff --git a/src/config.rs b/src/config.rs index 60c0179..3664f33 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,12 +1,16 @@ use std::fs; use std::path::PathBuf; +use std::str::FromStr; -use anyhow::{Context, Result}; +use anyhow::{Context, Result, bail}; +use polymarket_client_sdk_v2::types::{Address, address}; use serde::{Deserialize, Serialize}; const ENV_VAR: &str = "POLYMARKET_PRIVATE_KEY"; const SIG_TYPE_ENV_VAR: &str = "POLYMARKET_SIGNATURE_TYPE"; +const FUNDER_ENV_VAR: &str = "POLYMARKET_FUNDER"; pub(crate) const DEFAULT_SIGNATURE_TYPE: &str = "proxy"; +pub(crate) const POLY_1271_SIGNATURE_TYPE: &str = "poly-1271"; pub(crate) const NO_WALLET_MSG: &str = "No wallet configured. Run `polymarket wallet create` or `polymarket wallet import `"; @@ -17,6 +21,8 @@ pub(crate) struct Config { pub chain_id: u64, #[serde(default = "default_signature_type")] pub signature_type: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub funder: Option, } fn default_signature_type() -> String { @@ -78,23 +84,77 @@ pub fn load_config() -> Result> { Ok(Some(config)) } +pub fn normalize_signature_type(signature_type: &str) -> Result<&'static str> { + match signature_type.to_ascii_lowercase().as_str() { + "eoa" | "0" => Ok("eoa"), + "proxy" | "1" => Ok(DEFAULT_SIGNATURE_TYPE), + "gnosis-safe" | "gnosis_safe" | "2" => Ok("gnosis-safe"), + "poly-1271" | "poly1271" | "poly_1271" | "3" => Ok(POLY_1271_SIGNATURE_TYPE), + _ => bail!( + "Invalid signature type '{signature_type}'. Expected eoa, proxy, gnosis-safe, or poly-1271" + ), + } +} + /// Priority: CLI flag > env var > config file > default ("proxy"). pub fn resolve_signature_type(cli_flag: Option<&str>) -> Result { if let Some(st) = cli_flag { - return Ok(st.to_string()); + return Ok(normalize_signature_type(st)?.to_string()); } if let Ok(st) = std::env::var(SIG_TYPE_ENV_VAR) && !st.is_empty() { - return Ok(st); + return Ok(normalize_signature_type(&st)?.to_string()); } if let Some(config) = load_config()? { - return Ok(config.signature_type); + return Ok(normalize_signature_type(&config.signature_type)?.to_string()); } Ok(DEFAULT_SIGNATURE_TYPE.to_string()) } -pub fn save_wallet(key: &str, chain_id: u64, signature_type: &str) -> Result<()> { +/// Priority: CLI flag > env var > config file. +pub fn resolve_funder(cli_flag: Option<&str>) -> Result> { + let value = if let Some(funder) = cli_flag { + Some(funder.to_string()) + } else if let Ok(funder) = std::env::var(FUNDER_ENV_VAR) + && !funder.is_empty() + { + Some(funder) + } else { + load_config()?.and_then(|config| config.funder) + }; + + value + .map(|funder| { + Address::from_str(&funder).with_context(|| format!("Invalid funder address: {funder}")) + }) + .transpose() +} + +pub fn save_wallet( + key: &str, + chain_id: u64, + signature_type: &str, + funder: Option<&str>, +) -> Result<()> { + let signature_type = normalize_signature_type(signature_type)?; + let funder = funder + .map(|value| { + Address::from_str(value) + .with_context(|| format!("Invalid funder address: {value}")) + .map(|address| address.to_string()) + }) + .transpose()?; + + if signature_type == POLY_1271_SIGNATURE_TYPE { + let address = funder + .as_deref() + .context("--funder is required when using signature type poly-1271")?; + if Address::from_str(address)? == address!("0000000000000000000000000000000000000000") { + bail!("The funder address cannot be zero for signature type poly-1271"); + } + } + let dir = config_dir()?; fs::create_dir_all(&dir).context("Failed to create config directory")?; @@ -108,6 +168,7 @@ pub fn save_wallet(key: &str, chain_id: u64, signature_type: &str) -> Result<()> private_key: key.to_string(), chain_id, signature_type: signature_type.to_string(), + funder, }; let json = serde_json::to_string_pretty(&config)?; let path = config_path()?; @@ -222,4 +283,37 @@ mod tests { let result = resolve_signature_type(None).unwrap(); assert!(!result.is_empty()); } + + #[test] + fn normalize_signature_type_accepts_poly_1271_aliases() { + for alias in ["poly-1271", "poly1271", "POLY_1271", "3"] { + assert_eq!( + normalize_signature_type(alias).unwrap(), + POLY_1271_SIGNATURE_TYPE + ); + } + } + + #[test] + fn normalize_signature_type_rejects_unknown_values() { + assert!(normalize_signature_type("unknown").is_err()); + } + + #[test] + fn poly_1271_wallet_requires_funder_before_writing_config() { + let error = save_wallet("unused", 137, POLY_1271_SIGNATURE_TYPE, None).unwrap_err(); + assert!(error.to_string().contains("--funder is required")); + } + + #[test] + fn resolve_funder_reads_env_address() { + let _lock = ENV_LOCK.lock().unwrap(); + unsafe { set(FUNDER_ENV_VAR, "0xd1615A7B6146cDbA40a559eC876A3bcca4050890") }; + let funder = resolve_funder(None).unwrap().unwrap(); + assert_eq!( + funder, + Address::from_str("0xd1615A7B6146cDbA40a559eC876A3bcca4050890").unwrap() + ); + unsafe { unset(FUNDER_ENV_VAR) }; + } } diff --git a/src/main.rs b/src/main.rs index aaf5840..99b67dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,9 +23,13 @@ pub(crate) struct Cli { #[arg(long, global = true)] private_key: Option, - /// Signature type: eoa, proxy, or gnosis-safe + /// Signature type: eoa, proxy, gnosis-safe, or poly-1271 #[arg(long, global = true)] signature_type: Option, + + /// Explicit Polymarket funding wallet (required for poly-1271) + #[arg(long, global = true)] + funder: Option, } #[derive(Subcommand)] @@ -102,6 +106,7 @@ pub(crate) async fn run(cli: Cli) -> anyhow::Result<()> { cli.output, cli.private_key.as_deref(), cli.signature_type.as_deref(), + cli.funder.as_deref(), ) .await } @@ -111,6 +116,7 @@ pub(crate) async fn run(cli: Cli) -> anyhow::Result<()> { cli.output, cli.private_key.as_deref(), cli.signature_type.as_deref(), + cli.funder.as_deref(), ) .await } @@ -125,9 +131,13 @@ pub(crate) async fn run(cli: Cli) -> anyhow::Result<()> { } Commands::Data(args) => commands::data::execute(&data, args, cli.output).await, Commands::Bridge(args) => commands::bridge::execute(&bridge, args, cli.output).await, - Commands::Wallet(args) => { - commands::wallet::execute(args, cli.output, cli.private_key.as_deref()) - } + Commands::Wallet(args) => commands::wallet::execute( + args, + cli.output, + cli.private_key.as_deref(), + cli.signature_type.as_deref(), + cli.funder.as_deref(), + ), Commands::Upgrade => commands::upgrade::execute(), Commands::Status => { let status = gamma.status().await?; @@ -143,3 +153,35 @@ pub(crate) async fn run(cli: Cli) -> anyhow::Result<()> { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn wallet_import_accepts_poly_1271_funder() { + let cli = Cli::try_parse_from([ + "polymarket", + "wallet", + "import", + "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "--signature-type", + "poly-1271", + "--funder", + "0xd1615A7B6146cDbA40a559eC876A3bcca4050890", + ]) + .unwrap(); + + assert_eq!( + cli.funder.as_deref(), + Some("0xd1615A7B6146cDbA40a559eC876A3bcca4050890") + ); + let Commands::Wallet(args) = cli.command else { + panic!("expected wallet command"); + }; + let commands::wallet::WalletCommand::Import { signature_type, .. } = args.command else { + panic!("expected wallet import command"); + }; + assert_eq!(signature_type, "poly-1271"); + } +} diff --git a/tests/cli_integration.rs b/tests/cli_integration.rs index 41d3d11..4b8806c 100644 --- a/tests/cli_integration.rs +++ b/tests/cli_integration.rs @@ -7,9 +7,19 @@ fn polymarket() -> Command { let mut cmd = Command::cargo_bin("polymarket").unwrap(); cmd.env_remove("POLYMARKET_PRIVATE_KEY"); cmd.env_remove("POLYMARKET_SIGNATURE_TYPE"); + cmd.env_remove("POLYMARKET_FUNDER"); cmd } +#[test] +fn help_lists_poly_1271_funder_options() { + polymarket() + .arg("--help") + .assert() + .success() + .stdout(predicate::str::contains("poly-1271").and(predicate::str::contains("--funder"))); +} + #[test] fn help_lists_all_top_level_commands() { polymarket().arg("--help").assert().success().stdout( From 8c044fec4490c7174cd0cb14c877a602640825b8 Mon Sep 17 00:00:00 2001 From: Anurag Sharan <34830688+sharananurag998@users.noreply.github.com> Date: Sat, 18 Jul 2026 12:33:14 +0530 Subject: [PATCH 2/3] Validate POLY_1271 funder configuration --- README.md | 4 +- src/auth.rs | 8 ++- src/commands/approve.rs | 7 ++- src/commands/wallet.rs | 16 +++-- src/config.rs | 129 +++++++++++++++++++++++++++++---------- tests/cli_integration.rs | 34 +++++++++++ 6 files changed, 156 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 7a45a81..146ad8d 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,9 @@ The config file (`~/.config/polymarket/config.json`): - `poly-1271` — for Polymarket deposit wallets; requires an explicit funder address Override per-command with `--signature-type eoa` or via `POLYMARKET_SIGNATURE_TYPE`. -Override the funding wallet with `--funder 0x...` or `POLYMARKET_FUNDER`. +For `poly-1271`, provide the funding wallet with `--funder 0x...` or +`POLYMARKET_FUNDER`. Supplying a funder with another signature type is rejected +to prevent authenticating against the wrong wallet. For an email/social-login Polymarket account backed by a deposit wallet: diff --git a/src/auth.rs b/src/auth.rs index d396f94..4bacd49 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -54,8 +54,12 @@ pub async fn authenticate_with_signer( signature_type_flag: Option<&str>, funder_flag: Option<&str>, ) -> Result>> { - let sig_type = parse_signature_type(&config::resolve_signature_type(signature_type_flag)?)?; - let funder = config::resolve_funder(funder_flag)?; + let signature_type = config::resolve_signature_type(signature_type_flag)?; + let sig_type = parse_signature_type(&signature_type)?; + let funder = config::validate_funder_for_signature_type( + &signature_type, + config::resolve_funder(funder_flag)?, + )?; let mut builder = unauthenticated_clob_client()? .authentication_builder(signer) diff --git a/src/commands/approve.rs b/src/commands/approve.rs index 56d3b70..f83104c 100644 --- a/src/commands/approve.rs +++ b/src/commands/approve.rs @@ -121,8 +121,11 @@ async fn check( let owner: Address = if let Some(addr) = address_arg { addr } else if proxy::is_poly_1271_mode(signature_type)? { - crate::config::resolve_funder(funder)? - .context("--funder is required when using signature type poly-1271")? + crate::config::validate_funder_for_signature_type( + crate::config::POLY_1271_SIGNATURE_TYPE, + crate::config::resolve_funder(funder)?, + )? + .context("--funder is required when using signature type poly-1271")? } else if proxy::is_proxy_mode(signature_type)? { proxy::derive_proxy_address(private_key)? } else { diff --git a/src/commands/wallet.rs b/src/commands/wallet.rs index 692e9fa..2cbb704 100644 --- a/src/commands/wallet.rs +++ b/src/commands/wallet.rs @@ -90,17 +90,18 @@ fn cmd_create( ) -> Result<()> { guard_overwrite(force)?; let signature_type = config::normalize_signature_type(signature_type)?; + let funder = config::resolve_funder_input(funder)?.map(|address| address.to_string()); let signer = LocalSigner::random().with_chain_id(Some(POLYGON)); let address = signer.address(); let key_hex = format!("{:#x}", signer.to_bytes()); - config::save_wallet(&key_hex, POLYGON, signature_type, funder)?; + config::save_wallet(&key_hex, POLYGON, signature_type, funder.as_deref())?; let config_path = config::config_path()?; let proxy_addr = (signature_type == config::DEFAULT_SIGNATURE_TYPE) .then(|| derive_proxy_wallet(address, POLYGON)) .flatten(); - let funder_addr = config::resolve_funder(funder)?.map(|address| address.to_string()); + let funder_addr = funder; match output { OutputFormat::Json => { @@ -143,6 +144,7 @@ fn cmd_import( ) -> Result<()> { guard_overwrite(force)?; let signature_type = config::normalize_signature_type(signature_type)?; + let funder = config::resolve_funder_input(funder)?.map(|address| address.to_string()); let signer = LocalSigner::from_str(key) .context("Invalid private key")? @@ -150,12 +152,12 @@ fn cmd_import( let address = signer.address(); let key_hex = format!("{:#x}", signer.to_bytes()); - config::save_wallet(&key_hex, POLYGON, signature_type, funder)?; + config::save_wallet(&key_hex, POLYGON, signature_type, funder.as_deref())?; let config_path = config::config_path()?; let proxy_addr = (signature_type == config::DEFAULT_SIGNATURE_TYPE) .then(|| derive_proxy_wallet(address, POLYGON)) .flatten(); - let funder_addr = config::resolve_funder(funder)?.map(|address| address.to_string()); + let funder_addr = funder; match output { OutputFormat::Json => { @@ -222,7 +224,11 @@ fn cmd_show( .map(|a| a.to_string()) }) .flatten(); - let funder = config::resolve_funder(funder_flag)?.map(|address| address.to_string()); + let funder = config::validate_funder_for_signature_type( + &sig_type, + config::resolve_funder(funder_flag)?, + )? + .map(|address| address.to_string()); let config_path = config::config_path()?; match output { diff --git a/src/config.rs b/src/config.rs index 3664f33..e607190 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use std::str::FromStr; use anyhow::{Context, Result, bail}; -use polymarket_client_sdk_v2::types::{Address, address}; +use polymarket_client_sdk_v2::types::Address; use serde::{Deserialize, Serialize}; const ENV_VAR: &str = "POLYMARKET_PRIVATE_KEY"; @@ -112,25 +112,56 @@ pub fn resolve_signature_type(cli_flag: Option<&str>) -> Result { Ok(DEFAULT_SIGNATURE_TYPE.to_string()) } -/// Priority: CLI flag > env var > config file. -pub fn resolve_funder(cli_flag: Option<&str>) -> Result> { - let value = if let Some(funder) = cli_flag { - Some(funder.to_string()) - } else if let Ok(funder) = std::env::var(FUNDER_ENV_VAR) +fn parse_funder(funder: &str) -> Result
{ + Address::from_str(funder).with_context(|| format!("Invalid funder address: {funder}")) +} + +/// Resolve a funder supplied for a new wallet, without inheriting an existing config value. +/// Priority: CLI flag > env var. +pub fn resolve_funder_input(cli_flag: Option<&str>) -> Result> { + if let Some(funder) = cli_flag { + return parse_funder(funder).map(Some); + } + if let Ok(funder) = std::env::var(FUNDER_ENV_VAR) && !funder.is_empty() { - Some(funder) - } else { - load_config()?.and_then(|config| config.funder) - }; + return parse_funder(&funder).map(Some); + } + Ok(None) +} + +/// Resolve a funder for runtime commands. Priority: CLI flag > env var > config file. +pub fn resolve_funder(cli_flag: Option<&str>) -> Result> { + if let Some(funder) = resolve_funder_input(cli_flag)? { + return Ok(Some(funder)); + } - value - .map(|funder| { - Address::from_str(&funder).with_context(|| format!("Invalid funder address: {funder}")) - }) + load_config()? + .and_then(|config| config.funder) + .map(|funder| parse_funder(&funder)) .transpose() } +pub fn validate_funder_for_signature_type( + signature_type: &str, + funder: Option
, +) -> Result> { + let signature_type = normalize_signature_type(signature_type)?; + match (signature_type, funder) { + (POLY_1271_SIGNATURE_TYPE, None) => { + bail!("--funder is required when using signature type poly-1271") + } + (POLY_1271_SIGNATURE_TYPE, Some(Address::ZERO)) => { + bail!("The funder address cannot be zero for signature type poly-1271") + } + (POLY_1271_SIGNATURE_TYPE, Some(funder)) => Ok(Some(funder)), + (_, Some(_)) => bail!( + "A funder address is only valid with signature type poly-1271; unset --funder, POLYMARKET_FUNDER, or the config funder" + ), + (_, None) => Ok(None), + } +} + pub fn save_wallet( key: &str, chain_id: u64, @@ -138,22 +169,9 @@ pub fn save_wallet( funder: Option<&str>, ) -> Result<()> { let signature_type = normalize_signature_type(signature_type)?; - let funder = funder - .map(|value| { - Address::from_str(value) - .with_context(|| format!("Invalid funder address: {value}")) - .map(|address| address.to_string()) - }) - .transpose()?; - - if signature_type == POLY_1271_SIGNATURE_TYPE { - let address = funder - .as_deref() - .context("--funder is required when using signature type poly-1271")?; - if Address::from_str(address)? == address!("0000000000000000000000000000000000000000") { - bail!("The funder address cannot be zero for signature type poly-1271"); - } - } + let funder = funder.map(parse_funder).transpose()?; + let funder = validate_funder_for_signature_type(signature_type, funder)? + .map(|address| address.to_string()); let dir = config_dir()?; fs::create_dir_all(&dir).context("Failed to create config directory")?; @@ -306,14 +324,61 @@ mod tests { } #[test] - fn resolve_funder_reads_env_address() { + fn non_poly_wallet_rejects_funder_before_writing_config() { + let error = save_wallet( + "unused", + 137, + DEFAULT_SIGNATURE_TYPE, + Some("0xd1615A7B6146cDbA40a559eC876A3bcca4050890"), + ) + .unwrap_err(); + assert!( + error + .to_string() + .contains("only valid with signature type poly-1271") + ); + } + + #[test] + fn poly_1271_rejects_zero_funder() { + let error = + validate_funder_for_signature_type(POLY_1271_SIGNATURE_TYPE, Some(Address::ZERO)) + .unwrap_err(); + assert!(error.to_string().contains("cannot be zero")); + } + + #[test] + fn poly_1271_accepts_nonzero_funder() { + let funder = Address::from_str("0xd1615A7B6146cDbA40a559eC876A3bcca4050890").unwrap(); + assert_eq!( + validate_funder_for_signature_type(POLY_1271_SIGNATURE_TYPE, Some(funder)).unwrap(), + Some(funder) + ); + } + + #[test] + fn resolve_funder_input_reads_env_address() { let _lock = ENV_LOCK.lock().unwrap(); unsafe { set(FUNDER_ENV_VAR, "0xd1615A7B6146cDbA40a559eC876A3bcca4050890") }; - let funder = resolve_funder(None).unwrap().unwrap(); + let funder = resolve_funder_input(None).unwrap().unwrap(); assert_eq!( funder, Address::from_str("0xd1615A7B6146cDbA40a559eC876A3bcca4050890").unwrap() ); unsafe { unset(FUNDER_ENV_VAR) }; } + + #[test] + fn resolve_funder_input_flag_overrides_env() { + let _lock = ENV_LOCK.lock().unwrap(); + unsafe { set(FUNDER_ENV_VAR, "0xd1615A7B6146cDbA40a559eC876A3bcca4050890") }; + let funder = resolve_funder_input(Some("0x0000000000000000000000000000000000000001")) + .unwrap() + .unwrap(); + assert_eq!( + funder, + Address::from_str("0x0000000000000000000000000000000000000001").unwrap() + ); + unsafe { unset(FUNDER_ENV_VAR) }; + } } diff --git a/tests/cli_integration.rs b/tests/cli_integration.rs index 4b8806c..92535f6 100644 --- a/tests/cli_integration.rs +++ b/tests/cli_integration.rs @@ -20,6 +20,40 @@ fn help_lists_poly_1271_funder_options() { .stdout(predicate::str::contains("poly-1271").and(predicate::str::contains("--funder"))); } +#[test] +fn proxy_auth_rejects_stale_funder_before_network_request() { + polymarket() + .env( + "POLYMARKET_PRIVATE_KEY", + "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + ) + .env("POLYMARKET_SIGNATURE_TYPE", "proxy") + .env( + "POLYMARKET_FUNDER", + "0xd1615A7B6146cDbA40a559eC876A3bcca4050890", + ) + .args(["clob", "balance", "--asset-type", "collateral"]) + .assert() + .failure() + .stderr(predicate::str::contains( + "only valid with signature type poly-1271", + )); +} + +#[test] +fn poly_1271_auth_requires_funder_before_network_request() { + polymarket() + .env( + "POLYMARKET_PRIVATE_KEY", + "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + ) + .env("POLYMARKET_SIGNATURE_TYPE", "poly-1271") + .args(["clob", "balance", "--asset-type", "collateral"]) + .assert() + .failure() + .stderr(predicate::str::contains("--funder is required")); +} + #[test] fn help_lists_all_top_level_commands() { polymarket().arg("--help").assert().success().stdout( From 72d872337f690d1d8ed24928d7c72fa613913f57 Mon Sep 17 00:00:00 2001 From: Anurag Sharan <34830688+sharananurag998@users.noreply.github.com> Date: Sat, 18 Jul 2026 16:49:00 +0530 Subject: [PATCH 3/3] Fix POLY_1271 V2 order signing --- Cargo.lock | 9 +++++---- Cargo.toml | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b60d968..1c1656d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3021,9 +3021,9 @@ dependencies = [ [[package]] name = "polymarket_client_sdk_v2" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "594de0bf0e129e266946f02acbf579253951afbbee92fc1564da3c9179303aea" +checksum = "ba212e0641f178c274af266772de15962ac7e76da550a0f79f47b49349b1138a" dependencies = [ "alloy", "async-stream", @@ -3048,6 +3048,7 @@ dependencies = [ "sha1", "sha2", "strum_macros 0.28.0", + "tokio", "url", "uuid", ] @@ -4501,9 +4502,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", diff --git a/Cargo.toml b/Cargo.toml index 6e4755f..bcda8d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,9 @@ name = "polymarket" path = "src/main.rs" [dependencies] -polymarket_client_sdk_v2 = { version = "0.5.1", features = ["gamma", "data", "bridge", "clob", "ctf"] } +# 0.7.0 is the first release that sets deposit-wallet maker/signer fields and +# emits the ERC-7739-wrapped signature required for POLY_1271 V2 orders. +polymarket_client_sdk_v2 = { version = "0.7.0", features = ["gamma", "data", "bridge", "clob", "ctf"] } alloy = { version = "1.6.3", default-features = false, features = ["providers", "rpc-types", "sol-types", "contract", "reqwest", "reqwest-rustls-tls", "signer-local", "signers"] } clap = { version = "4", features = ["derive"] } tokio = { version = "1", features = ["rt-multi-thread", "macros"] }