diff --git a/Cargo.lock b/Cargo.lock
index 26a3032..0b45d9b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2976,6 +2976,7 @@ dependencies = [
"dirs",
"polymarket-client-sdk",
"predicates",
+ "reqwest 0.13.2",
"rust_decimal",
"rust_decimal_macros",
"rustyline",
diff --git a/Cargo.toml b/Cargo.toml
index a01bf05..3299fb2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,6 +23,7 @@ serde = { version = "1", features = ["derive"] }
tabled = "0.17"
rust_decimal = "1"
anyhow = "1"
+reqwest = { version = "0.13", features = ["socks"] }
chrono = "0.4"
dirs = "6"
rustyline = "15"
diff --git a/src/commands/setup.rs b/src/commands/setup.rs
index dd04671..7d5082f 100644
--- a/src/commands/setup.rs
+++ b/src/commands/setup.rs
@@ -5,9 +5,9 @@ use std::str::FromStr;
use anyhow::{Context, Result};
use polymarket_client_sdk::auth::{LocalSigner, Signer as _};
use polymarket_client_sdk::types::Address;
-use polymarket_client_sdk::{POLYGON, derive_proxy_wallet};
+use polymarket_client_sdk::POLYGON;
-use super::wallet::normalize_key;
+use super::wallet::{derive_trading_wallet, normalize_key};
use crate::config;
fn print_banner() {
@@ -154,18 +154,18 @@ fn setup_wallet() -> Result
{
fn finish_setup(address: Address) -> Result<()> {
let total = 4;
- step_header(2, total, "Proxy Wallet");
+ step_header(2, total, "Trading Wallet");
- let proxy = derive_proxy_wallet(address, POLYGON);
- match proxy {
- Some(proxy) => {
- println!(" ✓ Proxy wallet derived");
- println!(" Proxy: {proxy}");
+ let sig_type = config::resolve_signature_type(None);
+ let trading = derive_trading_wallet(address, POLYGON, &sig_type);
+ match trading {
+ Some(tw) => {
+ println!(" ✓ Trading wallet derived ({sig_type})");
+ println!(" Trading wallet: {tw}");
println!(" Deposit USDC to this address to start trading.");
}
None => {
- println!(" ✗ Could not derive proxy wallet");
- println!(" You may need to use --signature-type eoa");
+ println!(" ℹ Using EOA directly (signature type: {sig_type})");
}
}
@@ -173,7 +173,7 @@ fn finish_setup(address: Address) -> Result<()> {
step_header(3, total, "Fund Wallet");
- let deposit_addr = proxy.unwrap_or(address);
+ let deposit_addr = trading.unwrap_or(address);
println!(" ○ Deposit USDC to your wallet to start trading");
println!(" Run: polymarket bridge deposit {deposit_addr}");
println!(" Or transfer USDC directly on Polygon");
diff --git a/src/commands/wallet.rs b/src/commands/wallet.rs
index ce7597f..8b46109 100644
--- a/src/commands/wallet.rs
+++ b/src/commands/wallet.rs
@@ -5,11 +5,27 @@ use anyhow::{Context, Result, bail};
use clap::{Args, Subcommand};
use polymarket_client_sdk::auth::LocalSigner;
use polymarket_client_sdk::auth::Signer as _;
-use polymarket_client_sdk::{POLYGON, derive_proxy_wallet};
+use polymarket_client_sdk::{POLYGON, derive_proxy_wallet, derive_safe_wallet};
use crate::config;
use crate::output::OutputFormat;
+/// Derive the trading wallet address based on the configured signature type.
+/// - "proxy" → Polymarket Proxy wallet (Magic/email)
+/// - "gnosis-safe" → Gnosis Safe wallet (browser/MetaMask)
+/// - anything else (e.g. "eoa") → None (the EOA itself is used)
+pub(crate) fn derive_trading_wallet(
+ address: polymarket_client_sdk::types::Address,
+ chain_id: u64,
+ signature_type: &str,
+) -> Option {
+ match signature_type {
+ "proxy" => derive_proxy_wallet(address, chain_id),
+ "gnosis-safe" => derive_safe_wallet(address, chain_id),
+ _ => None,
+ }
+}
+
#[derive(Args)]
pub struct WalletArgs {
#[command(subcommand)]
@@ -103,7 +119,7 @@ fn cmd_create(output: &OutputFormat, force: bool, signature_type: &str) -> Resul
config::save_wallet(&key_hex, POLYGON, signature_type)?;
let config_path = config::config_path()?;
- let proxy_addr = derive_proxy_wallet(address, POLYGON);
+ let trading_addr = derive_trading_wallet(address, POLYGON, signature_type);
match output {
OutputFormat::Json => {
@@ -111,7 +127,7 @@ fn cmd_create(output: &OutputFormat, force: bool, signature_type: &str) -> Resul
"{}",
serde_json::json!({
"address": address.to_string(),
- "proxy_address": proxy_addr.map(|a| a.to_string()),
+ "trading_wallet": trading_addr.map(|a| a.to_string()),
"signature_type": signature_type,
"config_path": config_path.display().to_string(),
})
@@ -120,8 +136,8 @@ fn cmd_create(output: &OutputFormat, force: bool, signature_type: &str) -> Resul
OutputFormat::Table => {
println!("Wallet created successfully!");
println!("Address: {address}");
- if let Some(proxy) = proxy_addr {
- println!("Proxy wallet: {proxy}");
+ if let Some(tw) = trading_addr {
+ println!("Trading wallet: {tw}");
}
println!("Signature type: {signature_type}");
println!("Config: {}", config_path.display());
@@ -144,7 +160,7 @@ fn cmd_import(key: &str, output: &OutputFormat, force: bool, signature_type: &st
config::save_wallet(&normalized, POLYGON, signature_type)?;
let config_path = config::config_path()?;
- let proxy_addr = derive_proxy_wallet(address, POLYGON);
+ let trading_addr = derive_trading_wallet(address, POLYGON, signature_type);
match output {
OutputFormat::Json => {
@@ -152,7 +168,7 @@ fn cmd_import(key: &str, output: &OutputFormat, force: bool, signature_type: &st
"{}",
serde_json::json!({
"address": address.to_string(),
- "proxy_address": proxy_addr.map(|a| a.to_string()),
+ "trading_wallet": trading_addr.map(|a| a.to_string()),
"signature_type": signature_type,
"config_path": config_path.display().to_string(),
})
@@ -161,8 +177,8 @@ fn cmd_import(key: &str, output: &OutputFormat, force: bool, signature_type: &st
OutputFormat::Table => {
println!("Wallet imported successfully!");
println!("Address: {address}");
- if let Some(proxy) = proxy_addr {
- println!("Proxy wallet: {proxy}");
+ if let Some(tw) = trading_addr {
+ println!("Trading wallet: {tw}");
}
println!("Signature type: {signature_type}");
println!("Config: {}", config_path.display());
@@ -193,12 +209,13 @@ fn cmd_show(output: &OutputFormat, private_key_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
+
+ let sig_type = config::resolve_signature_type(None);
+ let trading_addr = signer
.as_ref()
- .and_then(|s| derive_proxy_wallet(s.address(), POLYGON))
+ .and_then(|s| derive_trading_wallet(s.address(), POLYGON, &sig_type))
.map(|a| a.to_string());
- let sig_type = config::resolve_signature_type(None);
let config_path = config::config_path()?;
match output {
@@ -207,7 +224,7 @@ fn cmd_show(output: &OutputFormat, private_key_flag: Option<&str>) -> Result<()>
"{}",
serde_json::json!({
"address": address,
- "proxy_address": proxy_addr,
+ "trading_wallet": trading_addr,
"signature_type": sig_type,
"config_path": config_path.display().to_string(),
"source": source.label(),
@@ -220,8 +237,8 @@ fn cmd_show(output: &OutputFormat, private_key_flag: Option<&str>) -> Result<()>
Some(addr) => println!("Address: {addr}"),
None => println!("Address: (not configured)"),
}
- if let Some(proxy) = &proxy_addr {
- println!("Proxy wallet: {proxy}");
+ if let Some(tw) = &trading_addr {
+ println!("Trading wallet: {tw}");
}
println!("Signature type: {sig_type}");
println!("Config path: {}", config_path.display());
diff --git a/src/config.rs b/src/config.rs
index d2f5395..df299e5 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
const ENV_VAR: &str = "POLYMARKET_PRIVATE_KEY";
const SIG_TYPE_ENV_VAR: &str = "POLYMARKET_SIGNATURE_TYPE";
+const PROXY_ENV_VAR: &str = "POLYMARKET_PROXY";
pub const DEFAULT_SIGNATURE_TYPE: &str = "proxy";
pub const NO_WALLET_MSG: &str =
@@ -13,10 +14,13 @@ pub const NO_WALLET_MSG: &str =
#[derive(Serialize, Deserialize)]
pub struct Config {
+ #[serde(default)]
pub private_key: String,
pub chain_id: u64,
#[serde(default = "default_signature_type")]
pub signature_type: String,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub proxy: Option,
}
fn default_signature_type() -> String {
@@ -94,10 +98,14 @@ pub fn save_wallet(key: &str, chain_id: u64, signature_type: &str) -> Result<()>
fs::set_permissions(&dir, fs::Permissions::from_mode(0o700))?;
}
+ // Preserve existing proxy setting from config file
+ let existing_proxy = load_config().and_then(|c| c.proxy);
+
let config = Config {
private_key: key.to_string(),
chain_id,
signature_type: signature_type.to_string(),
+ proxy: existing_proxy,
};
let json = serde_json::to_string_pretty(&config)?;
let path = config_path()?;
@@ -125,6 +133,22 @@ pub fn save_wallet(key: &str, chain_id: u64, signature_type: &str) -> Result<()>
Ok(())
}
+
+/// Priority: CLI flag > env var > config file.
+pub fn resolve_proxy(cli_flag: Option<&str>) -> Option {
+ if let Some(url) = cli_flag {
+ return Some(url.to_string());
+ }
+ if let Ok(url) = std::env::var(PROXY_ENV_VAR)
+ && !url.is_empty()
+ {
+ return Some(url);
+ }
+ if let Some(config) = load_config() {
+ return config.proxy;
+ }
+ None
+}
/// Priority: CLI flag > env var > config file.
pub fn resolve_key(cli_flag: Option<&str>) -> (Option, KeySource) {
if let Some(key) = cli_flag {
@@ -136,7 +160,9 @@ pub fn resolve_key(cli_flag: Option<&str>) -> (Option, KeySource) {
return (Some(key), KeySource::EnvVar);
}
if let Some(config) = load_config() {
- return (Some(config.private_key), KeySource::ConfigFile);
+ if !config.private_key.is_empty() {
+ return (Some(config.private_key), KeySource::ConfigFile);
+ }
}
(None, KeySource::None)
}
diff --git a/src/main.rs b/src/main.rs
index 61af087..1b74553 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -26,6 +26,10 @@ pub(crate) struct Cli {
/// Signature type: eoa, proxy, or gnosis-safe
#[arg(long, global = true)]
signature_type: Option,
+
+ /// SOCKS5 or HTTP proxy URL (e.g., socks5://127.0.0.1:1080)
+ #[arg(long, global = true)]
+ proxy: Option,
}
#[derive(Subcommand)]
@@ -66,12 +70,32 @@ enum Commands {
Upgrade,
}
-#[tokio::main]
-async fn main() -> ExitCode {
+fn main() -> ExitCode {
+ // Resolve proxy BEFORE tokio spawns worker threads.
+ // Parse CLI args early (sync) to get --proxy flag.
let cli = Cli::parse();
+
+ // Apply proxy: --proxy flag > POLYMARKET_PROXY env > config file proxy field.
+ // Only set HTTP(S)_PROXY for CLOB/Gamma API calls.
+ // Exclude the Polygon RPC so alloy (which uses reqwest 0.12 without socks
+ // support) can still reach the RPC directly.
+ if let Some(ref url) = config::resolve_proxy(cli.proxy.as_deref()) {
+ // SAFETY: no threads exist yet — called before tokio runtime is built.
+ unsafe {
+ std::env::set_var("HTTPS_PROXY", url);
+ std::env::set_var("HTTP_PROXY", url);
+ std::env::set_var("NO_PROXY", "polygon.drpc.org,drpc.org");
+ }
+ }
+
let output = cli.output;
- if let Err(e) = run(cli).await {
+ let runtime = tokio::runtime::Builder::new_multi_thread()
+ .enable_all()
+ .build()
+ .expect("Failed to build tokio runtime");
+
+ if let Err(e) = runtime.block_on(run(cli)) {
match output {
OutputFormat::Json => {
println!("{}", serde_json::json!({"error": e.to_string()}));