Skip to main content

cdk_ffi/
token.rs

1//! FFI token bindings
2
3use std::collections::BTreeSet;
4use std::str::FromStr;
5
6use crate::error::FfiError;
7use crate::{Amount, CurrencyUnit, KeySetInfo, MintUrl, Proofs};
8
9/// FFI-compatible Token
10#[derive(Debug, uniffi::Object)]
11pub struct Token {
12    pub(crate) inner: cdk::nuts::Token,
13}
14
15impl std::fmt::Display for Token {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        write!(f, "{}", self.inner)
18    }
19}
20
21impl FromStr for Token {
22    type Err = FfiError;
23
24    fn from_str(s: &str) -> Result<Self, Self::Err> {
25        let token = cdk::nuts::Token::from_str(s)
26            .map_err(|e| FfiError::internal(format!("Invalid token: {}", e)))?;
27        Ok(Token { inner: token })
28    }
29}
30
31impl From<cdk::nuts::Token> for Token {
32    fn from(token: cdk::nuts::Token) -> Self {
33        Self { inner: token }
34    }
35}
36
37impl From<Token> for cdk::nuts::Token {
38    fn from(token: Token) -> Self {
39        token.inner
40    }
41}
42
43#[uniffi::export]
44impl Token {
45    /// Create a new Token from string
46    #[uniffi::constructor]
47    pub fn from_string(encoded_token: String) -> Result<Token, FfiError> {
48        let token = cdk::nuts::Token::from_str(&encoded_token)
49            .map_err(|e| FfiError::internal(format!("Invalid token: {}", e)))?;
50        Ok(Token { inner: token })
51    }
52
53    /// Get the total value of the token
54    pub fn value(&self) -> Result<Amount, FfiError> {
55        Ok(self.inner.value()?.into())
56    }
57
58    /// Get the memo from the token
59    pub fn memo(&self) -> Option<String> {
60        self.inner.memo().clone()
61    }
62
63    /// Get the currency unit
64    pub fn unit(&self) -> Option<CurrencyUnit> {
65        self.inner.unit().map(Into::into)
66    }
67
68    /// Get the mint URL
69    pub fn mint_url(&self) -> Result<MintUrl, FfiError> {
70        Ok(self.inner.mint_url()?.into())
71    }
72
73    /// Get proofs from the token (simplified - no keyset filtering for now)
74    pub fn proofs_simple(&self) -> Result<Proofs, FfiError> {
75        // For now, return empty keysets to get all proofs
76        let empty_keysets = vec![];
77        let proofs = self.inner.proofs(&empty_keysets)?;
78        Ok(proofs.into_iter().map(|p| p.into()).collect())
79    }
80
81    /// Get proofs from the token
82    pub fn proofs(&self, mint_keysets: Vec<KeySetInfo>) -> Result<Proofs, FfiError> {
83        let mint_keysets: Vec<_> = mint_keysets
84            .into_iter()
85            .map(TryInto::try_into)
86            .collect::<Result<_, _>>()?;
87        let proofs = self.inner.proofs(&mint_keysets)?;
88        Ok(proofs.into_iter().map(|p| p.into()).collect())
89    }
90
91    /// Convert token to raw bytes
92    pub fn to_raw_bytes(&self) -> Result<Vec<u8>, FfiError> {
93        Ok(self.inner.to_raw_bytes()?)
94    }
95
96    /// Encode token to string representation
97    pub fn encode(&self) -> String {
98        self.to_string()
99    }
100
101    /// Decode token from raw bytes
102    #[uniffi::constructor]
103    pub fn from_raw_bytes(bytes: Vec<u8>) -> Result<Token, FfiError> {
104        let token = cdk::nuts::Token::try_from(&bytes)?;
105        Ok(Token { inner: token })
106    }
107
108    /// Decode token from string representation
109    #[uniffi::constructor]
110    pub fn decode(encoded_token: String) -> Result<Token, FfiError> {
111        encoded_token.parse()
112    }
113
114    /// Return unique spending conditions across all proofs in this token
115    pub fn spending_conditions(&self) -> Vec<crate::types::SpendingConditions> {
116        self.inner
117            .spending_conditions()
118            .map(|set| set.into_iter().map(Into::into).collect())
119            .unwrap_or_default()
120    }
121
122    /// Return all P2PK pubkeys referenced by this token's spending conditions
123    pub fn p2pk_pubkeys(&self) -> Vec<String> {
124        let set = self
125            .inner
126            .p2pk_pubkeys()
127            .map(|keys| {
128                keys.into_iter()
129                    .map(|k| k.to_string())
130                    .collect::<BTreeSet<_>>()
131            })
132            .unwrap_or_default();
133        set.into_iter().collect()
134    }
135
136    /// Return all refund pubkeys from P2PK spending conditions
137    pub fn p2pk_refund_pubkeys(&self) -> Vec<String> {
138        let set = self
139            .inner
140            .p2pk_refund_pubkeys()
141            .map(|keys| {
142                keys.into_iter()
143                    .map(|k| k.to_string())
144                    .collect::<BTreeSet<_>>()
145            })
146            .unwrap_or_default();
147        set.into_iter().collect()
148    }
149
150    /// Return all HTLC hashes from spending conditions
151    pub fn htlc_hashes(&self) -> Vec<String> {
152        let set = self
153            .inner
154            .htlc_hashes()
155            .map(|hashes| {
156                hashes
157                    .into_iter()
158                    .map(|h| h.to_string())
159                    .collect::<BTreeSet<_>>()
160            })
161            .unwrap_or_default();
162        set.into_iter().collect()
163    }
164
165    /// Return all locktimes from spending conditions (sorted ascending)
166    pub fn locktimes(&self) -> Vec<u64> {
167        self.inner
168            .locktimes()
169            .map(|s| s.into_iter().collect())
170            .unwrap_or_default()
171    }
172}