mod algorand;
mod aptos;
mod concordium;
mod eip155;
mod hedera;
mod keeta;
mod near;
mod solana;
mod stellar;
mod tron;
mod tvm;
mod xrpl;
use r402_protocol::network::{ChainId, ChainIdPattern};
use r402_protocol::payment::PriceTag;
use crate::config::{AcceptConfig, ConfigError};
pub(super) fn build_tag(
accept: &AcceptConfig,
amount: u128,
pay_to: &indexmap::IndexMap<String, String>,
) -> Result<PriceTag, ConfigError> {
let network = parse_chain(&accept.network)?;
let pay_to = pay_to_for(&network, pay_to)?;
match network.namespace() {
"eip155" => eip155::build(accept, &network, amount, pay_to),
"solana" => solana::build(accept, &network, amount, pay_to),
"near" => near::build(accept, &network, amount, pay_to),
"xrpl" => xrpl::build(accept, &network, amount, pay_to),
"hedera" => hedera::build(accept, &network, amount, pay_to),
"algorand" => algorand::build(accept, &network, amount, pay_to),
"aptos" => aptos::build(accept, &network, amount, pay_to),
"keeta" => keeta::build(accept, &network, amount, pay_to),
"tvm" => tvm::build(accept, &network, amount, pay_to),
"stellar" => stellar::build(accept, &network, amount, pay_to),
"ccd" => concordium::build(accept, &network, amount, pay_to),
"tron" => tron::build(accept, &network, amount, pay_to),
other => Err(unsupported_namespace(other)),
}
}
pub(super) fn accept_decimals(accept: &AcceptConfig) -> Result<u8, ConfigError> {
if let Some(decimals) = accept.decimals {
return u8::try_from(decimals).map_err(|_| {
ConfigError::Validation(format!(
"payment.accepts decimals {decimals} is out of range"
))
});
}
let network = parse_chain(&accept.network)?;
match network.namespace() {
"eip155" => eip155::decimals(accept, &network),
"solana" => solana::decimals(accept, &network),
"near" => near::decimals(accept, &network),
"xrpl" => xrpl::decimals(accept, &network),
"hedera" => hedera::decimals(accept, &network),
"algorand" => algorand::decimals(accept, &network),
"aptos" => aptos::decimals(accept, &network),
"keeta" => keeta::decimals(accept, &network),
"tvm" => tvm::decimals(accept, &network),
"stellar" => stellar::decimals(accept, &network),
"ccd" => concordium::decimals(accept, &network),
"tron" => tron::decimals(accept, &network),
other => Err(unsupported_namespace(other)),
}
}
fn pay_to_for<'a>(
network: &ChainId,
pay_to: &'a indexmap::IndexMap<String, String>,
) -> Result<&'a str, ConfigError> {
for (pattern, address) in pay_to {
let parsed: ChainIdPattern = pattern.parse().map_err(|error| {
ConfigError::Validation(format!(
"invalid payment.pay_to pattern '{pattern}': {error}"
))
})?;
if parsed.matches(network) {
return Ok(address.as_str());
}
}
Err(ConfigError::Validation(format!(
"payment.pay_to has no address for {network}"
)))
}
fn parse_chain(raw: &str) -> Result<ChainId, ConfigError> {
raw.parse().map_err(|error| {
ConfigError::Validation(format!("invalid accept network '{raw}': {error}"))
})
}
fn unsupported_namespace(namespace: &str) -> ConfigError {
ConfigError::Validation(format!("network namespace '{namespace}' is not supported"))
}
#[cfg(not(all(
feature = "evm",
feature = "svm",
feature = "near",
feature = "xrpl",
feature = "hedera",
feature = "avm",
feature = "aptos",
feature = "keeta",
feature = "tvm",
feature = "stellar",
feature = "concordium",
feature = "experimental-tron",
)))]
fn feature_missing(namespace: &str, feature: &str) -> ConfigError {
ConfigError::Validation(format!(
"network namespace '{namespace}' is not enabled in this build (cargo feature '{feature}')"
))
}
fn require_exact(accept: &AcceptConfig) -> Result<(), ConfigError> {
if accept.scheme == "exact" {
Ok(())
} else {
Err(ConfigError::Validation(format!(
"scheme = \"upto\" is only supported on eip155 networks (got {})",
accept.network
)))
}
}
fn reject_evm_fields(accept: &AcceptConfig, namespace: &str) -> Result<(), ConfigError> {
if accept.transfer_method.is_some() {
return Err(ConfigError::Validation(format!(
"transfer_method is not valid on {namespace} accepts"
)));
}
if accept.eip712_name.is_some() {
return Err(ConfigError::Validation(format!(
"eip712_name is not valid on {namespace} accepts"
)));
}
if accept.eip712_version.is_some() {
return Err(ConfigError::Validation(format!(
"eip712_version is not valid on {namespace} accepts"
)));
}
Ok(())
}
fn named_ticker<'a>(
accept: &'a AcceptConfig,
allowed: &[&str],
) -> Result<Option<&'a str>, ConfigError> {
match accept.asset.as_deref() {
None => Ok(None),
Some(ticker) if allowed.contains(&ticker) => Ok(Some(ticker)),
Some(other) => Err(unknown_asset(other, allowed)),
}
}
fn unknown_asset(got: &str, allowed: &[&str]) -> ConfigError {
ConfigError::Validation(format!(
"unknown payment.accepts asset '{got}'; use {}",
tickers_hint(allowed)
))
}
fn tickers_hint(allowed: &[&str]) -> String {
match allowed {
[one] => format!("asset = \"{one}\""),
[first, second] => format!("asset = \"{first}\" or \"{second}\""),
_ => "a named asset".to_owned(),
}
}
fn custom_asset_address(accept: &AcceptConfig) -> Result<&str, ConfigError> {
accept.asset_address.as_deref().ok_or_else(|| {
ConfigError::Validation("custom payment.accepts entries need asset_address".to_owned())
})
}
fn accept_custom_decimals(accept: &AcceptConfig) -> Result<u8, ConfigError> {
let decimals = accept.decimals.ok_or_else(|| {
ConfigError::Validation("custom payment.accepts entries need decimals".to_owned())
})?;
u8::try_from(decimals).map_err(|_| {
ConfigError::Validation(format!(
"payment.accepts decimals {decimals} is out of range"
))
})
}
fn amount_u64(amount: u128, namespace: &str) -> Result<u64, ConfigError> {
u64::try_from(amount).map_err(|_| {
ConfigError::Validation(format!("exact amount exceeds u64 on {namespace} accepts"))
})
}
#[cfg(feature = "stellar")]
fn amount_i128(amount: u128) -> Result<i128, ConfigError> {
i128::try_from(amount).map_err(|_| {
ConfigError::Validation("exact amount exceeds i128 on stellar accepts".to_owned())
})
}
fn no_deployment(symbol: &str, network: &str) -> ConfigError {
ConfigError::Validation(format!("no {symbol} deployment for {network}"))
}
fn invalid_network(kind: &str, network: &str, error: impl std::fmt::Display) -> ConfigError {
ConfigError::Validation(format!("invalid {kind} network '{network}': {error}"))
}
fn invalid_pay_to(kind: &str, pay_to: &str, error: impl std::fmt::Display) -> ConfigError {
ConfigError::Validation(format!("invalid {kind} pay_to '{pay_to}': {error}"))
}
fn invalid_asset_address(address: &str, error: impl std::fmt::Display) -> ConfigError {
ConfigError::Validation(format!("invalid asset_address '{address}': {error}"))
}
#[cfg(test)]
mod tests;