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, Vault};
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 internal vault positions for a market. Returns `(yes_lots, no_lots)`.
75    ///
76    /// For v1.1 markets that use internal position tracking instead of ERC1155.
77    pub async fn internal_positions(&self, market_id: u64, owner: Address) -> Result<(u128, u128)> {
78        let vault = Vault::new(self.config.addresses.vault, self.provider);
79        let result = vault
80            .positions(owner, U256::from(market_id))
81            .call()
82            .await
83            .map_err(|e| StrikeError::Contract(e.to_string()))?;
84        Ok((result.yesLots, result.noLots))
85    }
86
87    /// Check balances of YES and NO tokens for a market. Returns `(yes_balance, no_balance)`.
88    pub async fn balances(&self, market_id: u64, owner: Address) -> Result<(U256, U256)> {
89        let ot = OutcomeToken::new(self.config.addresses.outcome_token, self.provider);
90        let mid = U256::from(market_id);
91
92        let yes_id = ot
93            .yesTokenId(mid)
94            .call()
95            .await
96            .map_err(|e| StrikeError::Contract(e.to_string()))?;
97        let no_id = ot
98            .noTokenId(mid)
99            .call()
100            .await
101            .map_err(|e| StrikeError::Contract(e.to_string()))?;
102
103        let yes_bal = ot
104            .balanceOf(owner, yes_id)
105            .call()
106            .await
107            .map_err(|e| StrikeError::Contract(e.to_string()))?;
108        let no_bal = ot
109            .balanceOf(owner, no_id)
110            .call()
111            .await
112            .map_err(|e| StrikeError::Contract(e.to_string()))?;
113
114        Ok((yes_bal, no_bal))
115    }
116}