tempo_primitives/address.rs
1use alloy_primitives::{Address, FixedBytes, hex};
2
3/// TIP20 token address prefix (12 bytes)
4/// The full address is: TIP20_TOKEN_PREFIX (12 bytes) || derived_bytes (8 bytes)
5const TIP20_TOKEN_PREFIX: [u8; 12] = hex!("20C000000000000000000000");
6
7/// Returns `true` if `addr` has the TIP-20 token prefix.
8///
9/// NOTE: This only checks the prefix, not whether the token was actually created.
10/// Use `TIP20Factory::is_tip20()` for full validation.
11pub fn is_tip20_prefix(addr: Address) -> bool {
12 addr.as_slice().starts_with(&TIP20_TOKEN_PREFIX)
13}
14
15/// 4-byte master identifier derived from the registration hash.
16pub type MasterId = FixedBytes<4>;
17
18/// 6-byte user tag occupying the trailing bytes of a virtual address.
19pub type UserTag = FixedBytes<6>;
20
21/// Extension trait with helper functions for Tempo addresses.
22pub trait TempoAddressExt {
23 /// 12-byte prefix shared by all TIP-20 token addresses.
24 ///
25 /// NOTE: prefix alone does not prove a token exists — use `TIP20Factory::is_tip20()` for that.
26 const TIP20_PREFIX: [u8; 12];
27
28 /// 10-byte magic value occupying bytes `[4:14]` of every [TIP-1022] virtual address.
29 ///
30 /// [TIP-1022]: <https://docs.tempo.xyz/protocol/tip1022>
31 const VIRTUAL_MAGIC: [u8; 10];
32
33 /// Returns `true` if the address has the [TIP-20] token prefix.
34 ///
35 /// NOTE: This only checks the prefix, not whether the token was actually created.
36 /// Use `TIP20Factory::is_tip20()` for full validation.
37 ///
38 /// [TIP-20]: <https://docs.tempo.xyz/protocol/tip20>
39 fn is_tip20(&self) -> bool;
40
41 /// Returns `true` if the address matches the [TIP-1022] virtual-address format
42 /// (bytes `[4:14]` == [`Self::VIRTUAL_MAGIC`]).
43 ///
44 /// [TIP-1022]: <https://docs.tempo.xyz/protocol/tip1022>
45 fn is_virtual(&self) -> bool;
46
47 /// Returns `true` if the address is eligible to be a virtual-address master per TIP-1022.
48 fn is_valid_master(&self) -> bool;
49
50 /// Decodes a virtual address into its `(masterId, userTag)` components.
51 ///
52 /// Returns `None` if the address does not match the virtual-address format.
53 fn decode_virtual(&self) -> Option<(MasterId, UserTag)>;
54
55 /// Builds a [TIP-1022] virtual address from a `masterId` and `userTag`.
56 ///
57 /// [TIP-1022]: <https://docs.tempo.xyz/protocol/tip1022>
58 fn new_virtual(master_id: MasterId, user_tag: UserTag) -> Self;
59}
60
61impl TempoAddressExt for Address {
62 const TIP20_PREFIX: [u8; 12] = TIP20_TOKEN_PREFIX;
63 const VIRTUAL_MAGIC: [u8; 10] = [0xFD; 10];
64
65 fn is_tip20(&self) -> bool {
66 is_tip20_prefix(*self)
67 }
68
69 fn is_virtual(&self) -> bool {
70 self.as_slice()[4..14] == Self::VIRTUAL_MAGIC
71 }
72
73 fn is_valid_master(&self) -> bool {
74 !self.is_zero() && !self.is_virtual() && !self.is_tip20()
75 }
76
77 fn decode_virtual(&self) -> Option<(MasterId, UserTag)> {
78 if !self.is_virtual() {
79 return None;
80 }
81 let bytes = self.as_slice();
82 Some((
83 MasterId::from_slice(&bytes[0..4]),
84 UserTag::from_slice(&bytes[14..20]),
85 ))
86 }
87
88 fn new_virtual(master_id: MasterId, user_tag: UserTag) -> Self {
89 let mut bytes = [0u8; 20];
90 bytes[0..4].copy_from_slice(master_id.as_slice());
91 bytes[4..14].copy_from_slice(&Self::VIRTUAL_MAGIC);
92 bytes[14..20].copy_from_slice(user_tag.as_slice());
93 Self::from(bytes)
94 }
95}