1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
//! Unofficial Polymarket relayer client for gasless transactions.
//!
//! This crate provides a typed Rust client for the
//! [Polymarket relayer API](https://docs.polymarket.com/trading/gasless),
//! enabling gasless on-chain operations through Safe and Proxy wallets.
//!
//! # Client construction
//!
//! The client uses a typestate pattern: start unauthenticated, then
//! attach credentials to unlock submission methods.
//!
//! ```
//! use polyrel::{RelayerClient, Auth, BuilderCredentials};
//! use secrecy::SecretString;
//!
//! let client = RelayerClient::builder()
//! .build()
//! .expect("default config is valid");
//!
//! let auth = Auth::Builder(BuilderCredentials {
//! api_key: SecretString::from("key"),
//! secret: SecretString::from("c2VjcmV0"),
//! passphrase: SecretString::from("pass"),
//! });
//! let client = client.authenticate(auth);
//! ```
//!
//! Override defaults for testnets or custom deployments. When
//! pointing at a non-Polygon deployment, override all contract
//! addresses and init-code hashes that differ — `base_url` and
//! `chain_id` alone are not sufficient:
//!
//! ```
//! use alloy_primitives::{address, B256};
//! use polyrel::RelayerClient;
//!
//! let client = RelayerClient::builder()
//! .base_url("https://relayer-testnet.example.com".into())
//! .chain_id(80002_u64)
//! .safe_factory(address!("aacFeEa03eb1561C4e67d661e40682Bd20E3541b"))
//! .safe_multisend(address!("A238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761"))
//! .safe_init_code_hash(B256::from(polyrel::SAFE_INIT_CODE_HASH))
//! .build()
//! .expect("valid config");
//! ```
//!
//! # Wallet address derivation
//!
//! Derive deterministic Safe or Proxy wallet addresses from an owner
//! and factory using CREATE2:
//!
//! ```
//! use alloy_primitives::{address, Address, B256};
//! use polyrel::{derive_safe_address, derive_proxy_address};
//!
//! let owner = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
//! let safe_factory = address!("aacFeEa03eb1561C4e67d661e40682Bd20E3541b");
//! let proxy_factory = address!("aB45c5A4B0c941a2F231C04C3f49182e1A254052");
//! let safe_hash = B256::from(polyrel::SAFE_INIT_CODE_HASH);
//! let proxy_hash = B256::from(polyrel::PROXY_INIT_CODE_HASH);
//!
//! let safe_addr = derive_safe_address(owner, safe_factory, safe_hash);
//! let proxy_addr = derive_proxy_address(owner, proxy_factory, proxy_hash);
//!
//! assert_ne!(safe_addr, Address::ZERO);
//! assert_ne!(proxy_addr, Address::ZERO);
//! ```
//!
//! # Configuration
//!
//! [`Config`] holds all contract addresses, init-code hashes, and the
//! relayer base URL. It defaults to Polygon mainnet values and can be
//! inspected after construction:
//!
//! ```
//! use polyrel::Config;
//!
//! let config = Config::builder().build().unwrap();
//! assert_eq!(config.chain_id(), 137);
//! assert_eq!(config.base_url().scheme(), "https");
//! ```
//!
//! # Batch approvals with MultiSend
//!
//! Combine multiple approvals into a single relayer transaction using
//! the public calldata builders and [`aggregate_transactions`]:
//!
//! ```
//! use alloy_primitives::U256;
//! use polyrel::{Config, NonEmptyTransactions, OperationType, SafeTransaction};
//!
//! let config = Config::builder().build().unwrap();
//!
//! let (to1, data1) = polyrel::usdc_approve_exchange(&config, U256::MAX);
//! let (to2, data2) = polyrel::ctf_approve_exchange(&config);
//!
//! let tx1 = SafeTransaction {
//! to: to1,
//! value: U256::ZERO,
//! data: data1.to_vec(),
//! operation: OperationType::Call,
//! };
//! let tx2 = SafeTransaction {
//! to: to2,
//! value: U256::ZERO,
//! data: data2.to_vec(),
//! operation: OperationType::Call,
//! };
//!
//! let batch = NonEmptyTransactions::new(vec![tx1, tx2]).unwrap();
//! let combined = polyrel::aggregate_transactions(batch, config.safe_multisend());
//!
//! assert_eq!(combined.operation, OperationType::DelegateCall);
//! assert_eq!(combined.to, config.safe_multisend());
//! ```
//!
//! Then submit with
//! `client.sign_and_submit_safe(&signer, combined, nonce).await`.
//!
//! # Safe signature packing
//!
//! Raw ECDSA signatures must be packed into Safe's expected format
//! before submission. The v-value is adjusted: `0/1 → +31`, `27/28 → +4`.
//!
//! ```
//! use polyrel::pack_safe_signature;
//!
//! // 64 bytes of r+s, then v=27
//! let mut sig = vec![0xaa; 64];
//! sig.push(27);
//! let hex = alloy_primitives::hex::encode(&sig);
//!
//! let packed = pack_safe_signature(&hex).unwrap();
//! let bytes = alloy_primitives::hex::decode(packed.strip_prefix("0x").unwrap()).unwrap();
//! assert_eq!(bytes[64], 31); // 27 + 4
//! ```
//!
//! # Submitting transactions
//!
//! Authenticated clients can sign and submit Safe transactions. The
//! Safe address is derived automatically from the signer and factory:
//!
//! ```no_run
//! use polyrel::{RelayerClient, Auth, BuilderCredentials};
//! use secrecy::SecretString;
//! use alloy_primitives::U256;
//! use alloy_signer::Signer;
//!
//! async fn run(signer: &(impl Signer + Sync)) -> Result<(), polyrel::PolyrelError> {
//! let client = RelayerClient::builder().build()?
//! .authenticate(Auth::Builder(BuilderCredentials {
//! api_key: SecretString::from("key"),
//! secret: SecretString::from("c2VjcmV0"),
//! passphrase: SecretString::from("pass"),
//! }));
//!
//! // fetch the current nonce for this signer's Safe wallet
//! let nonce_str = client.safe_nonce(signer.address()).await?;
//! let nonce = nonce_str.parse::<U256>().expect("valid nonce");
//!
//! // approve USDC for the CTF Exchange
//! let resp = client
//! .approve_usdc_for_exchange(signer, U256::MAX, nonce)
//! .await?;
//!
//! // poll until confirmed
//! let txn = client
//! .poll_until_state(
//! &resp.transaction_id,
//! &["STATE_MINED", "STATE_CONFIRMED"],
//! Some("STATE_FAILED"),
//! None,
//! None,
//! )
//! .await?;
//! Ok(())
//! }
pub use ;
pub use ;
pub use PolyrelError;
pub use ;
pub use ;
use ;
/// Safe factory EIP-712 domain name for `CreateProxy` typed data.
pub const SAFE_FACTORY_NAME: &str = "Polymarket Contract Proxy Factory";
pub const CHAIN_ID: u64 = 137;
pub const RELAYER_BASE_URL: &str = "https://relayer-v2.polymarket.com";
pub const CTF_EXCHANGE: Address = address!;
pub const NEG_RISK_CTF_EXCHANGE: Address =
address!;
pub const NEG_RISK_ADAPTER: Address = address!;
pub const CONDITIONAL_TOKENS: Address = address!;
pub const USDC_E: Address = address!;
pub const PROXY_WALLET_FACTORY: Address =
address!;
pub const RELAY_HUB: Address = address!;
/// Gnosis Safe Factory (Polygon mainnet default).
pub const SAFE_FACTORY: Address = address!;
pub const SAFE_MULTISEND: Address = address!;
/// Safe init code hash for CREATE2 derivation (Polygon mainnet default).
pub const SAFE_INIT_CODE_HASH: =
hex!;
/// Proxy init code hash for CREATE2 derivation (Polygon mainnet default).
pub const PROXY_INIT_CODE_HASH: =
hex!;