use std::cell::RefCell; use ic_cdk_macros::{init, query, update}; use candid::{candid_method, Nat}; use ic_cdk::caller; use icrc_ledger_types::icrc1::account::Account; use icrc::icrc7::consts::*; use icrc::icrc7::ledger::{Ledger, Value}; use icrc::icrc7::types::{TransferArgs, TransferResult}; struct Access; impl Access { fn with_ledger(f: impl FnOnce(&Ledger) -> R) -> R { LEDGER.with(|cell| { f(cell .borrow() .as_ref() .expect("ledger state not initialized")) }) } fn with_ledger_mut(f: impl FnOnce(&mut Ledger) -> R) -> R { LEDGER.with(|cell| { f(cell .borrow_mut() .as_mut() .expect("ledger state not initialized")) }) } } thread_local! { static LEDGER: RefCell> = RefCell::default(); } #[init] #[candid_method(init)] fn init() { LEDGER.with(|ledger| { let mut ledger = ledger.borrow_mut(); *ledger = Some(Ledger::new( "ICRC7".to_string(), "ICRC7".to_string(), )); }); } #[query(name = "icrc7_collection_metadata")] #[candid_method(query, rename = "icrc7_collection_metadata")] fn collection_metadata() -> Vec<(String, Value)> { Access::with_ledger(|ledger| ledger.collection_metadata()) } #[query(name = "icrc7_symbol")] #[candid_method(query, rename = "icrc7_symbol")] fn symbol() -> String { Access::with_ledger(|ledger| ledger.symbol.clone()) } #[query(name = "icrc7_name")] #[candid_method(query, rename = "icrc7_name")] fn name() -> String { Access::with_ledger(|ledger| ledger.name.clone()) } #[query(name = "icrc7_description")] #[candid_method(query, rename = "icrc7_description")] fn description() -> Option { Access::with_ledger(|ledger| ledger.description.clone()) } #[query(name = "icrc7_logo")] #[candid_method(query, rename = "icrc7_logo")] fn logo() -> Option { Access::with_ledger(|ledger| ledger.logo.clone()) } #[query(name = "icrc7_total_supply")] #[candid_method(query, rename = "icrc7_total_supply")] fn total_supply() -> Nat { Access::with_ledger(|ledger| ledger.tokens.len().into()) } #[query(name = "icrc7_supply_cap")] #[candid_method(query, rename = "icrc7_supply_cap")] fn supply_cap() -> Option { Access::with_ledger(|ledger| ledger.supply_cap.clone()) } #[query(name = "icrc7_max_query_batch_size")] #[candid_method(query, rename = "icrc7_max_query_batch_size")] fn max_query_batch_size() -> Option { Some(Nat::from(MAX_QUERY_BATCH_SIZE)) } #[query(name = "icrc7_max_update_batch_size")] #[candid_method(query, rename = "icrc7_max_update_batch_size")] fn max_update_batch_size() -> Option { Some(Nat::from(MAX_UPDATE_BATCH_SIZE)) } #[query(name = "icrc7_default_take_value")] #[candid_method(query, rename = "icrc7_default_take_value")] fn default_take_value() -> Option { Some(Nat::from(DEFAULT_TAKE_VALUE)) } #[query(name = "icrc7_max_take_value")] #[candid_method(query, rename = "icrc7_max_take_value")] fn max_take_value() -> Option { Some(Nat::from(MAX_TAKE_VALUE)) } #[query(name = "icrc7_max_memo_size")] #[candid_method(query, rename = "icrc7_max_memo_size")] fn max_memo_size() -> Option { Some(Nat::from(MAX_MEMO_SIZE)) } #[query(name = "icrc7_atomic_batch_transfers")] #[candid_method(query, rename = "icrc7_atomic_batch_transfers")] fn atomic_batch_transfers() -> bool { ATOMIC_BATCH_TRANSFERS } #[query(name = "icrc7_tx_window")] #[candid_method(query, rename = "icrc7_tx_window")] fn tx_window() -> Option { Some(Nat::from(TX_WINDOW)) } #[query(name = "icrc7_permitted_drift")] #[candid_method(query, rename = "icrc7_permitted_drift")] fn permitted_drift() -> Option { Some(Nat::from(PERMITTED_DRIFT)) } #[query(name = "icrc7_token_metadata")] #[candid_method(query, rename = "icrc7_token_metadata")] fn token_metadata(token_ids: Vec) -> Vec>> { Access::with_ledger(|ledger| ledger.token_metadata(token_ids)) } #[query(name = "icrc7_owner_of")] #[candid_method(query, rename = "icrc7_owner_of")] fn owner_of(token_ids: Vec) -> Vec> { Access::with_ledger(|ledger| ledger.owner_of(token_ids)) } #[query(name = "icrc7_balance_of")] #[candid_method(query, rename = "icrc7_balance_of")] fn balance_of(owners: Vec) -> Vec { Access::with_ledger(|ledger| ledger.balance_of(owners)) } #[query(name = "icrc7_tokens")] #[candid_method(query, rename = "icrc7_tokens")] fn tokens(prev: Option, take: Option) -> Vec { Access::with_ledger(|ledger| ledger.tokens(prev, take)) } #[query(name = "icrc7_tokens_of")] #[candid_method(query, rename = "icrc7_tokens_of")] fn tokens_of(owner: Account, prev: Option, take: Option) -> Vec { Access::with_ledger(|ledger| ledger.tokens_of(owner, prev, take)) } #[update(name = "icrc7_transfer")] #[candid_method(update, rename = "icrc7_transfer")] fn transfer(args: Vec) -> Vec> { Access::with_ledger_mut(|ledger| ledger.transfer(caller(), args, true)) } candid::export_service!(); fn main() {} #[test] fn check_candid_interface() { use candid_parser::utils::{service_compatible, CandidSource}; let new_interface = __export_service(); let interface = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("icrc7.did"); service_compatible( CandidSource::Text(&new_interface), CandidSource::File(interface.as_path()), ) .unwrap_or_else(|e| { panic!( "the ledger interface is not compatible with {}: {:?}", interface.display(), e ) }); }