neo3 1.3.0

Production-ready Rust SDK for Neo N3 blockchain with high-level API, unified error handling, and enterprise features
Documentation
use async_trait::async_trait;
use num_traits::ToPrimitive;
use primitive_types::H160;
use rust_decimal::Decimal;

use crate::{
	neo_clients::JsonRpcProvider,
	neo_contract::{ContractError, SmartContractTrait},
	neo_types::NNSName,
};

#[async_trait]
pub trait TokenTrait<'a, P: JsonRpcProvider>: SmartContractTrait<'a, P = P> {
	const TOTAL_SUPPLY: &'static str = "totalSupply";
	const SYMBOL: &'static str = "symbol";
	const DECIMALS: &'static str = "decimals";

	fn total_supply(&self) -> Option<u64>;

	fn set_total_supply(&mut self, total_supply: u64);

	fn decimals(&self) -> Option<u8>;

	fn set_decimals(&mut self, decimals: u8);

	fn symbol(&self) -> Option<String>;

	fn set_symbol(&mut self, symbol: String);

	async fn get_total_supply(&mut self) -> Result<u64, ContractError> {
		if let Some(supply) = &self.total_supply() {
			return Ok(*supply);
		}

		let supply = self.call_function_returning_int(Self::TOTAL_SUPPLY, vec![]).await? as u64;

		self.set_total_supply(supply);
		Ok(supply)
	}

	async fn get_decimals(&mut self) -> Result<u8, ContractError> {
		if let Some(decimals) = &self.decimals() {
			return Ok(*decimals);
		}

		let decimals = self.call_function_returning_int(Self::DECIMALS, vec![]).await? as u8;

		self.set_decimals(decimals);
		Ok(decimals)
	}

	// Other methods

	async fn get_symbol(&mut self) -> Result<String, ContractError> {
		if let Some(symbol) = &self.symbol() {
			return Ok(symbol.clone());
		}

		let symbol = self.call_function_returning_string(Self::SYMBOL, vec![]).await?;

		self.set_symbol(symbol.clone());
		Ok(symbol)
	}

	fn to_fractions(&self, amount: u64, decimals: u32) -> Result<i64, ContractError> {
		let scaled = Decimal::from(amount) * Decimal::from(10i32.pow(decimals));
		scaled.trunc().to_i64().ok_or_else(|| {
			ContractError::RuntimeError("Amount is too large to fit into i64 fractions".to_string())
		})
	}

	fn to_fractions_decimal(&self, amount: Decimal, decimals: u32) -> Result<u64, ContractError> {
		if amount.scale() > decimals {
			return Err(ContractError::RuntimeError(
				"Amount has too many decimal places".to_string(),
			));
		}

		let mut scaled = amount;
		scaled *= Decimal::from(10_u32.pow(decimals));

		scaled.trunc().to_u64().ok_or_else(|| {
			ContractError::RuntimeError("Amount is too large to fit into u64 fractions".to_string())
		})
	}

	fn to_decimals_u64(&self, fractions: u64, decimals: u32) -> Decimal {
		let divisor = Decimal::from(10_u32.pow(decimals));
		let amount = Decimal::from(fractions);

		amount / divisor
	}

	fn to_decimals(&self, amount: i64, decimals: u32) -> Decimal {
		let divisor = Decimal::from(10_u32.pow(decimals));
		let decimal_amount = Decimal::from(amount);

		// u32 is always non-negative, so this check is redundant
		decimal_amount / divisor
	}

	/// Resolves an NNS name to a script hash.
	///
	/// The default implementation returns an error indicating NNS resolution
	/// requires a configured NNS contract. Override this in implementations
	/// that support NNS-based address resolution.
	async fn resolve_nns_text_record(&self, name: &NNSName) -> Result<H160, ContractError> {
		Err(ContractError::RuntimeError(format!(
			"NNS resolution for '{}' is not supported by this token contract. \
			 Use NameService directly to resolve NNS names.",
			name.name()
		)))
	}
}