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<R>(f: impl FnOnce(&Ledger) -> R) -> R {
LEDGER.with(|cell| {
f(cell
.borrow()
.as_ref()
.expect("ledger state not initialized"))
})
}
fn with_ledger_mut<R>(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<Option<Ledger>> = 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<String> {
Access::with_ledger(|ledger| ledger.description.clone())
}
#[query(name = "icrc7_logo")]
#[candid_method(query, rename = "icrc7_logo")]
fn logo() -> Option<String> {
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<Nat> {
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<Nat> {
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<Nat> {
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<Nat> {
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<Nat> {
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<Nat> {
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<Nat> {
Some(Nat::from(TX_WINDOW))
}
#[query(name = "icrc7_permitted_drift")]
#[candid_method(query, rename = "icrc7_permitted_drift")]
fn permitted_drift() -> Option<Nat> {
Some(Nat::from(PERMITTED_DRIFT))
}
#[query(name = "icrc7_token_metadata")]
#[candid_method(query, rename = "icrc7_token_metadata")]
fn token_metadata(token_ids: Vec<Nat>) -> Vec<Option<Vec<(String, Value)>>> {
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<Nat>) -> Vec<Option<Account>> {
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<Account>) -> Vec<Nat> {
Access::with_ledger(|ledger| ledger.balance_of(owners))
}
#[query(name = "icrc7_tokens")]
#[candid_method(query, rename = "icrc7_tokens")]
fn tokens(prev: Option<Nat>, take: Option<Nat>) -> Vec<Nat> {
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<Nat>, take: Option<Nat>) -> Vec<Nat> {
Access::with_ledger(|ledger| ledger.tokens_of(owner, prev, take))
}
#[update(name = "icrc7_transfer")]
#[candid_method(update, rename = "icrc7_transfer")]
fn transfer(args: Vec<TransferArgs>) -> Vec<Option<TransferResult>> {
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
)
});
}