use crate::error::{TxCompilerError, TxCompilerErrorCode};
const TRON_ADDRESS_LEN: usize = 21;
const TRON_ADDRESS_PREFIX: u8 = 0x41;
pub fn tron_address_to_bytes(address: &str) -> Result<Vec<u8>, TxCompilerError> {
if let Some(stripped) = strip_hex_prefix(address) {
let bytes = hex::decode(stripped).map_err(|_| invalid_address(address))?;
enforce_shape(&bytes, address)?;
return Ok(bytes);
}
if address.starts_with('T') && address.len() == 34 {
let bytes = bs58::decode(address)
.with_check(None)
.into_vec()
.map_err(|_| invalid_address(address))?;
enforce_shape(&bytes, address)?;
return Ok(bytes);
}
Err(invalid_address(address))
}
fn enforce_shape(bytes: &[u8], address: &str) -> Result<(), TxCompilerError> {
if bytes.len() != TRON_ADDRESS_LEN || bytes[0] != TRON_ADDRESS_PREFIX {
return Err(invalid_address(address));
}
Ok(())
}
fn strip_hex_prefix(address: &str) -> Option<&str> {
let unprefixed = address.strip_prefix("0x").unwrap_or(address);
if unprefixed.len() != 42 {
return None;
}
let bytes = unprefixed.as_bytes();
if bytes[0] != b'4' || bytes[1] != b'1' {
return None;
}
if unprefixed[2..].bytes().all(|b| b.is_ascii_hexdigit()) {
Some(unprefixed)
} else {
None
}
}
fn invalid_address(address: &str) -> TxCompilerError {
TxCompilerError::with_details(
TxCompilerErrorCode::InvalidAddress,
"Invalid Tron address format",
serde_json::json!({ "address": address }),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decodes_hex_form() {
let bytes = tron_address_to_bytes("41d8da6bf26964af9d7eed9e03e53415d37aa96045").unwrap();
assert_eq!(bytes.len(), 21);
assert_eq!(bytes[0], 0x41);
}
#[test]
fn decodes_hex_form_with_0x_prefix() {
let bytes = tron_address_to_bytes("0x41d8da6bf26964af9d7eed9e03e53415d37aa96045").unwrap();
assert_eq!(bytes.len(), 21);
assert_eq!(bytes[0], 0x41);
}
#[test]
fn decodes_t_prefix_base58check() {
let bytes = tron_address_to_bytes("TLsV52sRDL79HXGGm9yzwKibb6BeruhUzy").unwrap();
assert_eq!(bytes.len(), 21);
assert_eq!(bytes[0], 0x41);
}
#[test]
fn rejects_short_hex() {
let err = tron_address_to_bytes("41ab").unwrap_err();
assert_eq!(err.code, TxCompilerErrorCode::InvalidAddress);
}
#[test]
fn returned_bytes_always_start_with_0x41() {
for input in [
"41d8da6bf26964af9d7eed9e03e53415d37aa96045",
"0x41d8da6bf26964af9d7eed9e03e53415d37aa96045",
"TLsV52sRDL79HXGGm9yzwKibb6BeruhUzy",
] {
let bytes = tron_address_to_bytes(input).unwrap_or_else(|e| {
panic!("expected {input} to decode: {e}");
});
assert_eq!(bytes.len(), 21, "{input}: wrong byte length");
assert_eq!(bytes[0], 0x41, "{input}: missing 0x41 prefix");
}
}
#[test]
fn rejects_non_tron_prefix_hex() {
let err = tron_address_to_bytes("d8da6bf26964af9d7eed9e03e53415d37aa96045").unwrap_err();
assert_eq!(err.code, TxCompilerErrorCode::InvalidAddress);
}
#[test]
fn rejects_invalid_base58_checksum() {
let err = tron_address_to_bytes("TLsV52sRDL79HXGGm9yzwKibb6BeruhUzz").unwrap_err();
assert_eq!(err.code, TxCompilerErrorCode::InvalidAddress);
}
}