Skip to main content

onemoney_protocol/api/
tokens.rs

1//! Token-related API operations.
2
3use alloy_primitives::Address;
4use om_primitives_types::transaction::payload::{
5    TokenAuthorityPayload, TokenBlacklistPayload, TokenBurnPayload, TokenMetadataUpdatePayload, TokenMintPayload,
6    TokenPausePayload, TokenWhitelistPayload,
7};
8use om_rest_types::{
9    requests::{
10        TokenAuthorityRequest, TokenBlacklistRequest, TokenBurnRequest, TokenMetadataUpdateRequest, TokenMintRequest,
11        TokenPauseRequest, TokenWhitelistRequest,
12    },
13    responses::{MintInfo, TransactionResponse},
14};
15
16use crate::{
17    client::{
18        Client,
19        config::{
20            api_path,
21            endpoints::tokens::{
22                BURN, GRANT_AUTHORITY, MANAGE_BLACKLIST, MANAGE_WHITELIST, MINT, PAUSE, TOKEN_METADATA, UPDATE_METADATA,
23            },
24        },
25    },
26    crypto::sign_transaction_payload,
27    error::Result,
28};
29
30impl Client {
31    /// Mint tokens to an account.
32    ///
33    /// # Arguments
34    ///
35    /// * `payload` - Token mint parameters
36    /// * `private_key` - Private key for signing the transaction (must have
37    ///   mint authority)
38    ///
39    /// # Returns
40    ///
41    /// The transaction result.
42    pub async fn mint_token(&self, data: TokenMintPayload, private_key: &str) -> Result<TransactionResponse> {
43        let signature = sign_transaction_payload(&data, private_key)?;
44        let request = TokenMintRequest { data, signature };
45
46        self.post(&api_path(MINT), &request).await
47    }
48
49    /// Burn tokens from an account.
50    ///
51    /// # Arguments
52    ///
53    /// * `payload` - Token burn parameters
54    /// * `private_key` - Private key for signing the transaction (must have
55    ///   burn authority)
56    ///
57    /// # Returns
58    ///
59    /// The transaction result.
60    pub async fn burn_token(&self, data: TokenBurnPayload, private_key: &str) -> Result<TransactionResponse> {
61        let signature = sign_transaction_payload(&data, private_key)?;
62        let request = TokenBurnRequest { data, signature };
63
64        self.post(&api_path(BURN), &request).await
65    }
66
67    /// Grant authority for a token to an address.
68    ///
69    /// # Arguments
70    ///
71    /// * `payload` - Authority grant parameters
72    /// * `private_key` - Private key for signing the transaction (must have
73    ///   master authority)
74    ///
75    /// # Returns
76    ///
77    /// The transaction result.
78    pub async fn grant_authority(&self, data: TokenAuthorityPayload, private_key: &str) -> Result<TransactionResponse> {
79        let signature = sign_transaction_payload(&data, private_key)?;
80        let request = TokenAuthorityRequest { data, signature };
81
82        self.post(&api_path(GRANT_AUTHORITY), &request).await
83    }
84
85    /// Revoke authority for a token from an address.
86    ///
87    /// Note: This method uses the same `/v1/tokens/grant_authority` endpoint as
88    /// grant_authority(), but with `AuthorityAction::Revoke` in the payload
89    /// to indicate a revoke operation.
90    ///
91    /// # Arguments
92    ///
93    /// * `payload` - Authority revoke parameters (with action set to
94    ///   AuthorityAction::Revoke)
95    /// * `private_key` - Private key for signing the transaction (must have
96    ///   master authority)
97    ///
98    /// # Returns
99    ///
100    /// The transaction result.
101    pub async fn revoke_authority(
102        &self,
103        data: TokenAuthorityPayload,
104        private_key: &str,
105    ) -> Result<TransactionResponse> {
106        let signature = sign_transaction_payload(&data, private_key)?;
107        let request = TokenAuthorityRequest { data, signature };
108
109        self.post(&api_path(GRANT_AUTHORITY), &request).await
110    }
111
112    /// Get token metadata by mint address.
113    ///
114    /// # Arguments
115    ///
116    /// * `mint_address` - The token mint address
117    ///
118    /// # Returns
119    ///
120    /// The token metadata.
121    ///
122    /// # Example
123    ///
124    /// ```rust,no_run
125    /// use std::str::FromStr;
126    ///
127    /// use alloy_primitives::Address;
128    /// use onemoney_protocol::Client;
129    ///
130    /// #[tokio::main]
131    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
132    ///     let client = Client::mainnet()?;
133    ///     let mint = Address::from_str("0x1234567890abcdef1234567890abcdef12345678")?;
134    ///
135    ///     let mint_info = client.get_token_metadata(mint).await?;
136    ///     println!("Token: {}", mint_info.symbol);
137    ///
138    ///     Ok(())
139    /// }
140    /// ```
141    pub async fn get_token_metadata(&self, mint_address: Address) -> Result<MintInfo> {
142        let path = api_path(&format!("{}?token={}", TOKEN_METADATA, mint_address));
143        let response: MintInfo = self.get(&path).await?;
144        Ok(response)
145    }
146
147    /// Pause or unpause a token.
148    ///
149    /// # Arguments
150    ///
151    /// * `payload` - Token pause parameters
152    /// * `private_key` - Private key for signing the transaction (must have
153    ///   pause authority)
154    ///
155    /// # Returns
156    ///
157    /// The transaction result.
158    pub async fn pause_token(&self, data: TokenPausePayload, private_key: &str) -> Result<TransactionResponse> {
159        let signature = sign_transaction_payload(&data, private_key)?;
160        let request = TokenPauseRequest { data, signature };
161
162        self.post(&api_path(PAUSE), &request).await
163    }
164
165    /// Manage token blacklist (add or remove addresses).
166    ///
167    /// # Arguments
168    ///
169    /// * `payload` - Token blacklist management parameters
170    /// * `private_key` - Private key for signing the transaction (must have
171    ///   manage list authority)
172    ///
173    /// # Returns
174    ///
175    /// The transaction result.
176    pub async fn manage_blacklist(
177        &self,
178        data: TokenBlacklistPayload,
179        private_key: &str,
180    ) -> Result<TransactionResponse> {
181        let signature = sign_transaction_payload(&data, private_key)?;
182        let request = TokenBlacklistRequest { data, signature };
183
184        self.post(&api_path(MANAGE_BLACKLIST), &request).await
185    }
186
187    /// Manage token whitelist (add or remove addresses).
188    ///
189    /// # Arguments
190    ///
191    /// * `payload` - Token whitelist management parameters
192    /// * `private_key` - Private key for signing the transaction (must have
193    ///   manage list authority)
194    ///
195    /// # Returns
196    ///
197    /// The transaction result.
198    pub async fn manage_whitelist(
199        &self,
200        data: TokenWhitelistPayload,
201        private_key: &str,
202    ) -> Result<TransactionResponse> {
203        let signature = sign_transaction_payload(&data, private_key)?;
204        let request = TokenWhitelistRequest { data, signature };
205
206        self.post(&api_path(MANAGE_WHITELIST), &request).await
207    }
208
209    /// Update token metadata.
210    ///
211    /// # Arguments
212    ///
213    /// * `payload` - Token metadata update parameters
214    /// * `private_key` - Private key for signing the transaction (must have
215    ///   update metadata authority)
216    ///
217    /// # Returns
218    ///
219    /// The transaction result.
220    pub async fn update_token_metadata(
221        &self,
222        data: TokenMetadataUpdatePayload,
223        private_key: &str,
224    ) -> Result<TransactionResponse> {
225        let signature = sign_transaction_payload(&data, private_key)?;
226        let request = TokenMetadataUpdateRequest { data, signature };
227
228        self.post(&api_path(UPDATE_METADATA), &request).await
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use std::str::FromStr;
235
236    use alloy_primitives::{Address, U256};
237    use om_primitives_types::transaction::payload::{
238        Authority, AuthorityAction, BlacklistAction, PauseAction, WhitelistAction,
239    };
240
241    use super::*;
242    use crate::NamedChain;
243
244    #[test]
245    fn test_authority_values() {
246        assert_eq!(
247            serde_json::to_string(&Authority::MasterMintBurn).expect("Test data should be valid"),
248            "\"MasterMintBurn\""
249        );
250        assert_eq!(
251            serde_json::to_string(&Authority::MintBurnTokens).expect("Test data should be valid"),
252            "\"MintBurnTokens\""
253        );
254        assert_eq!(
255            serde_json::to_string(&Authority::Pause).expect("Test data should be valid"),
256            "\"Pause\""
257        );
258        assert_eq!(
259            serde_json::to_string(&Authority::ManageList).expect("Test data should be valid"),
260            "\"ManageList\""
261        );
262        assert_eq!(
263            serde_json::to_string(&Authority::UpdateMetadata).expect("Test data should be valid"),
264            "\"UpdateMetadata\""
265        );
266    }
267
268    #[test]
269    fn test_token_mint_payload_structure() {
270        let address =
271            Address::from_str("0x742d35Cc6634C0532925a3b8D91D6F4A81B8Cbc0").expect("Test data should be valid");
272        let token = Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid");
273
274        let payload = TokenMintPayload {
275            chain_id: NamedChain::TESTNET_CHAIN_ID,
276            nonce: 5,
277            recipient: address,
278            value: U256::from(1000000000000000000u64),
279            token,
280        };
281
282        assert_eq!(payload.chain_id, NamedChain::TESTNET_CHAIN_ID);
283        assert_eq!(payload.nonce, 5);
284        assert_eq!(payload.recipient, address);
285        assert_eq!(payload.value, U256::from(1000000000000000000u64));
286        assert_eq!(payload.token, token);
287    }
288
289    #[test]
290    fn test_token_burn_payload_structure() {
291        let token = Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid");
292
293        let payload = TokenBurnPayload {
294            chain_id: NamedChain::TESTNET_CHAIN_ID,
295            nonce: 5,
296            value: U256::from(500000000000000000u64),
297            token,
298        };
299
300        assert_eq!(payload.value, U256::from(500000000000000000u64));
301    }
302
303    #[test]
304    fn test_authority_action_serialization() {
305        assert_eq!(
306            serde_json::to_string(&AuthorityAction::Grant).expect("Test data should be valid"),
307            "\"Grant\""
308        );
309        assert_eq!(
310            serde_json::to_string(&AuthorityAction::Revoke).expect("Test data should be valid"),
311            "\"Revoke\""
312        );
313    }
314
315    #[test]
316    fn test_pause_action_serialization() {
317        assert_eq!(
318            serde_json::to_string(&PauseAction::Pause).expect("Test data should be valid"),
319            "\"Pause\""
320        );
321        assert_eq!(
322            serde_json::to_string(&PauseAction::Unpause).expect("Test data should be valid"),
323            "\"Unpause\""
324        );
325    }
326
327    #[test]
328    fn test_blacklist_action_serialization() {
329        assert_eq!(
330            serde_json::to_string(&BlacklistAction::Add).expect("Test data should be valid"),
331            "\"Add\""
332        );
333        assert_eq!(
334            serde_json::to_string(&BlacklistAction::Remove).expect("Test data should be valid"),
335            "\"Remove\""
336        );
337    }
338
339    #[test]
340    fn test_whitelist_action_serialization() {
341        assert_eq!(
342            serde_json::to_string(&WhitelistAction::Add).expect("Test data should be valid"),
343            "\"Add\""
344        );
345        assert_eq!(
346            serde_json::to_string(&WhitelistAction::Remove).expect("Test data should be valid"),
347            "\"Remove\""
348        );
349    }
350
351    #[test]
352    fn test_token_authority_payload_structure() {
353        let authority_address =
354            Address::from_str("0x742d35Cc6634C0532925a3b8D91D6F4A81B8Cbc0").expect("Test data should be valid");
355        let token = Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid");
356
357        let payload = TokenAuthorityPayload {
358            chain_id: NamedChain::TESTNET_CHAIN_ID,
359            nonce: 5,
360            action: AuthorityAction::Grant,
361            authority_type: Authority::MintBurnTokens,
362            authority_address,
363            token,
364            value: U256::from(1000000000000000000u64),
365        };
366
367        assert_eq!(payload.action, AuthorityAction::Grant);
368        assert_eq!(payload.authority_type, Authority::MintBurnTokens);
369        assert_eq!(payload.authority_address, authority_address);
370    }
371
372    #[test]
373    fn test_token_pause_payload_structure() {
374        let token = Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid");
375
376        let payload = TokenPausePayload {
377            chain_id: NamedChain::TESTNET_CHAIN_ID,
378            nonce: 5,
379            action: PauseAction::Pause,
380            token,
381        };
382
383        assert_eq!(payload.action, PauseAction::Pause);
384        assert_eq!(payload.token, token);
385    }
386
387    #[test]
388    fn test_token_blacklist_payload_structure() {
389        let address =
390            Address::from_str("0x742d35Cc6634C0532925a3b8D91D6F4A81B8Cbc0").expect("Test data should be valid");
391        let token = Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid");
392
393        let payload = TokenBlacklistPayload {
394            chain_id: NamedChain::TESTNET_CHAIN_ID,
395            nonce: 5,
396            action: BlacklistAction::Add,
397            address,
398            token,
399        };
400
401        assert_eq!(payload.action, BlacklistAction::Add);
402        assert_eq!(payload.address, address);
403        assert_eq!(payload.token, token);
404    }
405
406    #[test]
407    fn test_token_whitelist_payload_structure() {
408        let address =
409            Address::from_str("0x742d35Cc6634C0532925a3b8D91D6F4A81B8Cbc0").expect("Test data should be valid");
410        let token = Address::from_str("0x1234567890abcdef1234567890abcdef12345678").expect("Test data should be valid");
411
412        let payload = TokenWhitelistPayload {
413            chain_id: NamedChain::TESTNET_CHAIN_ID,
414            nonce: 5,
415            action: WhitelistAction::Add,
416            address,
417            token,
418        };
419
420        assert_eq!(payload.action, WhitelistAction::Add);
421        assert_eq!(payload.address, address);
422        assert_eq!(payload.token, token);
423    }
424}