#![allow(clippy::exhaustive_enums, reason = "Fine for examples")]
#![allow(clippy::exhaustive_structs, reason = "Fine for examples")]
use std::env;
use std::str::FromStr as _;
use alloy::primitives::{B256, U256};
use alloy::providers::ProviderBuilder;
use alloy::signers::Signer as _;
use alloy::signers::local::LocalSigner;
use anyhow::Result;
use polymarket_client_sdk::ctf::Client;
use polymarket_client_sdk::ctf::types::{
CollectionIdRequest, ConditionIdRequest, MergePositionsRequest, PositionIdRequest,
RedeemPositionsRequest, SplitPositionRequest,
};
use polymarket_client_sdk::types::address;
use polymarket_client_sdk::{POLYGON, PRIVATE_KEY_VAR};
use tracing::{error, info};
const RPC_URL: &str = "https://polygon-rpc.com";
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
let args: Vec<String> = env::args().collect();
let write_mode = args.iter().any(|arg| arg == "--write");
let chain = POLYGON;
info!("=== CTF (Conditional Token Framework) Example ===");
let provider = ProviderBuilder::new().connect(RPC_URL).await?;
let client = Client::new(provider, chain)?;
info!("Connected to Polygon {chain}");
info!("CTF contract: 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045");
info!("--- Calculating Condition ID ---");
let oracle = address!("0x0000000000000000000000000000000000000001");
let question_id = B256::ZERO;
let outcome_slot_count = U256::from(2);
let condition_req = ConditionIdRequest::builder()
.oracle(oracle)
.question_id(question_id)
.outcome_slot_count(outcome_slot_count)
.build();
let condition_resp = client.condition_id(&condition_req).await?;
info!("Oracle: {oracle}");
info!("Question ID: {question_id}");
info!("Outcome Slots: {outcome_slot_count}");
info!("→ Condition ID: {}", condition_resp.condition_id);
info!("--- Calculating Collection IDs ---");
let parent_collection_id = B256::ZERO;
let yes_collection_req = CollectionIdRequest::builder()
.parent_collection_id(parent_collection_id)
.condition_id(condition_resp.condition_id)
.index_set(U256::from(1))
.build();
let yes_collection_resp = client.collection_id(&yes_collection_req).await?;
info!("YES token (index set = 1):");
info!("→ Collection ID: {}", yes_collection_resp.collection_id);
let no_collection_req = CollectionIdRequest::builder()
.parent_collection_id(parent_collection_id)
.condition_id(condition_resp.condition_id)
.index_set(U256::from(2))
.build();
let no_collection_resp = client.collection_id(&no_collection_req).await?;
info!("NO token (index set = 2):");
info!("→ Collection ID: {}", no_collection_resp.collection_id);
info!("--- Calculating Position IDs ---");
let usdc = address!("0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174");
let yes_position_req = PositionIdRequest::builder()
.collateral_token(usdc)
.collection_id(yes_collection_resp.collection_id)
.build();
let yes_position_resp = client.position_id(&yes_position_req).await?;
info!(
"YES position (ERC1155 token ID): {}",
yes_position_resp.position_id
);
let no_position_req = PositionIdRequest::builder()
.collateral_token(usdc)
.collection_id(no_collection_resp.collection_id)
.build();
let no_position_resp = client.position_id(&no_position_req).await?;
info!(
"NO position (ERC1155 token ID): {}",
no_position_resp.position_id
);
if write_mode {
info!("--- Write Operations (requires wallet) ---");
let private_key =
env::var(PRIVATE_KEY_VAR).expect("Need a private key for write operations");
let signer = LocalSigner::from_str(&private_key)?.with_chain_id(Some(chain));
let provider = ProviderBuilder::new()
.wallet(signer.clone())
.connect(RPC_URL)
.await?;
let client = Client::new(provider, chain)?;
let wallet_address = signer.address();
info!("Using wallet: {wallet_address:?}");
info!("--- Splitting Position (Binary Market) ---");
info!("This will split 1 USDC into 1 YES and 1 NO token");
info!("Note: You must approve the CTF contract to spend your USDC first!");
let split_req = SplitPositionRequest::for_binary_market(
usdc,
condition_resp.condition_id,
U256::from(1_000_000), );
match client.split_position(&split_req).await {
Ok(split_resp) => {
info!("✓ Split transaction successful!");
info!(" Transaction hash: {}", split_resp.transaction_hash);
info!(" Block number: {}", split_resp.block_number);
}
Err(e) => {
error!("✗ Split failed: {e}");
error!(" Make sure you have approved the CTF contract and have sufficient USDC");
}
}
info!("--- Merging Positions (Binary Market) ---");
info!("This will merge 1 YES and 1 NO token back into 1 USDC");
let merge_req = MergePositionsRequest::for_binary_market(
usdc,
condition_resp.condition_id,
U256::from(1_000_000), );
match client.merge_positions(&merge_req).await {
Ok(merge_resp) => {
info!("✓ Merge transaction successful!");
info!(" Transaction hash: {}", merge_resp.transaction_hash);
info!(" Block number: {}", merge_resp.block_number);
}
Err(e) => {
error!("✗ Merge failed: {e}");
error!(" Make sure you have sufficient YES and NO tokens");
}
}
info!("--- Redeeming Positions ---");
info!("This redeems winning tokens after market resolution");
let redeem_req =
RedeemPositionsRequest::for_binary_market(usdc, condition_resp.condition_id);
match client.redeem_positions(&redeem_req).await {
Ok(redeem_resp) => {
info!("✓ Redeem transaction successful!");
info!(" Transaction hash: {}", redeem_resp.transaction_hash);
info!(" Block number: {}", redeem_resp.block_number);
}
Err(e) => {
error!("✗ Redeem failed: {e}");
error!(" Make sure the condition is resolved and you have winning tokens");
}
}
} else {
info!("--- Write Operations ---");
info!("To test write operations (split, merge, redeem), run with --write flag:");
info!(" export POLYMARKET_PRIVATE_KEY=\"your_private_key\"");
info!(" cargo run --example ctf --features ctf -- --write");
}
info!("=== Example Complete ===");
Ok(())
}