1use serde::{Deserialize, Serialize};
7use std::fmt;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub enum TokenVmType {
12 Native,
14 Evm,
16 Svm,
18 Daml,
20 TempoTip20,
22}
23
24impl fmt::Display for TokenVmType {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 match self {
27 TokenVmType::Native => write!(f, "native"),
28 TokenVmType::Evm => write!(f, "evm"),
29 TokenVmType::Svm => write!(f, "svm"),
30 TokenVmType::Daml => write!(f, "daml"),
31 TokenVmType::TempoTip20 => write!(f, "tempo-tip20"),
32 }
33 }
34}
35
36#[derive(Debug, Clone, Default, Serialize, Deserialize)]
38pub struct VmAddresses {
39 pub evm: Option<[u8; 20]>,
41 pub svm: Option<[u8; 32]>,
43 pub daml_template_id: Option<String>,
45 pub native: Option<[u8; 32]>,
47 pub tempo: Option<[u8; 20]>,
49}
50
51impl VmAddresses {
52 pub fn has_vm(&self, vm: TokenVmType) -> bool {
54 match vm {
55 TokenVmType::Native => self.native.is_some(),
56 TokenVmType::Evm => self.evm.is_some(),
57 TokenVmType::Svm => self.svm.is_some(),
58 TokenVmType::Daml => self.daml_template_id.is_some(),
59 TokenVmType::TempoTip20 => self.tempo.is_some(),
60 }
61 }
62
63 pub fn tempo_hex(&self) -> Option<String> {
65 self.tempo.map(|addr| format!("0x{}", hex::encode(addr)))
66 }
67
68 pub fn evm_hex(&self) -> Option<String> {
70 self.evm.map(|addr| format!("0x{}", hex::encode(addr)))
71 }
72
73 pub fn svm_hex(&self) -> Option<String> {
75 self.svm.map(hex::encode)
76 }
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct TokenPermissions {
82 pub mintable: bool,
84 pub burnable: bool,
86 pub pausable: bool,
88 pub freezable: bool,
90 pub paused: bool,
92}
93
94impl Default for TokenPermissions {
95 fn default() -> Self {
96 Self {
97 mintable: false,
98 burnable: true,
99 pausable: false,
100 freezable: false,
101 paused: false,
102 }
103 }
104}
105
106#[derive(Debug, Clone, Default, Serialize, Deserialize)]
108pub struct TokenMetadata {
109 pub description: Option<String>,
111 pub icon_uri: Option<String>,
113 pub website: Option<String>,
115 pub extra: std::collections::HashMap<String, String>,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
121pub struct TokenId(pub [u8; 32]);
122
123impl TokenId {
124 pub fn new(bytes: [u8; 32]) -> Self {
126 Self(bytes)
127 }
128
129 pub fn compute(creator: &[u8], nonce: u64) -> Self {
131 use sha2::{Sha256, Digest};
132 let mut hasher = Sha256::new();
133 hasher.update(b"tenzro-token-id:");
134 hasher.update(creator);
135 hasher.update(nonce.to_le_bytes());
136 let result = hasher.finalize();
137 let mut bytes = [0u8; 32];
138 bytes.copy_from_slice(&result);
139 Self(bytes)
140 }
141
142 pub fn to_hex(&self) -> String {
144 hex::encode(self.0)
145 }
146
147 pub fn tnzo() -> Self {
149 let mut bytes = [0u8; 32];
150 bytes[0] = 0x01;
151 Self(bytes)
152 }
153}
154
155impl fmt::Display for TokenId {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 write!(f, "{}", self.to_hex())
158 }
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
163pub enum TokenType {
164 Native,
166 Erc20,
168 Spl,
170 Cip56,
172 CrossVm,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct TokenDefinition {
179 pub token_id: TokenId,
181 pub name: String,
183 pub symbol: String,
185 pub decimals: u8,
187 pub total_supply: u128,
189 pub max_supply: Option<u128>,
191 pub creator: [u8; 32],
193 pub token_type: TokenType,
195 pub vm_addresses: VmAddresses,
197 pub permissions: TokenPermissions,
199 pub created_at: u64,
201 pub metadata: TokenMetadata,
203}
204
205pub const NATIVE_DECIMALS: u8 = 18;
207pub const SPL_DECIMALS: u8 = 9;
208pub 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> {
218 let spl_amount = native_amount / DECIMAL_SHIFT;
219 if spl_amount > u64::MAX as u128 {
220 None
221 } else {
222 Some(spl_amount as u64)
223 }
224}
225
226pub fn spl_to_native(spl_amount: u64) -> u128 {
229 (spl_amount as u128) * DECIMAL_SHIFT
230}
231
232pub fn truncation_dust(native_amount: u128) -> u128 {
234 native_amount % DECIMAL_SHIFT
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
239pub struct CrossVmTransfer {
240 pub token_id: TokenId,
242 pub from_vm: TokenVmType,
244 pub to_vm: TokenVmType,
246 pub from_address: Vec<u8>,
248 pub to_address: Vec<u8>,
250 pub amount: u128,
252 pub nonce: u64,
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn test_native_to_spl_conversion() {
262 assert_eq!(native_to_spl(NATIVE_UNIT), Some(SPL_UNIT));
264
265 let half_tnzo = NATIVE_UNIT / 2;
267 assert_eq!(native_to_spl(half_tnzo), Some(SPL_UNIT / 2));
268
269 assert_eq!(native_to_spl(999_999_999), Some(0)); assert_eq!(native_to_spl(DECIMAL_SHIFT), Some(1));
274 }
275
276 #[test]
277 fn test_spl_to_native_conversion() {
278 assert_eq!(spl_to_native(SPL_UNIT), NATIVE_UNIT);
279 assert_eq!(spl_to_native(1), DECIMAL_SHIFT);
280 assert_eq!(spl_to_native(0), 0);
281 }
282
283 #[test]
284 fn test_roundtrip_conversion() {
285 let original = 12_345_678_901_234_567_890u128; let spl = native_to_spl(original).unwrap();
287 let back = spl_to_native(spl);
288 let dust = truncation_dust(original);
289
290 assert_eq!(back + dust, original);
292 assert!(dust < DECIMAL_SHIFT);
294 }
295
296 #[test]
297 fn test_spl_overflow_protection() {
298 let huge = u128::MAX;
300 assert_eq!(native_to_spl(huge), None);
301 }
302
303 #[test]
304 fn test_token_id_computation() {
305 let creator = [42u8; 32];
306 let id1 = TokenId::compute(&creator, 0);
307 let id2 = TokenId::compute(&creator, 1);
308 assert_ne!(id1, id2); }
310
311 #[test]
312 fn test_token_id_tnzo() {
313 let tnzo = TokenId::tnzo();
314 assert_eq!(tnzo.0[0], 0x01);
315 assert!(tnzo.0[1..].iter().all(|&b| b == 0));
316 }
317
318 #[test]
319 fn test_vm_addresses_has_vm() {
320 let mut addrs = VmAddresses::default();
321 assert!(!addrs.has_vm(TokenVmType::Evm));
322 addrs.evm = Some([0u8; 20]);
323 assert!(addrs.has_vm(TokenVmType::Evm));
324 }
325
326 #[test]
327 fn test_token_permissions_default() {
328 let perms = TokenPermissions::default();
329 assert!(!perms.mintable);
330 assert!(perms.burnable);
331 assert!(!perms.pausable);
332 assert!(!perms.freezable);
333 assert!(!perms.paused);
334 }
335}