polymarket_client/secure/
wallet.rs1use std::str::FromStr as _;
4
5use alloy::providers::ProviderBuilder;
6use polymarket_client_sdk_v2::ctf::types::{
7 MergePositionsRequest as SdkMergeRequest, RedeemPositionsRequest as SdkRedeemRequest,
8 SplitPositionRequest as SdkSplitRequest,
9};
10use polymarket_client_sdk_v2::ctf::Client as CtfClient;
11use polymarket_client_sdk_v2::types::{Address, B256, U256};
12use polymarket_client_sdk_v2::POLYGON;
13
14use crate::error::{user_input, UserInputError};
15use crate::secure::secure_client::SecureClient;
16
17#[derive(Debug, thiserror::Error, Clone)]
18pub enum WalletOperationError {
19 #[error(transparent)]
20 UserInput(#[from] UserInputError),
21 #[error("on-chain error: {0}")]
22 OnChain(String),
23}
24
25#[derive(Clone, Debug)]
26pub struct TransactionOutcome {
27 pub transaction_hash: String,
28 pub block_number: u64,
29}
30
31#[derive(Clone, Debug)]
32pub struct SplitPositionRequest {
33 pub condition_id: String,
34 pub amount: u128,
36}
37
38#[derive(Clone, Debug)]
39pub struct MergePositionsRequest {
40 pub condition_id: String,
41 pub amount: u128,
42}
43
44#[derive(Clone, Debug, Default)]
45pub struct RedeemPositionsRequest {
46 pub condition_id: String,
47 pub index_sets: Vec<u64>,
49}
50
51impl SecureClient {
52 pub async fn split_position(
53 &self,
54 request: SplitPositionRequest,
55 ) -> Result<TransactionOutcome, WalletOperationError> {
56 validate_amount(request.amount)?;
57 let client = self.ctf_client().await?;
58 let condition_id = parse_condition_id(&request.condition_id)?;
59 let collateral = collateral_token(self)?;
60
61 let sdk_request = SdkSplitRequest::for_binary_market(
62 collateral,
63 condition_id,
64 U256::from(request.amount),
65 );
66
67 let response = client
68 .split_position(&sdk_request)
69 .await
70 .map_err(|e| WalletOperationError::OnChain(e.to_string()))?;
71
72 Ok(TransactionOutcome {
73 transaction_hash: response.transaction_hash.to_string(),
74 block_number: response.block_number,
75 })
76 }
77
78 pub async fn merge_positions(
79 &self,
80 request: MergePositionsRequest,
81 ) -> Result<TransactionOutcome, WalletOperationError> {
82 validate_amount(request.amount)?;
83 let client = self.ctf_client().await?;
84 let condition_id = parse_condition_id(&request.condition_id)?;
85 let collateral = collateral_token(self)?;
86
87 let sdk_request = SdkMergeRequest::for_binary_market(
88 collateral,
89 condition_id,
90 U256::from(request.amount),
91 );
92
93 let response = client
94 .merge_positions(&sdk_request)
95 .await
96 .map_err(|e| WalletOperationError::OnChain(e.to_string()))?;
97
98 Ok(TransactionOutcome {
99 transaction_hash: response.transaction_hash.to_string(),
100 block_number: response.block_number,
101 })
102 }
103
104 pub async fn redeem_positions(
105 &self,
106 request: RedeemPositionsRequest,
107 ) -> Result<TransactionOutcome, WalletOperationError> {
108 let client = self.ctf_client().await?;
109 let condition_id = parse_condition_id(&request.condition_id)?;
110 let collateral = collateral_token(self)?;
111
112 let sdk_request = SdkRedeemRequest::for_binary_market(collateral, condition_id);
113
114 let response = client
115 .redeem_positions(&sdk_request)
116 .await
117 .map_err(|e| WalletOperationError::OnChain(e.to_string()))?;
118
119 Ok(TransactionOutcome {
120 transaction_hash: response.transaction_hash.to_string(),
121 block_number: response.block_number,
122 })
123 }
124
125 async fn ctf_client(
126 &self,
127 ) -> Result<CtfClient<impl alloy::providers::Provider + Clone>, WalletOperationError> {
128 let provider = ProviderBuilder::new()
129 .wallet(self.signer.clone())
130 .connect(self.environment().rpc)
131 .await
132 .map_err(|e| WalletOperationError::OnChain(e.to_string()))?;
133
134 CtfClient::new(provider, POLYGON).map_err(|e| WalletOperationError::OnChain(e.to_string()))
135 }
136}
137
138fn collateral_token(client: &SecureClient) -> Result<Address, WalletOperationError> {
139 Address::from_str(client.environment().collateral_token.as_str()).map_err(|e| {
140 WalletOperationError::UserInput(user_input(format!("invalid collateral token: {e}")))
141 })
142}
143
144fn parse_condition_id(value: &str) -> Result<B256, UserInputError> {
145 B256::from_str(value).map_err(|e| user_input(format!("invalid condition_id: {e}")))
146}
147
148fn validate_amount(amount: u128) -> Result<(), UserInputError> {
149 if amount == 0 {
150 return Err(user_input("amount must be greater than zero"));
151 }
152 Ok(())
153}