Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ serde = { version = "1", features = ["derive"] }
tabled = "0.17"
rust_decimal = "1"
anyhow = "1"
reqwest = { version = "0.13", features = ["socks"] }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cargo.lock missing tokio-socks breaks SOCKS5 proxy support

High Severity

The Cargo.toml adds reqwest with features = ["socks"], but the committed Cargo.lock does not contain tokio-socks anywhere — the required transitive dependency for SOCKS5 support. The reqwest 0.13.2 entry in the lockfile lists no socks-related dependencies. This means builds using cargo install --locked or CI with --locked will compile reqwest without actual SOCKS5 support, silently making the PR's core feature non-functional at runtime.

Additional Locations (1)

Fix in Cursor Fix in Web

chrono = "0.4"
dirs = "6"
rustyline = "15"
Expand Down
28 changes: 27 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@ 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 =
"No wallet configured. Run `polymarket wallet create` or `polymarket wallet import <key>`";

#[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<String>,
}

fn default_signature_type() -> String {
Expand Down Expand Up @@ -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()?;
Expand Down Expand Up @@ -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<String> {
if let Some(url) = cli_flag {
return Some(url.to_string());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty proxy string not filtered from CLI flag

Low Severity

resolve_proxy filters empty strings for the POLYMARKET_PROXY env var path (via !url.is_empty()) but does not apply the same check to the CLI flag path or the config file path. An empty --proxy "" value or an empty proxy string in the config file would result in setting HTTPS_PROXY and HTTP_PROXY to empty strings, likely causing confusing request failures.

Fix in Cursor Fix in Web

}
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<String>, KeySource) {
if let Some(key) = cli_flag {
Expand All @@ -136,7 +160,9 @@ pub fn resolve_key(cli_flag: Option<&str>) -> (Option<String>, 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)
}
Expand Down
30 changes: 27 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ pub(crate) struct Cli {
/// Signature type: eoa, proxy, or gnosis-safe
#[arg(long, global = true)]
signature_type: Option<String>,

/// SOCKS5 or HTTP proxy URL (e.g., socks5://127.0.0.1:1080)
#[arg(long, global = true)]
proxy: Option<String>,
}

#[derive(Subcommand)]
Expand Down Expand Up @@ -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");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NO_PROXY overwrites existing user environment variable values

Medium Severity

Setting NO_PROXY to a hardcoded value unconditionally replaces any existing NO_PROXY entries the user already has in their environment. Since this feature specifically targets corporate/VPN users — who commonly have NO_PROXY configured for internal services — this overwrites those entries within the CLI process. The existing entries need to be preserved by reading the current NO_PROXY value and appending polygon.drpc.org,drpc.org to it rather than replacing it.

Fix in Cursor Fix in Web

}
}

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()}));
Expand Down