fix: support V2 proxy wallet funders

This commit is contained in:
floor-licker
2026-04-28 11:19:55 -03:00
parent eac25be5b2
commit 7ad3229af8
7 changed files with 247 additions and 32 deletions
+29 -5
View File
@@ -11,7 +11,7 @@ use crate::types::{
MarketOrderArgs, OrderArgs, OrderType, PostOrder, PostOrderOptions, PostOrderResponse, Side,
SignedOrderRequest,
};
use alloy_primitives::U256;
use alloy_primitives::{Address, U256};
use alloy_signer_local::PrivateKeySigner;
use reqwest::header::HeaderName;
use reqwest::Client;
@@ -99,6 +99,8 @@ impl ClobClient {
signer: Option<PrivateKeySigner>,
api_creds: Option<ApiCreds>,
builder_code: Option<String>,
sig_type: Option<crate::orders::SigType>,
funder: Option<Address>,
) -> Self {
let dns_cache = tokio::runtime::Handle::try_current().ok().and_then(|_| {
tokio::task::block_in_place(|| {
@@ -132,7 +134,7 @@ impl ClobClient {
let order_builder = signer
.clone()
.map(|signer| crate::orders::OrderBuilder::new(signer, None, None));
.map(|signer| crate::orders::OrderBuilder::new(signer, sig_type, funder));
Self {
http_client,
@@ -152,7 +154,7 @@ impl ClobClient {
/// Now includes DNS caching, connection management, and buffer pooling
pub fn new(host: &str) -> Self {
let http_client = build_http_client(host, None, None);
Self::build_client(host, 137, http_client, None, None, None)
Self::build_client(host, 137, http_client, None, None, None, None, None)
}
/// Create a V2-native client from config.
@@ -166,6 +168,26 @@ impl ClobClient {
None => None,
};
let sig_type = config
.signature_type
.map(crate::orders::sig_type_from_u8)
.transpose()?;
let explicit_funder = config
.funder
.as_deref()
.map(Address::from_str)
.transpose()
.map_err(|e| PolyfillError::config(format!("Invalid funder address: {e}")))?;
let funder = match (&signer, sig_type) {
(Some(signer), Some(sig_type)) => crate::orders::resolve_funder(
signer.address(),
config.chain,
sig_type,
explicit_funder,
)?,
_ => explicit_funder,
};
let http_client =
build_http_client(&config.base_url, config.timeout, config.max_connections);
@@ -176,6 +198,8 @@ impl ClobClient {
signer,
config.api_credentials,
config.builder_code,
sig_type,
funder,
))
}
@@ -187,7 +211,7 @@ impl ClobClient {
.build()
.expect("Failed to build reqwest client")
});
Self::build_client(host, 137, http_client, None, None, None)
Self::build_client(host, 137, http_client, None, None, None, None, None)
}
/// Create a client optimized for internet connections
@@ -198,7 +222,7 @@ impl ClobClient {
.build()
.expect("Failed to build reqwest client")
});
Self::build_client(host, 137, http_client, None, None, None)
Self::build_client(host, 137, http_client, None, None, None, None, None)
}
/// Create a client with L1 headers (for authentication)
+97 -2
View File
@@ -8,7 +8,7 @@ use crate::errors::{PolyfillError, Result};
use crate::types::{
CreateOrderOptions, MarketOrderArgs, OrderArgs, OrderType, Side, SignedOrderRequest,
};
use alloy_primitives::{Address, B256, U256};
use alloy_primitives::{keccak256, Address, B256, U256};
use alloy_signer_local::PrivateKeySigner;
use rand::Rng;
use rust_decimal::Decimal;
@@ -21,7 +21,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
pub const BYTES32_ZERO: &str = "0x0000000000000000000000000000000000000000000000000000000000000000";
/// Signature types for orders
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum SigType {
/// ECDSA EIP712 signatures signed by EOAs
Eoa = 0,
@@ -29,6 +29,8 @@ pub enum SigType {
PolyProxy = 1,
/// EIP712 signatures signed by EOAs that own Polymarket Gnosis safes
PolyGnosisSafe = 2,
/// EIP-1271 smart contract wallet signatures (V2 orders only)
Poly1271 = 3,
}
/// Rounding configuration for different tick sizes
@@ -52,6 +54,13 @@ pub struct OrderBuilder {
funder: Address,
}
const POLYGON_PROXY_FACTORY: &str = "0xaB45c5A4B0c941a2F231C04C3f49182e1A254052";
const POLYGON_SAFE_FACTORY: &str = "0xaacFeEa03eb1561C4e67d661e40682Bd20E3541b";
const PROXY_INIT_CODE_HASH: &str =
"0xd21df8dc65880a8606f09fe0ce3df9b8869287ab0b058be05aa9e8af6330a00b";
const SAFE_INIT_CODE_HASH: &str =
"0x2bce2127ff07fb632d16c8347c4ebf501f4841168bed00d9e6ef715ddb6fcecf";
/// Rounding configurations for different tick sizes
static ROUNDING_CONFIG: LazyLock<HashMap<Decimal, RoundConfig>> = LazyLock::new(|| {
HashMap::from([
@@ -107,6 +116,70 @@ pub fn get_contract_config(chain_id: u64, neg_risk: bool) -> Option<ContractConf
}
}
pub fn sig_type_from_u8(signature_type: u8) -> Result<SigType> {
match signature_type {
0 => Ok(SigType::Eoa),
1 => Ok(SigType::PolyProxy),
2 => Ok(SigType::PolyGnosisSafe),
3 => Ok(SigType::Poly1271),
other => Err(PolyfillError::validation(format!(
"Unsupported signature_type {other}"
))),
}
}
pub fn derive_proxy_wallet(eoa_address: Address, chain_id: u64) -> Result<Address> {
if chain_id != 137 {
return Err(PolyfillError::config(
"Proxy wallet auto-derivation is only configured for Polygon mainnet",
));
}
let factory = Address::from_str(POLYGON_PROXY_FACTORY)
.map_err(|e| PolyfillError::config(format!("Invalid proxy factory address: {e}")))?;
let init_code_hash = B256::from_str(PROXY_INIT_CODE_HASH)
.map_err(|e| PolyfillError::config(format!("Invalid proxy init code hash: {e}")))?;
let salt = keccak256(eoa_address);
Ok(factory.create2(salt, init_code_hash))
}
pub fn derive_safe_wallet(eoa_address: Address, chain_id: u64) -> Result<Address> {
if chain_id != 137 {
return Err(PolyfillError::config(
"Safe wallet auto-derivation is only configured for Polygon mainnet",
));
}
let factory = Address::from_str(POLYGON_SAFE_FACTORY)
.map_err(|e| PolyfillError::config(format!("Invalid safe factory address: {e}")))?;
let init_code_hash = B256::from_str(SAFE_INIT_CODE_HASH)
.map_err(|e| PolyfillError::config(format!("Invalid safe init code hash: {e}")))?;
let mut padded = [0_u8; 32];
padded[12..].copy_from_slice(eoa_address.as_slice());
let salt = keccak256(padded);
Ok(factory.create2(salt, init_code_hash))
}
pub fn resolve_funder(
signer_address: Address,
chain_id: u64,
sig_type: SigType,
funder: Option<Address>,
) -> Result<Option<Address>> {
match (sig_type, funder) {
(SigType::Eoa, Some(_)) => Err(PolyfillError::validation(
"funder cannot be set for EOA signature_type",
)),
(SigType::PolyProxy, None) => derive_proxy_wallet(signer_address, chain_id).map(Some),
(SigType::PolyGnosisSafe, None) => derive_safe_wallet(signer_address, chain_id).map(Some),
(SigType::Poly1271, None) => Err(PolyfillError::validation(
"funder is required for Poly1271 signature_type",
)),
(_, Some(Address::ZERO)) => Err(PolyfillError::validation("funder cannot be zero address")),
(_, explicit) => Ok(explicit),
}
}
/// Generate a random seed for order salt
fn generate_seed() -> u64 {
let mut rng = rand::thread_rng();
@@ -580,6 +653,28 @@ mod tests {
assert!(config_unsupported.is_none());
}
#[test]
fn test_signature_type_from_u8() {
assert_eq!(sig_type_from_u8(0).unwrap(), SigType::Eoa);
assert_eq!(sig_type_from_u8(1).unwrap(), SigType::PolyProxy);
assert_eq!(sig_type_from_u8(2).unwrap(), SigType::PolyGnosisSafe);
assert_eq!(sig_type_from_u8(3).unwrap(), SigType::Poly1271);
assert!(sig_type_from_u8(4).is_err());
}
#[test]
fn test_derive_polygon_funder_addresses() {
let eoa = Address::from_str("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266").unwrap();
assert_eq!(
derive_safe_wallet(eoa, 137).unwrap(),
Address::from_str("0xd93b25Cb943D14d0d34FBAf01fc93a0F8b5f6e47").unwrap()
);
assert_eq!(
derive_proxy_wallet(eoa, 137).unwrap(),
Address::from_str("0x365f0cA36ae1F641E02Fe3b7743673DA42A13a70").unwrap()
);
}
#[test]
fn test_normalize_optional_bytes32_defaults_to_zero() {
assert_eq!(
+7
View File
@@ -806,6 +806,11 @@ pub struct ClientConfig {
pub api_credentials: Option<ApiCredentials>,
/// Builder code applied to orders when none is specified on the order itself.
pub builder_code: Option<String>,
/// Polymarket signature type: 0 EOA, 1 Proxy, 2 Gnosis Safe, 3 Poly1271.
pub signature_type: Option<u8>,
/// Address that holds funds for proxy/Safe/smart-contract wallet flows.
/// If omitted for signature type 1 or 2, the Polygon funder is derived from the signer.
pub funder: Option<String>,
/// Request timeout
pub timeout: Option<std::time::Duration>,
/// Maximum number of connections
@@ -820,6 +825,8 @@ impl Default for ClientConfig {
private_key: None,
api_credentials: None,
builder_code: None,
signature_type: None,
funder: None,
timeout: Some(std::time::Duration::from_secs(30)),
max_connections: Some(100),
}