Skip to main content

bity_ic_icrc3_macros/
lib.rs

1//! Module for managing ICRC3 state in Internet Computer canisters.
2//!
3//! This module provides macros for creating thread-safe ICRC3 state management in canisters,
4//! with functions for initialization and direct access to ICRC3 interface methods.
5
6/// A macro that generates thread-safe ICRC3 state management functions.
7///
8/// This macro creates a set of functions for managing ICRC3 state in a thread-safe manner.
9/// It provides direct access to all ICRC3 interface methods.
10///
11/// # Generated Functions
12/// * `init_icrc3()` - Initializes the ICRC3 state
13/// * `add_transaction(transaction: T) -> Result<u64, Icrc3Error>` - Adds a new transaction
14/// * `icrc3_get_archives() -> Vec<ICRC3ArchiveInfo>` - Gets information about archives
15/// * `icrc3_get_blocks(args: Vec<GetBlocksRequest>) -> Response` - Gets blocks
16/// * `icrc3_get_properties() -> Response` - Gets blockchain properties
17/// * `icrc3_get_tip_certificate() -> ICRC3DataCertificate` - Gets the tip certificate
18/// * `icrc3_supported_block_types() -> Vec<SupportedBlockType>` - Gets supported block types
19/// * `upgrade_archive_wasm(wasm_module: Vec<u8>)` - Upgrades the archive canister WASM
20///
21/// # Example
22/// ```
23/// use icrc3_library::icrc3_macros::icrc3_state;
24///
25/// icrc3_state!();
26///
27/// fn add_transaction(transaction: MyTransaction) -> Result<u64, Icrc3Error> {
28///     add_transaction(transaction)
29/// }
30///
31/// fn get_archives() -> Vec<ICRC3ArchiveInfo> {
32///     icrc3_get_archives()
33/// }
34/// ```
35///
36///
37// icrc3_macros/src/lib.rs
38extern crate proc_macro;
39
40use proc_macro::TokenStream;
41use quote::quote;
42
43#[proc_macro]
44pub fn icrc3_state(_input: TokenStream) -> TokenStream {
45    let expanded = quote! {
46        use lazy_static::lazy_static;
47        use std::sync::{Arc, RwLock};
48        use icrc_ledger_types::icrc3::blocks::{GetBlocksResult, GetBlocksRequest, ICRC3DataCertificate, SupportedBlockType};
49        use icrc_ledger_types::icrc3::archive::ICRC3ArchiveInfo;
50        use bity_ic_icrc3::{config::{ICRC3Config, ICRC3Properties}, icrc3::ICRC3, interface::ICRC3Interface, types::Icrc3Error};
51        use bity_ic_canister_time::{run_interval, MINUTE_IN_MS, HOUR_IN_MS};
52        use std::time::Duration;
53
54        lazy_static! {
55            pub static ref ICRC3_INSTANCE: Arc<RwLock<Option<ICRC3>>> = Arc::new(RwLock::new(None));
56        }
57
58        const __ICRC3_NOT_INITIALIZED: &str = "ICRC3 state has not been initialized";
59
60        pub fn init_icrc3(config: ICRC3Config) {
61            let mut lock = ICRC3_INSTANCE.write().unwrap();
62            *lock = Some(ICRC3::new(config));
63        }
64
65        pub fn is_initialized() -> bool {
66            let lock = ICRC3_INSTANCE.read().unwrap();
67            lock.is_some()
68        }
69
70        pub fn take_icrc3() -> Option<ICRC3> {
71            let mut lock = ICRC3_INSTANCE.write().unwrap();
72            lock.take()
73        }
74
75        pub fn replace_icrc3(icrc3: ICRC3) {
76            let mut lock = ICRC3_INSTANCE.write().unwrap();
77            *lock = Some(icrc3);
78        }
79
80        pub fn icrc3_add_transaction<T: TransactionType>(
81            transaction: T,
82        ) -> Result<u64, Icrc3Error> {
83            let mut lock = ICRC3_INSTANCE.write().unwrap();
84            let icrc3 = lock.as_mut().expect(__ICRC3_NOT_INITIALIZED);
85            <ICRC3 as ICRC3Interface>::add_transaction(icrc3, transaction)
86        }
87
88        pub fn icrc3_prepare_transaction<T: TransactionType>(
89            transaction: T,
90        ) -> Result<bity_ic_icrc3::types::prepare_transaction::PreparedTransaction, Icrc3Error> {
91            let mut lock = ICRC3_INSTANCE.write().unwrap();
92            let icrc3 = lock.as_mut().expect(__ICRC3_NOT_INITIALIZED);
93            <ICRC3 as ICRC3Interface>::prepare_transaction(icrc3, transaction)
94        }
95
96        pub fn icrc3_commit_prepared_transaction<T: TransactionType>(
97            transaction: T,
98            timestamp: u128,
99        ) -> Result<u64, Icrc3Error> {
100            let mut lock = ICRC3_INSTANCE.write().unwrap();
101            let icrc3 = lock.as_mut().expect(__ICRC3_NOT_INITIALIZED);
102            <ICRC3 as ICRC3Interface>::commit_prepared_transaction(icrc3, transaction, timestamp)
103        }
104
105        pub fn icrc3_get_archives() -> Vec<ICRC3ArchiveInfo> {
106            let lock = ICRC3_INSTANCE.read().unwrap();
107            let icrc3 = lock.as_ref().expect(__ICRC3_NOT_INITIALIZED);
108            <ICRC3 as ICRC3Interface>::icrc3_get_archives(icrc3)
109        }
110
111        pub fn icrc3_get_blocks(
112            args: Vec<GetBlocksRequest>,
113        ) -> GetBlocksResult {
114            let lock = ICRC3_INSTANCE.read().unwrap();
115            let icrc3 = lock.as_ref().expect(__ICRC3_NOT_INITIALIZED);
116            <ICRC3 as ICRC3Interface>::icrc3_get_blocks(icrc3, args)
117        }
118
119        pub fn icrc3_get_properties() -> ICRC3Properties {
120            let lock = ICRC3_INSTANCE.read().unwrap();
121            let icrc3 = lock.as_ref().expect(__ICRC3_NOT_INITIALIZED);
122            <ICRC3 as ICRC3Interface>::icrc3_get_properties(icrc3)
123        }
124
125        pub fn icrc3_get_tip_certificate() -> ICRC3DataCertificate {
126            let lock = ICRC3_INSTANCE.read().unwrap();
127            let icrc3 = lock.as_ref().expect(__ICRC3_NOT_INITIALIZED);
128            <ICRC3 as ICRC3Interface>::icrc3_get_tip_certificate(icrc3)
129        }
130
131        pub fn icrc3_supported_block_types() -> Vec<SupportedBlockType> {
132            let lock = ICRC3_INSTANCE.read().unwrap();
133            let icrc3 = lock.as_ref().expect(__ICRC3_NOT_INITIALIZED);
134            <ICRC3 as ICRC3Interface>::icrc3_supported_block_types(icrc3)
135        }
136
137        pub fn start_archive_job(interval_ms: u64) {
138            run_interval(Duration::from_millis(interval_ms), || {
139                ic_cdk::futures::spawn(async {
140                    match ICRC3_INSTANCE.write() {
141                        Ok(mut lock) => {
142                            if let Some(icrc3) = lock.as_mut() {
143                                if let Err(e) = icrc3.archive_job().await {
144                                    bity_ic_icrc3::utils::trace(format!("Archive job failed: {}", e));
145                                } else {
146                                    bity_ic_icrc3::utils::trace(format!("Archive job completed successfully"));
147                                }
148                            } else {
149                                bity_ic_icrc3::utils::trace("ICRC3 instance not initialized");
150                            }
151                        },
152                        Err(e) => {
153                            bity_ic_icrc3::utils::trace(format!("Failed to acquire ICRC3 lock: {}", e));
154                        }
155                    }
156                });
157            });
158        }
159
160        pub fn start_cleanup_job(interval_ms: u64) {
161            run_interval(Duration::from_millis(interval_ms), || {
162                ic_cdk::futures::spawn(async {
163                    match ICRC3_INSTANCE.write() {
164                        Ok(mut lock) => {
165                            if let Some(icrc3) = lock.as_mut() {
166                                if let Err(e) = icrc3.cleanup_job() {
167                                    bity_ic_icrc3::utils::trace(format!("Cleanup job failed: {}", e));
168                                } else {
169                                    bity_ic_icrc3::utils::trace(format!("Cleanup job completed successfully"));
170                                }
171                            } else {
172                                bity_ic_icrc3::utils::trace("ICRC3 instance not initialized");
173                            }
174                        },
175                        Err(e) => {
176                            bity_ic_icrc3::utils::trace(format!("Failed to acquire ICRC3 lock: {}", e));
177                        }
178                    }
179                });
180            });
181        }
182
183        // by default you can use this method, to run archive 10mins
184        pub fn start_default_archive_job() {
185            start_archive_job(10 * MINUTE_IN_MS);
186            start_cleanup_job(1 * HOUR_IN_MS);
187        }
188    };
189
190    expanded.into()
191}