use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum TokenVmType {
Native,
Evm,
Svm,
Daml,
TempoTip20,
}
impl fmt::Display for TokenVmType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TokenVmType::Native => write!(f, "native"),
TokenVmType::Evm => write!(f, "evm"),
TokenVmType::Svm => write!(f, "svm"),
TokenVmType::Daml => write!(f, "daml"),
TokenVmType::TempoTip20 => write!(f, "tempo-tip20"),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct VmAddresses {
pub evm: Option<[u8; 20]>,
pub svm: Option<[u8; 32]>,
pub daml_template_id: Option<String>,
pub native: Option<[u8; 32]>,
pub tempo: Option<[u8; 20]>,
}
impl VmAddresses {
pub fn has_vm(&self, vm: TokenVmType) -> bool {
match vm {
TokenVmType::Native => self.native.is_some(),
TokenVmType::Evm => self.evm.is_some(),
TokenVmType::Svm => self.svm.is_some(),
TokenVmType::Daml => self.daml_template_id.is_some(),
TokenVmType::TempoTip20 => self.tempo.is_some(),
}
}
pub fn tempo_hex(&self) -> Option<String> {
self.tempo.map(|addr| format!("0x{}", hex::encode(addr)))
}
pub fn evm_hex(&self) -> Option<String> {
self.evm.map(|addr| format!("0x{}", hex::encode(addr)))
}
pub fn svm_hex(&self) -> Option<String> {
self.svm.map(hex::encode)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenPermissions {
pub mintable: bool,
pub burnable: bool,
pub pausable: bool,
pub freezable: bool,
pub paused: bool,
}
impl Default for TokenPermissions {
fn default() -> Self {
Self {
mintable: false,
burnable: true,
pausable: false,
freezable: false,
paused: false,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TokenMetadata {
pub description: Option<String>,
pub icon_uri: Option<String>,
pub website: Option<String>,
pub extra: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TokenId(pub [u8; 32]);
impl TokenId {
pub fn new(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub fn compute(creator: &[u8], nonce: u64) -> Self {
use sha2::{Sha256, Digest};
let mut hasher = Sha256::new();
hasher.update(b"tenzro-token-id:");
hasher.update(creator);
hasher.update(nonce.to_le_bytes());
let result = hasher.finalize();
let mut bytes = [0u8; 32];
bytes.copy_from_slice(&result);
Self(bytes)
}
pub fn to_hex(&self) -> String {
hex::encode(self.0)
}
pub fn tnzo() -> Self {
let mut bytes = [0u8; 32];
bytes[0] = 0x01;
Self(bytes)
}
}
impl fmt::Display for TokenId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_hex())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TokenType {
Native,
Erc20,
Spl,
Cip56,
CrossVm,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenDefinition {
pub token_id: TokenId,
pub name: String,
pub symbol: String,
pub decimals: u8,
pub total_supply: u128,
pub max_supply: Option<u128>,
pub creator: [u8; 32],
pub token_type: TokenType,
pub vm_addresses: VmAddresses,
pub permissions: TokenPermissions,
pub created_at: u64,
pub metadata: TokenMetadata,
}
pub const NATIVE_DECIMALS: u8 = 18;
pub const SPL_DECIMALS: u8 = 9;
pub const NATIVE_UNIT: u128 = 1_000_000_000_000_000_000; pub const SPL_UNIT: u64 = 1_000_000_000; pub const DECIMAL_SHIFT: u128 = 1_000_000_000;
pub fn native_to_spl(native_amount: u128) -> Option<u64> {
let spl_amount = native_amount / DECIMAL_SHIFT;
if spl_amount > u64::MAX as u128 {
None
} else {
Some(spl_amount as u64)
}
}
pub fn spl_to_native(spl_amount: u64) -> u128 {
(spl_amount as u128) * DECIMAL_SHIFT
}
pub fn truncation_dust(native_amount: u128) -> u128 {
native_amount % DECIMAL_SHIFT
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrossVmTransfer {
pub token_id: TokenId,
pub from_vm: TokenVmType,
pub to_vm: TokenVmType,
pub from_address: Vec<u8>,
pub to_address: Vec<u8>,
pub amount: u128,
pub nonce: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_native_to_spl_conversion() {
assert_eq!(native_to_spl(NATIVE_UNIT), Some(SPL_UNIT));
let half_tnzo = NATIVE_UNIT / 2;
assert_eq!(native_to_spl(half_tnzo), Some(SPL_UNIT / 2));
assert_eq!(native_to_spl(999_999_999), Some(0));
assert_eq!(native_to_spl(DECIMAL_SHIFT), Some(1));
}
#[test]
fn test_spl_to_native_conversion() {
assert_eq!(spl_to_native(SPL_UNIT), NATIVE_UNIT);
assert_eq!(spl_to_native(1), DECIMAL_SHIFT);
assert_eq!(spl_to_native(0), 0);
}
#[test]
fn test_roundtrip_conversion() {
let original = 12_345_678_901_234_567_890u128; let spl = native_to_spl(original).unwrap();
let back = spl_to_native(spl);
let dust = truncation_dust(original);
assert_eq!(back + dust, original);
assert!(dust < DECIMAL_SHIFT);
}
#[test]
fn test_spl_overflow_protection() {
let huge = u128::MAX;
assert_eq!(native_to_spl(huge), None);
}
#[test]
fn test_token_id_computation() {
let creator = [42u8; 32];
let id1 = TokenId::compute(&creator, 0);
let id2 = TokenId::compute(&creator, 1);
assert_ne!(id1, id2); }
#[test]
fn test_token_id_tnzo() {
let tnzo = TokenId::tnzo();
assert_eq!(tnzo.0[0], 0x01);
assert!(tnzo.0[1..].iter().all(|&b| b == 0));
}
#[test]
fn test_vm_addresses_has_vm() {
let mut addrs = VmAddresses::default();
assert!(!addrs.has_vm(TokenVmType::Evm));
addrs.evm = Some([0u8; 20]);
assert!(addrs.has_vm(TokenVmType::Evm));
}
#[test]
fn test_token_permissions_default() {
let perms = TokenPermissions::default();
assert!(!perms.mintable);
assert!(perms.burnable);
assert!(!perms.pausable);
assert!(!perms.freezable);
assert!(!perms.paused);
}
}