Skip to main content

strike_sdk/chain/
redeem.rs

1//! Redemption of outcome tokens for USDT after market resolution.
2
3use alloy::primitives::{Address, Bytes, U256};
4use alloy::providers::DynProvider;
5use alloy::rpc::types::TransactionRequest;
6use alloy::sol_types::SolCall;
7use std::sync::Arc;
8use tokio::sync::Mutex;
9use tracing::info;
10
11use crate::chain::send_tx;
12use crate::config::StrikeConfig;
13use crate::contracts::{OutcomeToken, RedemptionContract};
14use crate::error::{Result, StrikeError};
15use crate::nonce::NonceSender;
16
17/// Client for redeeming resolved market positions.
18pub struct RedeemClient<'a> {
19    provider: &'a DynProvider,
20    signer_addr: Option<Address>,
21    config: &'a StrikeConfig,
22    nonce_sender: Option<Arc<Mutex<NonceSender>>>,
23}
24
25impl<'a> RedeemClient<'a> {
26    pub(crate) fn new(
27        provider: &'a DynProvider,
28        signer_addr: Option<Address>,
29        config: &'a StrikeConfig,
30        nonce_sender: Option<Arc<Mutex<NonceSender>>>,
31    ) -> Self {
32        Self {
33            provider,
34            signer_addr,
35            config,
36            nonce_sender,
37        }
38    }
39
40    fn require_wallet(&self) -> Result<Address> {
41        self.signer_addr.ok_or(StrikeError::NoWallet)
42    }
43
44    /// Redeem outcome tokens for a resolved market.
45    ///
46    /// Calls `Redemption.redeem(factoryMarketId, amount)`. The contract determines
47    /// the winning side and burns the tokens, returning USDT.
48    pub async fn redeem(&self, market_id: u64, amount: U256) -> Result<()> {
49        self.require_wallet()?;
50
51        info!(market_id, amount = %amount, "redeeming outcome tokens");
52
53        let calldata = RedemptionContract::redeemCall {
54            factoryMarketId: U256::from(market_id),
55            amount,
56        }
57        .abi_encode();
58
59        let mut tx = TransactionRequest::default()
60            .to(self.config.addresses.redemption)
61            .input(Bytes::from(calldata).into());
62        tx.gas = Some(300_000);
63
64        let pending = send_tx(self.provider, &self.nonce_sender, tx).await?;
65        let receipt = pending
66            .get_receipt()
67            .await
68            .map_err(|e| StrikeError::Contract(e.to_string()))?;
69
70        info!(market_id, tx = %receipt.transaction_hash, gas_used = receipt.gas_used, "redemption confirmed");
71        Ok(())
72    }
73
74    /// Check balances of YES and NO tokens for a market. Returns `(yes_balance, no_balance)`.
75    pub async fn balances(&self, market_id: u64, owner: Address) -> Result<(U256, U256)> {
76        let ot = OutcomeToken::new(self.config.addresses.outcome_token, self.provider);
77        let mid = U256::from(market_id);
78
79        let yes_id = ot
80            .yesTokenId(mid)
81            .call()
82            .await
83            .map_err(|e| StrikeError::Contract(e.to_string()))?;
84        let no_id = ot
85            .noTokenId(mid)
86            .call()
87            .await
88            .map_err(|e| StrikeError::Contract(e.to_string()))?;
89
90        let yes_bal = ot
91            .balanceOf(owner, yes_id)
92            .call()
93            .await
94            .map_err(|e| StrikeError::Contract(e.to_string()))?;
95        let no_bal = ot
96            .balanceOf(owner, no_id)
97            .call()
98            .await
99            .map_err(|e| StrikeError::Contract(e.to_string()))?;
100
101        Ok((yes_bal, no_bal))
102    }
103}