use alloy_primitives::{Address, B256, Signature, U256, hex};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use std::fmt;
use std::fmt::{Display, Formatter};
use std::ops::Mul;
use std::str::FromStr;
use x402_types::chain::{ChainId, DeployedTokenAmount};
use x402_types::util::money_amount::{MoneyAmount, MoneyAmountParseError};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ChecksummedAddress(pub Address);
impl FromStr for ChecksummedAddress {
type Err = hex::FromHexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let address = Address::from_str(s)?;
Ok(Self(address))
}
}
impl Display for ChecksummedAddress {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.to_checksum(None))
}
}
impl Serialize for ChecksummedAddress {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.0.to_checksum(None))
}
}
impl<'de> Deserialize<'de> for ChecksummedAddress {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
impl From<ChecksummedAddress> for Address {
fn from(value: ChecksummedAddress) -> Self {
value.0
}
}
impl From<Address> for ChecksummedAddress {
fn from(address: Address) -> Self {
Self(address)
}
}
impl PartialEq<ChecksummedAddress> for Address {
fn eq(&self, other: &ChecksummedAddress) -> bool {
self.eq(&other.0)
}
}
pub mod decimal_u256 {
use alloy_primitives::U256;
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(value: &U256, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&value.to_string())
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<U256, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
U256::from_str_radix(&s, 10).map_err(serde::de::Error::custom)
}
}
pub const EIP155_NAMESPACE: &str = "eip155";
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Eip155ChainReference(u64);
impl Eip155ChainReference {
pub fn as_chain_id(&self) -> ChainId {
ChainId::new(EIP155_NAMESPACE, self.0.to_string())
}
}
impl From<Eip155ChainReference> for ChainId {
fn from(value: Eip155ChainReference) -> Self {
ChainId::new(EIP155_NAMESPACE, value.0.to_string())
}
}
impl From<&Eip155ChainReference> for ChainId {
fn from(value: &Eip155ChainReference) -> Self {
ChainId::new(EIP155_NAMESPACE, value.0.to_string())
}
}
impl TryFrom<ChainId> for Eip155ChainReference {
type Error = Eip155ChainReferenceFormatError;
fn try_from(value: ChainId) -> Result<Self, Self::Error> {
if value.namespace != EIP155_NAMESPACE {
return Err(Eip155ChainReferenceFormatError::InvalidNamespace(
value.namespace,
));
}
let chain_id: u64 = value.reference.parse().map_err(|_| {
Eip155ChainReferenceFormatError::InvalidReference(value.reference.clone())
})?;
Ok(Eip155ChainReference(chain_id))
}
}
impl TryFrom<&ChainId> for Eip155ChainReference {
type Error = Eip155ChainReferenceFormatError;
fn try_from(value: &ChainId) -> Result<Self, Self::Error> {
if value.namespace != EIP155_NAMESPACE {
return Err(Eip155ChainReferenceFormatError::InvalidNamespace(
value.namespace.clone(),
));
}
let chain_id: u64 = value.reference.parse().map_err(|_| {
Eip155ChainReferenceFormatError::InvalidReference(value.reference.clone())
})?;
Ok(Eip155ChainReference(chain_id))
}
}
#[derive(Debug, thiserror::Error)]
pub enum Eip155ChainReferenceFormatError {
#[error("Invalid namespace {0}, expected eip155")]
InvalidNamespace(String),
#[error("Invalid eip155 chain reference {0}")]
InvalidReference(String),
}
impl Eip155ChainReference {
pub fn new(chain_id: u64) -> Self {
Self(chain_id)
}
pub fn inner(&self) -> u64 {
self.0
}
}
impl Display for Eip155ChainReference {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[allow(dead_code)] pub struct Eip155TokenDeployment {
pub chain_reference: Eip155ChainReference,
pub address: Address,
pub decimals: u8,
pub transfer_method: AssetTransferMethod,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
#[serde(tag = "assetTransferMethod")]
pub enum AssetTransferMethod {
#[serde(rename = "eip3009")]
Eip3009 {
name: String,
version: String,
},
#[serde(rename = "permit2")]
Permit2,
}
impl<'de> Deserialize<'de> for AssetTransferMethod {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Debug, Deserialize)]
#[serde(untagged)]
#[allow(dead_code)]
enum AssetTransferMethodWire {
Permit2Tagged {
#[serde(rename = "assetTransferMethod")]
asset_transfer_method: Permit2Tag,
},
Eip3009Tagged {
#[serde(rename = "assetTransferMethod")]
asset_transfer_method: Eip3009Tag,
name: String,
version: String,
},
Eip3009Implicit {
name: String,
version: String,
},
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
enum Permit2Tag {
Permit2,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
enum Eip3009Tag {
Eip3009,
}
let wire = AssetTransferMethodWire::deserialize(deserializer)
.map_err(|e| serde::de::Error::custom(format!("invalid asset transfer method: {e}")))?;
Ok(match wire {
AssetTransferMethodWire::Permit2Tagged { .. } => AssetTransferMethod::Permit2,
AssetTransferMethodWire::Eip3009Tagged { name, version, .. }
| AssetTransferMethodWire::Eip3009Implicit { name, version } => {
AssetTransferMethod::Eip3009 { name, version }
}
})
}
}
#[allow(dead_code)] impl Eip155TokenDeployment {
pub fn amount<V: Into<u64>>(&self, v: V) -> DeployedTokenAmount<U256, Eip155TokenDeployment> {
DeployedTokenAmount {
amount: U256::from(v.into()),
token: self.clone(),
}
}
pub fn parse<V>(
&self,
v: V,
) -> Result<DeployedTokenAmount<U256, Eip155TokenDeployment>, MoneyAmountParseError>
where
V: TryInto<MoneyAmount>,
MoneyAmountParseError: From<<V as TryInto<MoneyAmount>>::Error>,
{
let money_amount = v.try_into()?;
let scale = money_amount.scale();
let token_scale = self.decimals as u32;
if scale > token_scale {
return Err(MoneyAmountParseError::WrongPrecision {
money: scale,
token: token_scale,
});
}
let scale_diff = token_scale - scale;
let multiplier = U256::from(10).pow(U256::from(scale_diff));
let digits = money_amount.mantissa();
let value = U256::from(digits).mul(multiplier);
Ok(DeployedTokenAmount {
amount: value,
token: self.clone(),
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct EOASignature(Signature);
impl AsRef<Signature> for EOASignature {
fn as_ref(&self) -> &Signature {
&self.0
}
}
impl Serialize for EOASignature {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bytes = self.0.as_bytes(); let hex = format!("0x{}", hex::encode(bytes));
serializer.serialize_str(&hex)
}
}
impl<'de> Deserialize<'de> for EOASignature {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct EOASignatureVisitor;
impl<'de> de::Visitor<'de> for EOASignatureVisitor {
type Value = EOASignature;
fn expecting(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("a 0x-prefixed 65-byte Ethereum signature hex string")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
let hex = value
.strip_prefix("0x")
.ok_or_else(|| E::custom("signature must start with 0x"))?;
let bytes = hex::decode(hex)
.map_err(|err| E::custom(format!("invalid hex signature: {err}")))?;
let arr: [u8; 65] = bytes
.try_into()
.map_err(|_| E::custom("signature must be exactly 65 bytes"))?;
let sig = alloy_primitives::Signature::from_raw(&arr)
.map_err(|err| E::custom(format!("invalid signature: {err}")))?;
Ok(EOASignature(sig))
}
}
deserializer.deserialize_str(EOASignatureVisitor)
}
}
pub trait EOASignatureExt {
fn r_bytes(&self) -> B256;
fn s_bytes(&self) -> B256;
fn v_legacy(&self) -> u8;
}
impl EOASignatureExt for EOASignature {
#[inline]
fn r_bytes(&self) -> B256 {
self.0.r_bytes()
}
#[inline]
fn s_bytes(&self) -> B256 {
self.0.s_bytes()
}
#[inline]
fn v_legacy(&self) -> u8 {
self.0.v_legacy()
}
}
impl EOASignatureExt for Signature {
#[inline]
fn r_bytes(&self) -> B256 {
B256::from(self.r())
}
#[inline]
fn s_bytes(&self) -> B256 {
B256::from(self.s())
}
#[inline]
fn v_legacy(&self) -> u8 {
27 + (self.v() as u8)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_deployment(decimals: u8) -> Eip155TokenDeployment {
let chain_ref = Eip155ChainReference::new(1); Eip155TokenDeployment {
chain_reference: chain_ref,
address: Address::ZERO,
decimals,
transfer_method: AssetTransferMethod::Eip3009 {
name: "TestToken".into(),
version: "2".into(),
},
}
}
#[test]
fn test_parse_whole_number() {
let deployment = create_test_deployment(6); let result = deployment.parse("100");
assert!(result.is_ok());
assert_eq!(result.unwrap().amount, U256::from(100_000_000u64)); }
#[test]
fn test_parse_with_decimals() {
let deployment = create_test_deployment(6);
let result = deployment.parse("1.50");
assert!(result.is_ok());
assert_eq!(result.unwrap().amount, U256::from(1_500_000u64)); }
#[test]
fn test_parse_zero_decimals() {
let deployment = create_test_deployment(0);
let result = deployment.parse("42");
assert!(result.is_ok());
assert_eq!(result.unwrap().amount, U256::from(42u64));
}
#[test]
fn test_parse_precision_too_high() {
let deployment = create_test_deployment(2); let result = deployment.parse("1.234"); assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, MoneyAmountParseError::WrongPrecision { .. }));
}
#[test]
fn test_parse_exact_precision() {
let deployment = create_test_deployment(9); let result = deployment.parse("0.123456789");
assert!(result.is_ok());
assert_eq!(result.unwrap().amount, U256::from(123_456_789u64));
}
#[test]
fn test_parse_smallest_amount() {
let deployment = create_test_deployment(6);
let result = deployment.parse("0.000001");
assert!(result.is_ok());
assert_eq!(result.unwrap().amount, U256::from(1u64));
}
#[test]
fn test_parse_with_currency_symbol() {
let deployment = create_test_deployment(6);
let result = deployment.parse("$10.50");
assert!(result.is_ok());
assert_eq!(result.unwrap().amount, U256::from(10_500_000u64));
}
#[test]
fn test_parse_with_commas() {
let deployment = create_test_deployment(6);
let result = deployment.parse("1,000");
assert!(result.is_ok());
assert_eq!(result.unwrap().amount, U256::from(1_000_000_000u64));
}
#[test]
fn test_parse_large_amount() {
let deployment = create_test_deployment(6);
let result = deployment.parse("999999999");
assert!(result.is_ok());
assert_eq!(result.unwrap().amount, U256::from(999_999_999_000_000u64));
}
#[test]
fn test_parse_very_large_amount_with_high_decimals() {
let deployment = create_test_deployment(18); let result = deployment.parse("999999999"); assert!(result.is_ok());
let expected = U256::from(999_999_999u64) * U256::from(10).pow(U256::from(18));
assert_eq!(result.unwrap().amount, expected);
}
}