use std::fmt; use near_sdk::{ext_contract, json_types::U128, near, AccountId, NearToken, Promise}; use crate::constants::{GAS_FOR_TOKEN_TRANSFER, GAS_FOR_TOKEN_TRANSFER_CALL}; /// External contract interface for making cross-contract calls to NEP-141 contracts #[ext_contract(ext_ft_core)] pub trait _ExtFungibleTokenCore { fn ft_transfer(&mut self, receiver_id: AccountId, amount: U128, memo: Option); fn ft_transfer_call( &mut self, receiver_id: AccountId, amount: U128, memo: Option, msg: String, ); } /// External contract interface for making cross-contract calls to NEP-245 contracts #[ext_contract(ext_mt_core)] pub trait _ExtMultiTokenCore { fn mt_transfer( &mut self, receiver_id: AccountId, token_id: String, amount: U128, approval: Option, memo: Option, ); fn mt_transfer_call( &mut self, receiver_id: AccountId, token_id: String, amount: U128, approval: Option, memo: Option, msg: String, ); } #[near(serializers=[json, borsh])] #[derive(Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] pub enum Asset { FungibleToken { contract_id: AccountId, }, MultiToken { contract_id: AccountId, token_id: String, }, } impl Asset { pub fn contract_id(&self) -> &AccountId { match self { Asset::FungibleToken { contract_id } => contract_id, Asset::MultiToken { contract_id, .. } => contract_id, } } pub fn token_id(&self) -> &str { match self { Asset::FungibleToken { contract_id } => contract_id.as_str(), Asset::MultiToken { token_id, .. } => token_id, } } pub fn is_fungible_token(&self) -> bool { matches!(self, Asset::FungibleToken { .. }) } pub fn is_multi_token(&self) -> bool { matches!(self, Asset::MultiToken { .. }) } pub fn new_fungible_token(contract_id: AccountId) -> Self { Asset::FungibleToken { contract_id } } pub fn new_multi_token(contract_id: AccountId, token_id: String) -> Self { Asset::MultiToken { contract_id, token_id, } } pub fn to_string_representation(&self) -> String { match self { Asset::FungibleToken { contract_id } => { format!("ft:{}", contract_id) } Asset::MultiToken { contract_id, token_id, } => { format!("mt:{}:{}", contract_id, token_id) } } } pub fn from_string_representation(s: &str) -> Result { let parts: Vec<&str> = s.splitn(3, ':').collect(); if parts.len() < 2 { return Err( "Invalid asset format. Expected 'ft:contract' or 'mt:contract:token'".to_string(), ); } let asset_type = parts[0]; let contract_id = AccountId::try_from(parts[1].to_string()) .map_err(|_| "Invalid contract account ID".to_string())?; match asset_type { "ft" => { if parts.len() != 2 { return Err("Invalid FungibleToken format. Expected 'ft:contract'".to_string()); } Ok(Asset::FungibleToken { contract_id }) } "mt" => { if parts.len() != 3 { return Err( "Invalid MultiToken format. Expected 'mt:contract:token'".to_string() ); } let token_id = parts[2].to_string(); Ok(Asset::MultiToken { contract_id, token_id, }) } _ => Err("Invalid asset type. Expected 'ft' or 'mt'".to_string()), } } pub fn create_transfer_promise( &self, receiver_id: AccountId, amount: U128, memo: Option, ) -> Promise { match self { Asset::FungibleToken { contract_id } => ext_ft_core::ext(contract_id.clone()) .with_attached_deposit(NearToken::from_yoctonear(1)) .with_static_gas(GAS_FOR_TOKEN_TRANSFER) .ft_transfer(receiver_id, amount, memo), Asset::MultiToken { contract_id, token_id, } => ext_mt_core::ext(contract_id.clone()) .with_attached_deposit(NearToken::from_yoctonear(1)) .with_static_gas(GAS_FOR_TOKEN_TRANSFER) .mt_transfer(receiver_id, token_id.clone(), amount, None, memo), } } } impl fmt::Display for Asset { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.to_string_representation()) } }