use candid::CandidType;
use serde::Deserialize;
use crate::{
common::result::MotokoResult,
identity::{caller, to_account_identifier},
types::{CanisterId, NftStorage},
};
use super::types::*;
#[derive(CandidType, Deserialize)]
pub struct ExtBalanceArgs {
pub user: ExtUser,
pub token: ExtTokenIdentifier,
}
pub type ExtBalanceResult = MotokoResult<ExtBalance, ExtCommonError>;
#[derive(CandidType, Deserialize)]
pub struct ExtTransferArgs {
pub from: ExtUser,
pub to: ExtUser,
pub token: ExtTokenIdentifier, pub amount: ExtBalance, pub memo: Vec<u8>, pub notify: bool, pub subaccount: Option<ExtSubaccount>, }
#[derive(CandidType, Deserialize)]
pub enum ExtTransferError {
Unauthorized(ExtAccountIdentifierHex),
InsufficientBalance,
Rejected, InvalidToken(ExtTokenIdentifier),
CannotNotify(ExtAccountIdentifierHex),
Other(String),
}
#[derive(Clone)]
pub struct StableTransferArgs {
pub from: ExtUser,
pub to: ExtUser,
pub token: ExtTokenIdentifier, pub amount: ExtBalance, pub memo: Vec<u8>, pub notify: bool, pub subaccount: Option<ExtSubaccount>, }
pub type ExtTransferResult = MotokoResult<ExtBalance, ExtTransferError>;
pub trait ExtCore {
fn extensions(&self) -> Vec<String>;
fn balance(&self, args: ExtBalanceArgs) -> ExtBalanceResult;
fn transfer(
&mut self,
args: ExtTransferArgs,
) -> (ExtTransferResult, Option<StableTransferArgs>);
}
impl ExtCore for NftStorage {
fn extensions(&self) -> Vec<String> {
vec![
"@ext/common".to_string(), "@ext/allowance".to_string(), "@ext/nonfungible".to_string(), "@ext/batch".to_string(), ]
}
fn balance(&self, args: ExtBalanceArgs) -> ExtBalanceResult {
let index = match super::utils::parse_token_index_with_self_canister(&args.token) {
Ok(index) => index as usize,
Err(e) => return MotokoResult::Err(e),
};
let owner = args.user.to_account_identity();
let balance = self.nfts.get(index).and_then(|nft| {
if nft.owner.to_vec() == owner {
return Some(1);
}
return Some(0);
});
match balance {
Some(num) => MotokoResult::Ok(candid::Nat::from(num)),
None => MotokoResult::Err(ExtCommonError::InvalidToken(args.token)),
}
}
fn transfer(
&mut self,
args: ExtTransferArgs,
) -> (ExtTransferResult, Option<StableTransferArgs>) {
if args.amount != candid::Nat::from(1) {
return (
MotokoResult::Err(ExtTransferError::Other(format!("Must use amount of 1"))),
None,
);
};
let index = match super::utils::parse_token_index_with_self_canister(&args.token) {
Ok(index) => index as usize,
Err(_) => {
return (
MotokoResult::Err(ExtTransferError::InvalidToken(args.token)),
None,
);
}
};
let ExtTransferArgs {
from,
to,
token,
amount,
memo,
notify,
subaccount,
} = args;
let args = StableTransferArgs {
from,
to,
token,
amount: amount.clone(),
memo: memo.clone(),
notify,
subaccount,
};
let result = do_transfer(self, index as usize, args.clone());
match result {
Ok((_owner, _receiver)) => {
(MotokoResult::Ok(candid::Nat::from(amount)), Some(args)) }
Err(r) => (MotokoResult::Err(r.into()), None),
}
}
}
fn do_transfer(
storage: &mut NftStorage,
index: usize,
args: StableTransferArgs,
) -> Result<(ExtAccountIdentifierHex, ExtAccountIdentifierHex), ExtTransferError> {
let caller = ExtUser::parse_account_identifier(&caller(), &args.subaccount); let owner = args.from.to_account_identity(); let receiver = args.to.to_account_identity();
if let Some(err) = match storage.nfts.get_mut(index) {
Some(_nft) => {
if _nft.owner.to_vec() != owner {
return Err(ExtTransferError::Unauthorized(ExtUser::to_hex(&owner)));
}
if owner != caller {
match &_nft.approved {
Some(_approved) => {
if ExtUser::parse_account_identifier(_approved, &None) != caller {
return Err(ExtTransferError::Unauthorized(ExtUser::to_hex(&caller)));
}
}
None => return Err(ExtTransferError::Unauthorized(ExtUser::to_hex(&caller))), }
}
None }
None => Some(ExtTransferError::InvalidToken(args.token.clone())),
} {
return Err(err); }
if let Some(_nft) = storage.nfts.get_mut(index) {
_nft.approved = None;
_nft.owner = to_account_identifier(&receiver);
}
return Ok((ExtUser::to_hex(&owner), ExtUser::to_hex(&receiver)));
}
#[inline]
pub async fn notify_transfer_message(
canister_id: CanisterId,
args: StableTransferArgs,
) -> Result<Option<candid::Nat>, String> {
let method = "tokenTransferNotification";
let call_result: Result<(Option<candid::Nat>,), (ic_cdk::api::call::RejectionCode, String)> =
crate::canister::call::call_canister(
canister_id,
method,
(args.token, args.from, args.amount, args.memo),
)
.await;
if call_result.is_err() {
let err = call_result.unwrap_err();
let err = format!(
"canister: {} call: {} failed: {:?} {}",
canister_id.to_text(),
method,
err.0,
err.1
);
return Err(err);
}
Ok(call_result.unwrap().0)
}