xrpl_address_macro/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{LitStr, parse_macro_input};
4
5/// Macro to convert an r-address to a 20-byte array at compile time.
6///
7/// # Example
8/// ```
9/// use xrpl_address_macro::r_address;
10/// const ACCOUNT: [u8; 20] = r_address!("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh");
11/// ```
12#[proc_macro]
13pub fn r_address(input: TokenStream) -> TokenStream {
14    let addr_lit = parse_macro_input!(input as LitStr);
15    let addr = addr_lit.value();
16
17    match decode_classic_address_to_20bytes(&addr) {
18        Some(bytes) => {
19            if bytes.len() != 20 {
20                return syn::Error::new(
21                    addr_lit.span(),
22                    format!("Address decoded to {} bytes, expected 20", bytes.len()),
23                )
24                .to_compile_error()
25                .into();
26            }
27
28            let bytes_tokens = bytes.iter().map(|b| quote! { #b });
29            let expanded = quote! {
30                [#(#bytes_tokens),*]
31            };
32
33            TokenStream::from(expanded)
34        }
35        None => syn::Error::new(addr_lit.span(), format!("Invalid r-address: {addr}"))
36            .to_compile_error()
37            .into(),
38    }
39}
40
41fn decode_classic_address_to_20bytes(addr: &str) -> Option<Vec<u8>> {
42    if !addr.starts_with('r') {
43        return None;
44    }
45    let alphabet =
46        bs58::Alphabet::new(b"rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz").ok()?;
47    let full = bs58::decode(addr)
48        .with_alphabet(&alphabet)
49        .into_vec()
50        .ok()?;
51    if full.len() < 1 + 20 + 4 {
52        return None;
53    }
54    // Version byte should be 0x00 for classic AccountID
55    if full[0] != 0x00 {
56        return None;
57    }
58    // Split payload and checksum
59    let (payload, checksum) = full.split_at(full.len() - 4);
60    // Verify checksum: double SHA-256 of payload, take first 4 bytes
61    use sha2::{Digest, Sha256};
62    let first = Sha256::digest(payload);
63    let second = Sha256::digest(first);
64    if &second[0..4] != checksum {
65        return None;
66    }
67    // Payload is version (1) + 20 bytes account id
68    if payload.len() != 1 + 20 {
69        return None;
70    }
71    Some(payload[1..].to_vec())
72}