Skip to main content

aptos_sdk/types/
address.rs

1//! Account address type.
2//!
3//! Aptos account addresses are 32-byte values, typically displayed as
4//! 64 hexadecimal characters with a `0x` prefix.
5
6use crate::error::{AptosError, AptosResult};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use std::fmt;
9use std::str::FromStr;
10
11/// The length of an account address in bytes.
12pub const ADDRESS_LENGTH: usize = 32;
13
14/// A 32-byte Aptos account address.
15///
16/// Account addresses on Aptos are derived from public keys through a
17/// specific derivation scheme that includes an authentication key prefix.
18///
19/// # Display Format (AIP-40)
20///
21/// The `Display` trait follows [AIP-40](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-40.md):
22/// - **Special addresses** (0x1 through 0xf) use SHORT format: `0x1`, `0x3`, `0xa`
23/// - **Normal addresses** use LONG format: full 64 hex characters with `0x` prefix
24///
25/// Use `to_long_string()` to always get the full 64-character format,
26/// or `to_short_string()` to always get the trimmed format.
27///
28/// # Example
29///
30/// ```rust
31/// use aptos_sdk::AccountAddress;
32///
33/// // Parse from hex string
34/// let addr = AccountAddress::from_hex("0x1").unwrap();
35///
36/// // Display uses AIP-40: SHORT for special addresses
37/// assert_eq!(addr.to_string(), "0x1");
38///
39/// // Explicit long/short string methods
40/// assert_eq!(addr.to_long_string(), "0x0000000000000000000000000000000000000000000000000000000000000001");
41/// assert_eq!(addr.to_short_string(), "0x1");
42/// ```
43#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
44pub struct AccountAddress([u8; ADDRESS_LENGTH]);
45
46impl AccountAddress {
47    /// The "zero" address (all zeros).
48    pub const ZERO: Self = Self([0u8; ADDRESS_LENGTH]);
49
50    /// The core framework address (0x1).
51    pub const ONE: Self = Self::from_u64(1);
52
53    /// The token framework address (0x3).
54    pub const THREE: Self = Self::from_u64(3);
55
56    /// The fungible asset framework address (0x4).
57    pub const FOUR: Self = Self::from_u64(4);
58
59    /// The APT fungible asset metadata address (0xa).
60    pub const A: Self = Self::from_u64(10);
61
62    /// Creates an address from a byte array.
63    pub const fn new(bytes: [u8; ADDRESS_LENGTH]) -> Self {
64        Self(bytes)
65    }
66
67    /// Creates an address from a u64 value (for small addresses like 0x1).
68    const fn from_u64(value: u64) -> Self {
69        let mut bytes = [0u8; ADDRESS_LENGTH];
70        let value_bytes = value.to_be_bytes();
71        bytes[ADDRESS_LENGTH - 8] = value_bytes[0];
72        bytes[ADDRESS_LENGTH - 7] = value_bytes[1];
73        bytes[ADDRESS_LENGTH - 6] = value_bytes[2];
74        bytes[ADDRESS_LENGTH - 5] = value_bytes[3];
75        bytes[ADDRESS_LENGTH - 4] = value_bytes[4];
76        bytes[ADDRESS_LENGTH - 3] = value_bytes[5];
77        bytes[ADDRESS_LENGTH - 2] = value_bytes[6];
78        bytes[ADDRESS_LENGTH - 1] = value_bytes[7];
79        Self(bytes)
80    }
81
82    /// Creates an address from a hex string (with or without `0x` prefix).
83    ///
84    /// The hex string must contain at least one hex digit. Empty strings and
85    /// bare "0x" prefixes are rejected as invalid addresses.
86    ///
87    /// # Errors
88    ///
89    /// Returns an error if the input is empty, contains invalid UTF-8, has no hex digits
90    /// after the prefix, exceeds the maximum length (64 hex characters), or contains
91    /// invalid hex characters.
92    pub fn from_hex<T: AsRef<[u8]>>(hex_str: T) -> AptosResult<Self> {
93        let hex_str = hex_str.as_ref();
94
95        // Reject empty input
96        if hex_str.is_empty() {
97            return Err(AptosError::InvalidAddress(
98                "address cannot be empty".to_string(),
99            ));
100        }
101
102        let hex_str = if hex_str.starts_with(b"0x") || hex_str.starts_with(b"0X") {
103            &hex_str[2..]
104        } else {
105            hex_str
106        };
107
108        // Handle short addresses by zero-padding
109        let hex_string =
110            std::str::from_utf8(hex_str).map_err(|e| AptosError::InvalidAddress(e.to_string()))?;
111
112        // Reject empty hex string (e.g., just "0x" prefix with no digits)
113        if hex_string.is_empty() {
114            return Err(AptosError::InvalidAddress(
115                "address must contain at least one hex digit".to_string(),
116            ));
117        }
118
119        if hex_string.len() > ADDRESS_LENGTH * 2 {
120            return Err(AptosError::InvalidAddress(format!(
121                "address too long: {} characters (max {})",
122                hex_string.len(),
123                ADDRESS_LENGTH * 2
124            )));
125        }
126
127        // Zero-pad to full length
128        let padded = format!("{hex_string:0>64}");
129        let bytes = hex::decode(&padded)?;
130
131        let mut address = [0u8; ADDRESS_LENGTH];
132        address.copy_from_slice(&bytes);
133        Ok(Self(address))
134    }
135
136    /// Creates an address from a byte slice.
137    ///
138    /// # Errors
139    ///
140    /// Returns an error if the byte slice is not exactly 32 bytes long.
141    pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> AptosResult<Self> {
142        let bytes = bytes.as_ref();
143        if bytes.len() != ADDRESS_LENGTH {
144            return Err(AptosError::InvalidAddress(format!(
145                "expected {} bytes, got {}",
146                ADDRESS_LENGTH,
147                bytes.len()
148            )));
149        }
150        let mut address = [0u8; ADDRESS_LENGTH];
151        address.copy_from_slice(bytes);
152        Ok(Self(address))
153    }
154
155    /// Returns the address as a byte slice.
156    #[inline]
157    pub fn as_bytes(&self) -> &[u8] {
158        &self.0
159    }
160
161    /// Returns the address as a byte array.
162    #[inline]
163    pub fn to_bytes(&self) -> [u8; ADDRESS_LENGTH] {
164        self.0
165    }
166
167    /// Returns the address as a hex string with `0x` prefix (always 66 characters).
168    ///
169    /// This is an alias for `to_long_string()`.
170    pub fn to_hex(&self) -> String {
171        self.to_long_string()
172    }
173
174    /// Returns the full 64-character hex string with `0x` prefix.
175    ///
176    /// This always returns the LONG format regardless of whether the address is special.
177    /// For example, `0x1` becomes `0x0000000000000000000000000000000000000000000000000000000000000001`.
178    pub fn to_long_string(&self) -> String {
179        format!("0x{}", hex::encode(self.0))
180    }
181
182    /// Returns a short hex string, trimming leading zeros.
183    ///
184    /// For example, `0x0000...0001` becomes `0x1`.
185    pub fn to_short_string(&self) -> String {
186        let hex = hex::encode(self.0);
187        let trimmed = hex.trim_start_matches('0');
188        if trimmed.is_empty() {
189            "0x0".to_string()
190        } else {
191            format!("0x{trimmed}")
192        }
193    }
194
195    /// Returns the standard string representation following AIP-40.
196    ///
197    /// - Special addresses (0x1 through 0xf) use SHORT format
198    /// - Normal addresses use LONG format (full 64 hex characters)
199    pub fn to_standard_string(&self) -> String {
200        if self.is_special() {
201            self.to_short_string()
202        } else {
203            self.to_long_string()
204        }
205    }
206
207    /// Returns true if this is the zero address.
208    #[inline]
209    pub fn is_zero(&self) -> bool {
210        self == &Self::ZERO
211    }
212
213    /// Returns true if this is a "special" address (first 63 bytes are zero,
214    /// and the last byte is non-zero and less than 16).
215    ///
216    /// Special addresses include framework addresses like 0x1, 0x3, 0x4.
217    #[inline]
218    pub fn is_special(&self) -> bool {
219        self.0[..ADDRESS_LENGTH - 1].iter().all(|&b| b == 0)
220            && self.0[ADDRESS_LENGTH - 1] > 0
221            && self.0[ADDRESS_LENGTH - 1] < 16
222    }
223}
224
225impl Default for AccountAddress {
226    fn default() -> Self {
227        Self::ZERO
228    }
229}
230
231impl fmt::Debug for AccountAddress {
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        write!(f, "AccountAddress({})", self.to_short_string())
234    }
235}
236
237impl fmt::Display for AccountAddress {
238    /// Formats the address following AIP-40:
239    /// - Special addresses (0x1 through 0xf) use SHORT format
240    /// - Normal addresses use LONG format (full 64 hex characters)
241    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242        write!(f, "{}", self.to_standard_string())
243    }
244}
245
246impl FromStr for AccountAddress {
247    type Err = AptosError;
248
249    fn from_str(s: &str) -> Result<Self, Self::Err> {
250        Self::from_hex(s)
251    }
252}
253
254impl Serialize for AccountAddress {
255    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
256    where
257        S: Serializer,
258    {
259        if serializer.is_human_readable() {
260            serializer.serialize_str(&self.to_hex())
261        } else {
262            // BCS serialization: fixed-size array without length prefix
263            // Use tuple serialization to serialize each byte individually
264            use serde::ser::SerializeTuple;
265            let mut tuple = serializer.serialize_tuple(ADDRESS_LENGTH)?;
266            for byte in &self.0 {
267                tuple.serialize_element(byte)?;
268            }
269            tuple.end()
270        }
271    }
272}
273
274impl<'de> Deserialize<'de> for AccountAddress {
275    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
276    where
277        D: Deserializer<'de>,
278    {
279        if deserializer.is_human_readable() {
280            let s = String::deserialize(deserializer)?;
281            Self::from_hex(&s).map_err(serde::de::Error::custom)
282        } else {
283            let bytes = <[u8; ADDRESS_LENGTH]>::deserialize(deserializer)?;
284            Ok(Self(bytes))
285        }
286    }
287}
288
289impl From<[u8; ADDRESS_LENGTH]> for AccountAddress {
290    fn from(bytes: [u8; ADDRESS_LENGTH]) -> Self {
291        Self(bytes)
292    }
293}
294
295impl From<AccountAddress> for [u8; ADDRESS_LENGTH] {
296    fn from(addr: AccountAddress) -> Self {
297        addr.0
298    }
299}
300
301impl AsRef<[u8]> for AccountAddress {
302    fn as_ref(&self) -> &[u8] {
303        &self.0
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310
311    #[test]
312    fn test_from_hex() {
313        // Full address
314        let addr = AccountAddress::from_hex(
315            "0x0000000000000000000000000000000000000000000000000000000000000001",
316        )
317        .unwrap();
318        assert_eq!(addr, AccountAddress::ONE);
319
320        // Short address
321        let addr = AccountAddress::from_hex("0x1").unwrap();
322        assert_eq!(addr, AccountAddress::ONE);
323
324        // Without prefix
325        let addr = AccountAddress::from_hex("1").unwrap();
326        assert_eq!(addr, AccountAddress::ONE);
327    }
328
329    #[test]
330    fn test_to_string() {
331        // AIP-40: Special addresses use SHORT format in Display
332        assert_eq!(AccountAddress::ONE.to_string(), "0x1");
333        assert_eq!(AccountAddress::THREE.to_string(), "0x3");
334        assert_eq!(AccountAddress::FOUR.to_string(), "0x4");
335        assert_eq!(AccountAddress::A.to_string(), "0xa");
336
337        // ZERO is not special, so it uses LONG format
338        assert_eq!(
339            AccountAddress::ZERO.to_string(),
340            "0x0000000000000000000000000000000000000000000000000000000000000000"
341        );
342
343        // Explicit short/long methods
344        assert_eq!(AccountAddress::ONE.to_short_string(), "0x1");
345        assert_eq!(
346            AccountAddress::ONE.to_long_string(),
347            "0x0000000000000000000000000000000000000000000000000000000000000001"
348        );
349        assert_eq!(AccountAddress::ZERO.to_short_string(), "0x0");
350    }
351
352    #[test]
353    fn test_special_addresses() {
354        assert!(AccountAddress::ONE.is_special());
355        assert!(AccountAddress::THREE.is_special());
356        assert!(AccountAddress::FOUR.is_special());
357        assert!(AccountAddress::A.is_special());
358        assert!(!AccountAddress::ZERO.is_special());
359    }
360
361    #[test]
362    fn test_json_serialization() {
363        let addr = AccountAddress::ONE;
364        let json = serde_json::to_string(&addr).unwrap();
365        assert_eq!(
366            json,
367            "\"0x0000000000000000000000000000000000000000000000000000000000000001\""
368        );
369
370        let parsed: AccountAddress = serde_json::from_str(&json).unwrap();
371        assert_eq!(parsed, addr);
372    }
373
374    #[test]
375    fn test_from_str() {
376        let addr: AccountAddress = "0x1".parse().unwrap();
377        assert_eq!(addr, AccountAddress::ONE);
378    }
379
380    #[test]
381    fn test_from_bytes() {
382        let bytes = [0u8; ADDRESS_LENGTH];
383        let addr = AccountAddress::new(bytes);
384        assert_eq!(addr, AccountAddress::ZERO);
385    }
386
387    #[test]
388    fn test_as_bytes() {
389        let addr = AccountAddress::ONE;
390        let bytes = addr.as_bytes();
391        assert_eq!(bytes.len(), ADDRESS_LENGTH);
392        assert_eq!(bytes[ADDRESS_LENGTH - 1], 1);
393    }
394
395    #[test]
396    fn test_is_zero() {
397        assert!(AccountAddress::ZERO.is_zero());
398        assert!(!AccountAddress::ONE.is_zero());
399    }
400
401    #[test]
402    fn test_debug() {
403        let addr = AccountAddress::ONE;
404        let debug = format!("{addr:?}");
405        assert!(debug.contains("AccountAddress"));
406    }
407
408    #[test]
409    fn test_display() {
410        // Special address uses SHORT format (AIP-40)
411        let addr = AccountAddress::ONE;
412        let display = format!("{addr}");
413        assert_eq!(display, "0x1");
414
415        // Non-special address uses LONG format (AIP-40)
416        let mut bytes = [0u8; ADDRESS_LENGTH];
417        bytes[0] = 0xab;
418        bytes[ADDRESS_LENGTH - 1] = 0xcd;
419        let addr = AccountAddress::new(bytes);
420        let display = format!("{addr}");
421        assert!(display.starts_with("0x"));
422        assert_eq!(display.len(), 66); // Full 64 hex chars + "0x"
423    }
424
425    #[test]
426    fn test_from_hex_uppercase() {
427        let addr = AccountAddress::from_hex("0X1").unwrap();
428        assert_eq!(addr, AccountAddress::ONE);
429    }
430
431    #[test]
432    fn test_from_hex_invalid() {
433        let result = AccountAddress::from_hex("not_hex");
434        assert!(result.is_err());
435    }
436
437    #[test]
438    fn test_into_array() {
439        let addr = AccountAddress::new([42u8; ADDRESS_LENGTH]);
440        let bytes: [u8; ADDRESS_LENGTH] = addr.into();
441        assert_eq!(bytes, [42u8; ADDRESS_LENGTH]);
442    }
443
444    #[test]
445    fn test_as_ref() {
446        let addr = AccountAddress::ONE;
447        let slice: &[u8] = addr.as_ref();
448        assert_eq!(slice.len(), ADDRESS_LENGTH);
449    }
450
451    #[test]
452    fn test_equality() {
453        assert_eq!(AccountAddress::ONE, AccountAddress::ONE);
454        assert_ne!(AccountAddress::ONE, AccountAddress::THREE);
455    }
456
457    #[test]
458    fn test_hash() {
459        use std::collections::HashSet;
460        let mut set = HashSet::new();
461        set.insert(AccountAddress::ONE);
462        set.insert(AccountAddress::THREE);
463        assert_eq!(set.len(), 2);
464        assert!(set.contains(&AccountAddress::ONE));
465    }
466
467    #[test]
468    fn test_from_array() {
469        let bytes = [0x12u8; ADDRESS_LENGTH];
470        let addr: AccountAddress = bytes.into();
471        assert_eq!(addr.as_bytes(), &bytes);
472    }
473
474    #[test]
475    fn test_bcs_serialization() {
476        let addr = AccountAddress::ONE;
477        let serialized = aptos_bcs::to_bytes(&addr).unwrap();
478        let deserialized: AccountAddress = aptos_bcs::from_bytes(&serialized).unwrap();
479        assert_eq!(addr, deserialized);
480    }
481
482    #[test]
483    fn test_bcs_serialization_roundtrip_all_special() {
484        let addresses = [
485            AccountAddress::ZERO,
486            AccountAddress::ONE,
487            AccountAddress::THREE,
488            AccountAddress::FOUR,
489            AccountAddress::A,
490        ];
491
492        for addr in &addresses {
493            let serialized = aptos_bcs::to_bytes(addr).unwrap();
494            let deserialized: AccountAddress = aptos_bcs::from_bytes(&serialized).unwrap();
495            assert_eq!(addr, &deserialized);
496        }
497    }
498
499    #[test]
500    fn test_from_hex_too_long() {
501        // More than 64 hex characters (32 bytes)
502        let long_hex = "0x".to_string() + &"a".repeat(65);
503        let result = AccountAddress::from_hex(&long_hex);
504        assert!(result.is_err());
505    }
506
507    #[test]
508    fn test_from_hex_empty() {
509        let result = AccountAddress::from_hex("");
510        assert!(result.is_err());
511    }
512
513    #[test]
514    fn test_from_hex_just_prefix() {
515        let result = AccountAddress::from_hex("0x");
516        assert!(result.is_err());
517    }
518
519    #[test]
520    fn test_from_hex_with_leading_zeros() {
521        let addr = AccountAddress::from_hex("0x0000000000000001").unwrap();
522        assert_eq!(addr, AccountAddress::ONE);
523    }
524
525    #[test]
526    fn test_short_string_non_special() {
527        // Create an address that isn't "special" (first 15 bytes not all zeros)
528        let mut bytes = [0u8; ADDRESS_LENGTH];
529        bytes[0] = 0xab;
530        bytes[ADDRESS_LENGTH - 1] = 0xcd;
531        let addr = AccountAddress::new(bytes);
532
533        // Should return full hex without stripping zeros
534        let short = addr.to_short_string();
535        assert!(short.starts_with("0x"));
536        // Non-special addresses return the full form
537        assert_eq!(short.len(), 66);
538    }
539
540    #[test]
541    fn test_to_hex() {
542        let addr = AccountAddress::ONE;
543        let hex = addr.to_hex();
544        assert!(hex.starts_with("0x"));
545        assert_eq!(hex.len(), 66);
546        assert!(hex.ends_with('1'));
547    }
548
549    #[test]
550    fn test_json_deserialization_short() {
551        // JSON with short address
552        let json = "\"0x1\"";
553        let addr: AccountAddress = serde_json::from_str(json).unwrap();
554        assert_eq!(addr, AccountAddress::ONE);
555    }
556
557    #[test]
558    fn test_clone() {
559        let addr = AccountAddress::ONE;
560        // AccountAddress is Copy, so we can just copy it
561        let cloned = addr;
562        assert_eq!(addr, cloned);
563    }
564
565    #[test]
566    fn test_copy() {
567        let addr = AccountAddress::ONE;
568        let copied = addr; // Copy
569        assert_eq!(addr, copied);
570    }
571
572    #[test]
573    fn test_default() {
574        let addr = AccountAddress::default();
575        assert_eq!(addr, AccountAddress::ZERO);
576    }
577
578    #[test]
579    fn test_from_hex_mixed_case() {
580        let addr1 = AccountAddress::from_hex("0xAbCdEf").unwrap();
581        let addr2 = AccountAddress::from_hex("0xabcdef").unwrap();
582        let addr3 = AccountAddress::from_hex("0xABCDEF").unwrap();
583        assert_eq!(addr1, addr2);
584        assert_eq!(addr2, addr3);
585    }
586
587    #[test]
588    fn test_special_address_boundary() {
589        // Address with non-zero first 31 bytes is not special
590        let mut bytes = [0u8; ADDRESS_LENGTH];
591        bytes[14] = 1; // Set byte 14 to non-zero
592        let addr = AccountAddress::new(bytes);
593        assert!(!addr.is_special());
594
595        // Address with all zeros in first 31 bytes and last byte 1-15 is special
596        let mut bytes = [0u8; ADDRESS_LENGTH];
597        bytes[ADDRESS_LENGTH - 1] = 0x0f; // 15 is the max for special
598        let addr = AccountAddress::new(bytes);
599        assert!(addr.is_special());
600
601        // Address with last byte >= 16 is NOT special
602        let mut bytes = [0u8; ADDRESS_LENGTH];
603        bytes[ADDRESS_LENGTH - 1] = 0x10; // 16 is NOT special
604        let addr = AccountAddress::new(bytes);
605        assert!(!addr.is_special());
606
607        // Address with last byte == 0 is NOT special (it's ZERO)
608        let addr = AccountAddress::ZERO;
609        assert!(!addr.is_special());
610    }
611
612    #[test]
613    fn test_from_bytes_wrong_length() {
614        // Test with too short
615        let short = [0u8; 16];
616        let result = AccountAddress::from_bytes(short);
617        assert!(result.is_err());
618        assert!(
619            result
620                .unwrap_err()
621                .to_string()
622                .contains("expected 32 bytes")
623        );
624
625        // Test with too long
626        let long = [0u8; 64];
627        let result = AccountAddress::from_bytes(long);
628        assert!(result.is_err());
629
630        // Test with empty
631        let empty: [u8; 0] = [];
632        let result = AccountAddress::from_bytes(empty);
633        assert!(result.is_err());
634    }
635
636    #[test]
637    fn test_from_bytes_valid() {
638        let bytes = [0xab; ADDRESS_LENGTH];
639        let addr = AccountAddress::from_bytes(bytes).unwrap();
640        assert_eq!(addr.as_bytes(), &bytes);
641    }
642}