Skip to content

Commit 9bce2b1

Browse files
committed
route ctf proxy operations through v2 contracts
1 parent a88284d commit 9bce2b1

13 files changed

Lines changed: 483 additions & 278 deletions

File tree

Cargo.lock

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ path = "src/main.rs"
1515

1616
[dependencies]
1717
polymarket_client_sdk_v2 = { version = "0.5.1", features = ["gamma", "data", "bridge", "clob", "ctf"] }
18-
alloy = { version = "1.6.3", default-features = false, features = ["providers", "sol-types", "contract", "reqwest", "reqwest-rustls-tls", "signer-local", "signers"] }
18+
alloy = { version = "1.6.3", default-features = false, features = ["providers", "rpc-types", "sol-types", "contract", "reqwest", "reqwest-rustls-tls", "signer-local", "signers"] }
1919
clap = { version = "4", features = ["derive"] }
2020
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
2121
serde_json = "1"

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -332,14 +332,14 @@ polymarket data builder-volume --period month
332332

333333
### Contract Approvals
334334

335-
Before trading, Polymarket contracts need ERC-20 (USDC) and ERC-1155 (CTF token) approvals.
335+
Before trading, Polymarket contracts need ERC-20 (pUSD) and ERC-1155 (CTF token) approvals.
336336

337337
```bash
338338
# Check current approvals (read-only)
339339
polymarket approve check
340340
polymarket approve check 0xSOME_ADDRESS
341341

342-
# Approve all contracts (sends 6 on-chain transactions, needs MATIC for gas)
342+
# Approve all contracts (sends on-chain transactions, needs MATIC for gas)
343343
polymarket approve set
344344
```
345345

@@ -348,10 +348,10 @@ polymarket approve set
348348
Split, merge, and redeem conditional tokens directly on-chain.
349349

350350
```bash
351-
# Split $10 USDC into YES/NO tokens
351+
# Split $10 pUSD into YES/NO tokens
352352
polymarket ctf split --condition 0xCONDITION... --amount 10
353353

354-
# Merge tokens back to USDC
354+
# Merge tokens back to pUSD
355355
polymarket ctf merge --condition 0xCONDITION... --amount 10
356356

357357
# Redeem winning tokens after resolution
@@ -366,7 +366,7 @@ polymarket ctf collection-id --condition 0xCONDITION... --index-set 1
366366
polymarket ctf position-id --collection 0xCOLLECTION...
367367
```
368368

369-
`--amount` is in USDC (e.g., `10` = $10). The `--partition` flag defaults to binary (`1,2`). On-chain operations require MATIC for gas on Polygon.
369+
`--amount` is in pUSD (e.g., `10` = $10). The `--partition` flag defaults to binary (`1,2`). On-chain operations require MATIC for gas on Polygon.
370370

371371
### Bridge
372372

src/commands/approve.rs

Lines changed: 125 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@
33

44
use alloy::primitives::U256;
55
use alloy::sol;
6+
use alloy::sol_types::SolCall;
67
use anyhow::{Context, Result};
78
use clap::{Args, Subcommand};
8-
use polymarket_client_sdk_v2::types::{Address, address};
9-
use polymarket_client_sdk_v2::{POLYGON, contract_config};
9+
use polymarket_client_sdk_v2::types::Address;
1010

1111
use crate::auth;
1212
use crate::output::OutputFormat;
1313
use crate::output::approve::{ApprovalStatus, print_approval_status, print_tx_result};
1414

15-
/// Polygon USDC (same address as `USDC_ADDRESS_STR`; `address!` requires a literal).
16-
const USDC_ADDRESS: Address = address!("0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174");
15+
use super::{
16+
COLLATERAL_SYMBOL, CONDITIONAL_TOKENS, CTF_COLLATERAL_ADAPTER, CTF_EXCHANGE, NEG_RISK_ADAPTER,
17+
NEG_RISK_CTF_COLLATERAL_ADAPTER, NEG_RISK_CTF_EXCHANGE, proxy,
18+
};
1719

1820
sol! {
1921
#[sol(rpc)]
@@ -49,101 +51,132 @@ pub enum ApproveCommand {
4951
struct ApprovalTarget {
5052
name: &'static str,
5153
address: Address,
54+
needs_collateral_allowance: bool,
55+
needs_ctf_operator: bool,
5256
}
5357

5458
fn approval_targets() -> Result<Vec<ApprovalTarget>> {
55-
let config = contract_config(POLYGON, false).context("No contract config for Polygon")?;
56-
let neg_risk_config =
57-
contract_config(POLYGON, true).context("No neg-risk contract config for Polygon")?;
58-
59-
let mut targets = vec![
59+
Ok(vec![
6060
ApprovalTarget {
6161
name: "CTF Exchange",
62-
address: config.exchange,
62+
address: CTF_EXCHANGE,
63+
needs_collateral_allowance: true,
64+
needs_ctf_operator: true,
6365
},
6466
ApprovalTarget {
6567
name: "Neg Risk Exchange",
66-
address: neg_risk_config.exchange,
68+
address: NEG_RISK_CTF_EXCHANGE,
69+
needs_collateral_allowance: true,
70+
needs_ctf_operator: true,
6771
},
68-
];
69-
70-
if let Some(adapter) = neg_risk_config.neg_risk_adapter {
71-
targets.push(ApprovalTarget {
72+
ApprovalTarget {
7273
name: "Neg Risk Adapter",
73-
address: adapter,
74-
});
75-
}
76-
77-
Ok(targets)
74+
address: NEG_RISK_ADAPTER,
75+
needs_collateral_allowance: true,
76+
needs_ctf_operator: true,
77+
},
78+
ApprovalTarget {
79+
name: "Conditional Tokens",
80+
address: CONDITIONAL_TOKENS,
81+
needs_collateral_allowance: true,
82+
needs_ctf_operator: false,
83+
},
84+
ApprovalTarget {
85+
name: "CTF Collateral Adapter",
86+
address: CTF_COLLATERAL_ADAPTER,
87+
needs_collateral_allowance: true,
88+
needs_ctf_operator: true,
89+
},
90+
ApprovalTarget {
91+
name: "Neg Risk CTF Collateral Adapter",
92+
address: NEG_RISK_CTF_COLLATERAL_ADAPTER,
93+
needs_collateral_allowance: true,
94+
needs_ctf_operator: true,
95+
},
96+
])
7897
}
7998

8099
pub async fn execute(
81100
args: ApproveArgs,
82101
output: OutputFormat,
83102
private_key: Option<&str>,
103+
signature_type: Option<&str>,
84104
) -> Result<()> {
85105
match args.command {
86-
ApproveCommand::Check { address } => check(address, private_key, output).await,
87-
ApproveCommand::Set => set(private_key, output).await,
106+
ApproveCommand::Check { address } => {
107+
check(address, private_key, signature_type, output).await
108+
}
109+
ApproveCommand::Set => set(private_key, signature_type, output).await,
88110
}
89111
}
90112

91113
async fn check(
92114
address_arg: Option<Address>,
93115
private_key: Option<&str>,
116+
signature_type: Option<&str>,
94117
output: OutputFormat,
95118
) -> Result<()> {
96119
let owner: Address = if let Some(addr) = address_arg {
97120
addr
121+
} else if proxy::is_proxy_mode(signature_type)? {
122+
proxy::derive_proxy_address(private_key)?
98123
} else {
99124
let signer = auth::resolve_signer(private_key)?;
100125
polymarket_client_sdk_v2::auth::Signer::address(&signer)
101126
};
102127

103128
let provider = auth::create_readonly_provider().await?;
104-
let config = contract_config(POLYGON, false).context("No contract config for Polygon")?;
105-
106-
let usdc = IERC20::new(USDC_ADDRESS, provider.clone());
107-
let ctf = IERC1155::new(config.conditional_tokens, provider.clone());
129+
let collateral = IERC20::new(super::COLLATERAL_ADDRESS, provider.clone());
130+
let ctf = IERC1155::new(CONDITIONAL_TOKENS, provider.clone());
108131

109132
let targets = approval_targets()?;
110133
let mut statuses = Vec::new();
111134

112135
for target in &targets {
113-
let (usdc_allowance, usdc_error) = match usdc.allowance(owner, target.address).call().await
114-
{
115-
Ok(val) => (val, None),
116-
Err(e) => (U256::ZERO, Some(e.to_string())),
136+
let (collateral_allowance, collateral_error) = if target.needs_collateral_allowance {
137+
match collateral.allowance(owner, target.address).call().await {
138+
Ok(val) => (val, None),
139+
Err(e) => (U256::ZERO, Some(e.to_string())),
140+
}
141+
} else {
142+
(U256::ZERO, None)
117143
};
118144

119-
let (ctf_approved, ctf_error) =
145+
let (ctf_approved, ctf_error) = if target.needs_ctf_operator {
120146
match ctf.isApprovedForAll(owner, target.address).call().await {
121-
Ok(val) => (val, None),
122-
Err(e) => (false, Some(e.to_string())),
123-
};
147+
Ok(val) => (Some(val), None),
148+
Err(e) => (Some(false), Some(e.to_string())),
149+
}
150+
} else {
151+
(None, None)
152+
};
124153

125154
statuses.push(ApprovalStatus {
126155
contract_name: target.name.to_string(),
127156
contract_address: format!("{}", target.address),
128-
usdc_allowance,
157+
collateral_allowance,
129158
ctf_approved,
130-
usdc_error,
159+
collateral_error,
131160
ctf_error,
132161
});
133162
}
134163

135164
print_approval_status(&statuses, &output)
136165
}
137166

138-
async fn set(private_key: Option<&str>, output: OutputFormat) -> Result<()> {
139-
let provider = auth::create_provider(private_key).await?;
140-
let config = contract_config(POLYGON, false).context("No contract config for Polygon")?;
141-
142-
let usdc = IERC20::new(USDC_ADDRESS, provider.clone());
143-
let ctf = IERC1155::new(config.conditional_tokens, provider.clone());
144-
167+
async fn set(
168+
private_key: Option<&str>,
169+
signature_type: Option<&str>,
170+
output: OutputFormat,
171+
) -> Result<()> {
172+
let use_proxy = proxy::is_proxy_mode(signature_type)?;
145173
let targets = approval_targets()?;
146-
let total = targets.len() * 2;
174+
let total = targets
175+
.iter()
176+
.map(|target| {
177+
usize::from(target.needs_collateral_allowance) + usize::from(target.needs_ctf_operator)
178+
})
179+
.sum();
147180

148181
if matches!(output, OutputFormat::Table) {
149182
println!("Approving contracts...\n");
@@ -153,52 +186,56 @@ async fn set(private_key: Option<&str>, output: OutputFormat) -> Result<()> {
153186
let mut step = 0;
154187

155188
for target in &targets {
156-
step += 1;
157-
let label = format!("USDC \u{2192} {}", target.name);
158-
let tx_hash = usdc
159-
.approve(target.address, U256::MAX)
160-
.send()
161-
.await
162-
.context(format!("Failed to send USDC approval for {}", target.name))?
163-
.watch()
164-
.await
165-
.context(format!(
166-
"Failed to confirm USDC approval for {}",
167-
target.name
168-
))?;
169-
170-
match output {
171-
OutputFormat::Table => print_tx_result(step, total, &label, tx_hash),
172-
OutputFormat::Json => results.push(serde_json::json!({
173-
"step": step,
174-
"type": "erc20",
175-
"contract": target.name,
176-
"tx_hash": format!("{tx_hash}"),
177-
})),
189+
if target.needs_collateral_allowance {
190+
step += 1;
191+
let label = format!("{COLLATERAL_SYMBOL} \u{2192} {}", target.name);
192+
let calldata = IERC20::approveCall {
193+
spender: target.address,
194+
value: U256::MAX,
195+
}
196+
.abi_encode();
197+
let (tx_hash, _) =
198+
proxy::send_call(private_key, use_proxy, super::COLLATERAL_ADDRESS, calldata)
199+
.await
200+
.context(format!(
201+
"Failed {COLLATERAL_SYMBOL} approval for {}",
202+
target.name
203+
))?;
204+
205+
match output {
206+
OutputFormat::Table => print_tx_result(step, total, &label, tx_hash),
207+
OutputFormat::Json => results.push(serde_json::json!({
208+
"step": step,
209+
"type": "erc20",
210+
"asset": COLLATERAL_SYMBOL,
211+
"contract": target.name,
212+
"tx_hash": format!("{tx_hash}"),
213+
})),
214+
}
178215
}
179216

180-
step += 1;
181-
let label = format!("CTF \u{2192} {}", target.name);
182-
let tx_hash = ctf
183-
.setApprovalForAll(target.address, true)
184-
.send()
185-
.await
186-
.context(format!("Failed to send CTF approval for {}", target.name))?
187-
.watch()
188-
.await
189-
.context(format!(
190-
"Failed to confirm CTF approval for {}",
191-
target.name
192-
))?;
193-
194-
match output {
195-
OutputFormat::Table => print_tx_result(step, total, &label, tx_hash),
196-
OutputFormat::Json => results.push(serde_json::json!({
197-
"step": step,
198-
"type": "erc1155",
199-
"contract": target.name,
200-
"tx_hash": format!("{tx_hash}"),
201-
})),
217+
if target.needs_ctf_operator {
218+
step += 1;
219+
let label = format!("CTF \u{2192} {}", target.name);
220+
let calldata = IERC1155::setApprovalForAllCall {
221+
operator: target.address,
222+
approved: true,
223+
}
224+
.abi_encode();
225+
let (tx_hash, _) =
226+
proxy::send_call(private_key, use_proxy, CONDITIONAL_TOKENS, calldata)
227+
.await
228+
.context(format!("Failed CTF approval for {}", target.name))?;
229+
230+
match output {
231+
OutputFormat::Table => print_tx_result(step, total, &label, tx_hash),
232+
OutputFormat::Json => results.push(serde_json::json!({
233+
"step": step,
234+
"type": "erc1155",
235+
"contract": target.name,
236+
"tx_hash": format!("{tx_hash}"),
237+
})),
238+
}
202239
}
203240
}
204241

src/commands/clob.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ pub enum ClobCommand {
244244
/// Side: buy or sell
245245
#[arg(long)]
246246
side: CliSide,
247-
/// Amount (USDC for buys, shares for sells)
247+
/// Amount (pUSD for buys, shares for sells)
248248
#[arg(long)]
249249
amount: String,
250250
/// Order type: FOK or FAK (default: FOK)

0 commit comments

Comments
 (0)