-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapprovals.rs
More file actions
212 lines (180 loc) · 7.22 KB
/
approvals.rs
File metadata and controls
212 lines (180 loc) · 7.22 KB
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
#![allow(clippy::exhaustive_enums, reason = "Generated by sol! macro")]
#![allow(clippy::exhaustive_structs, reason = "Generated by sol! macro")]
#![allow(clippy::unwrap_used, reason = "Examples use unwrap for brevity")]
//! Token approval example for Kuest CLOB trading.
//!
//! This example demonstrates how to set the required token allowances for trading on Kuest.
//! You must approve three contracts:
//!
//! 1. **CTF Exchange** (`config.exchange`) - Standard market trading
//! 2. **Neg Risk CTF Exchange** (`neg_risk_config.exchange`) - Neg-risk market trading
//! 3. **Neg Risk Adapter** (`neg_risk_config.neg_risk_adapter`) - Token minting/splitting for neg-risk
//!
//! Each contract needs two approvals:
//! - ERC-20 approval for USDC (collateral token)
//! - ERC-1155 approval for Conditional Tokens (outcome tokens)
//!
//! You only need to run these approvals once per wallet.
//!
//! Run with tracing enabled:
//! ```sh
//! RUST_LOG=info,hyper_util=off,hyper=off,reqwest=off,h2=off,rustls=off cargo run --example approvals --features tracing
//! ```
//!
//! Dry run (no transactions executed):
//! ```sh
//! RUST_LOG=info cargo run --example approvals --features tracing -- --dry-run
//! ```
//!
//! Optionally log to a file:
//! ```sh
//! LOG_FILE=approvals.log RUST_LOG=info,hyper_util=off,hyper=off,reqwest=off,h2=off,rustls=off cargo run --example approvals --features tracing
//! ```
use std::env;
use std::fs::File;
use std::str::FromStr as _;
use alloy::primitives::U256;
use alloy::providers::ProviderBuilder;
use alloy::signers::Signer as _;
use alloy::signers::local::LocalSigner;
use alloy::sol;
use kuest_client_sdk::types::Address;
use kuest_client_sdk::{POLYGON, PRIVATE_KEY_VAR, contract_config};
use tracing::{error, info};
use tracing_subscriber::EnvFilter;
use tracing_subscriber::layer::SubscriberExt as _;
use tracing_subscriber::util::SubscriberInitExt as _;
const RPC_URL: &str = "https://polygon-rpc.com";
sol! {
#[sol(rpc)]
interface IERC20 {
function approve(address spender, uint256 value) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
}
#[sol(rpc)]
interface IERC1155 {
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address account, address operator) external view returns (bool);
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
if let Ok(path) = env::var("LOG_FILE") {
let file = File::create(path)?;
tracing_subscriber::registry()
.with(EnvFilter::from_default_env())
.with(
tracing_subscriber::fmt::layer()
.with_writer(file)
.with_ansi(false),
)
.init();
} else {
tracing_subscriber::fmt::init();
}
let args: Vec<String> = env::args().collect();
let dry_run = args.iter().any(|arg| arg == "--dry-run");
let chain = POLYGON;
let config = contract_config(chain, false).unwrap();
let neg_risk_config = contract_config(chain, true).unwrap();
// Collect all contracts that need approval
let mut targets: Vec<(&str, Address)> = vec![
("CTF Exchange", config.exchange),
("Neg Risk CTF Exchange", neg_risk_config.exchange),
];
// Add the Neg Risk Adapter if available
if let Some(adapter) = neg_risk_config.neg_risk_adapter {
targets.push(("Neg Risk Adapter", adapter));
}
if dry_run {
info!(mode = "dry_run", "showing approvals without executing");
for (name, target) in &targets {
info!(contract = name, address = %target, "would receive approval");
}
info!(total = targets.len(), "contracts would be approved");
return Ok(());
}
let private_key = env::var(PRIVATE_KEY_VAR).expect("Need a private key");
let signer = LocalSigner::from_str(&private_key)?.with_chain_id(Some(chain));
let provider = ProviderBuilder::new()
.wallet(signer.clone())
.connect(RPC_URL)
.await?;
let owner = signer.address();
info!(address = %owner, "wallet loaded");
let token = IERC20::new(config.collateral, provider.clone());
let ctf = IERC1155::new(config.conditional_tokens, provider.clone());
info!(phase = "checking", "querying current allowances");
for (name, target) in &targets {
match check_allowance(&token, owner, *target).await {
Ok(allowance) => info!(contract = name, usdc_allowance = %allowance),
Err(e) => error!(contract = name, error = ?e, "failed to check USDC allowance"),
}
match check_approval_for_all(&ctf, owner, *target).await {
Ok(approved) => info!(contract = name, ctf_approved = approved),
Err(e) => error!(contract = name, error = ?e, "failed to check CTF approval"),
}
}
info!(phase = "approving", "setting approvals");
for (name, target) in &targets {
info!(contract = name, address = %target, "approving");
match approve(&token, *target, U256::MAX).await {
Ok(tx_hash) => info!(contract = name, tx = %tx_hash, "USDC approved"),
Err(e) => error!(contract = name, error = ?e, "USDC approve failed"),
}
match set_approval_for_all(&ctf, *target, true).await {
Ok(tx_hash) => info!(contract = name, tx = %tx_hash, "CTF approved"),
Err(e) => error!(contract = name, error = ?e, "CTF setApprovalForAll failed"),
}
}
info!(phase = "verifying", "confirming approvals");
for (name, target) in &targets {
match check_allowance(&token, owner, *target).await {
Ok(allowance) => info!(contract = name, usdc_allowance = %allowance, "verified"),
Err(e) => error!(contract = name, error = ?e, "verification failed"),
}
match check_approval_for_all(&ctf, owner, *target).await {
Ok(approved) => info!(contract = name, ctf_approved = approved, "verified"),
Err(e) => error!(contract = name, error = ?e, "verification failed"),
}
}
info!("all approvals complete");
Ok(())
}
async fn check_allowance<P: alloy::providers::Provider>(
token: &IERC20::IERC20Instance<P>,
owner: Address,
spender: Address,
) -> anyhow::Result<U256> {
let allowance = token.allowance(owner, spender).call().await?;
Ok(allowance)
}
async fn check_approval_for_all<P: alloy::providers::Provider>(
ctf: &IERC1155::IERC1155Instance<P>,
account: Address,
operator: Address,
) -> anyhow::Result<bool> {
let approved = ctf.isApprovedForAll(account, operator).call().await?;
Ok(approved)
}
async fn approve<P: alloy::providers::Provider>(
usdc: &IERC20::IERC20Instance<P>,
spender: Address,
amount: U256,
) -> anyhow::Result<alloy::primitives::FixedBytes<32>> {
let tx_hash = usdc.approve(spender, amount).send().await?.watch().await?;
Ok(tx_hash)
}
async fn set_approval_for_all<P: alloy::providers::Provider>(
ctf: &IERC1155::IERC1155Instance<P>,
operator: Address,
approved: bool,
) -> anyhow::Result<alloy::primitives::FixedBytes<32>> {
let tx_hash = ctf
.setApprovalForAll(operator, approved)
.send()
.await?
.watch()
.await?;
Ok(tx_hash)
}