Skip to main content

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}