use std::time::Duration;
use r402_http::server::{FacilitatorClient, X402Middleware};
use r402_protocol::network::ChainIdPattern;
use r402_server::ResourceServer;
use url::Url;
use crate::config::{ConfigError, FacilitatorAuth, PaymentConfig};
pub(crate) fn resource_server(
payment: &PaymentConfig,
base_url: &Url,
) -> Result<ResourceServer, ConfigError> {
let fac_cfg = payment.facilitator.as_ref().ok_or_else(|| {
ConfigError::Validation(
"payment.facilitator.url is required when payment.enabled = true".to_owned(),
)
})?;
let url = fac_cfg.url.as_ref().ok_or_else(|| {
ConfigError::Validation(
"payment.facilitator.url is required when payment.enabled = true".to_owned(),
)
})?;
let mut fac = FacilitatorClient::try_from(url.as_str())
.map_err(|error| ConfigError::Validation(format!("facilitator client: {error}")))?;
fac = fac.with_timeout(Duration::from_secs(fac_cfg.timeout_secs));
fac = if fac_cfg.supported_cache_ttl_secs == 0 {
fac.without_supported_cache()
} else {
fac.with_supported_cache_ttl(Duration::from_secs(fac_cfg.supported_cache_ttl_secs))
};
if let Some(auth) = fac_cfg.auth.clone() {
fac = fac.with_auth(move || {
let auth = auth.clone();
async move { Ok(auth.to_path_keyed_json()) }
});
}
let mut mw = X402Middleware::from_facilitator(fac).with_base_url(base_url.clone());
mw = register_schemes(mw)?;
Ok(mw.resource_server().clone())
}
fn register_schemes(mut mw: X402Middleware) -> Result<X402Middleware, ConfigError> {
#[cfg(feature = "evm")]
{
mw = mw.with_scheme(parse_pattern("eip155:*")?, r402_evm::Eip155Exact);
mw = mw.with_scheme(parse_pattern("eip155:*")?, r402_evm::Eip155Upto);
}
#[cfg(feature = "svm")]
{
mw = mw.with_scheme(parse_pattern("solana:*")?, r402_svm::SolanaExact);
}
#[cfg(feature = "near")]
{
mw = mw.with_scheme(parse_pattern("near:*")?, r402_near::NearExact);
}
#[cfg(feature = "xrpl")]
{
mw = mw.with_scheme(parse_pattern("xrpl:*")?, r402_xrpl::XrplExact);
}
#[cfg(feature = "hedera")]
{
mw = mw.with_scheme(parse_pattern("hedera:*")?, r402_hedera::HederaExact);
}
#[cfg(feature = "avm")]
{
mw = mw.with_scheme(parse_pattern("algorand:*")?, r402_avm::AlgorandExact);
}
#[cfg(feature = "aptos")]
{
mw = mw.with_scheme(parse_pattern("aptos:*")?, r402_aptos::AptosExact);
}
#[cfg(feature = "keeta")]
{
mw = mw.with_scheme(parse_pattern("keeta:*")?, r402_keeta::KeetaExact);
}
#[cfg(feature = "tvm")]
{
mw = mw.with_scheme(parse_pattern("tvm:*")?, r402_tvm::TvmExact);
}
#[cfg(feature = "stellar")]
{
mw = mw.with_scheme(parse_pattern("stellar:*")?, r402_stellar::StellarExact);
}
#[cfg(feature = "concordium")]
{
mw = mw.with_scheme(
parse_pattern("ccd:*")?,
r402_concordium::ConcordiumExact::new(),
);
}
#[cfg(feature = "experimental-tron")]
{
mw = mw.with_scheme(parse_pattern("tron:*")?, r402_tron::TronExact);
}
let _ = &mut mw;
Ok(mw)
}
fn parse_pattern(raw: &str) -> Result<ChainIdPattern, ConfigError> {
raw.parse()
.map_err(|error| ConfigError::Validation(format!("invalid chain pattern '{raw}': {error}")))
}
impl FacilitatorAuth {
pub(crate) fn to_path_keyed_json(&self) -> serde_json::Value {
let mut root = serde_json::Map::new();
insert_path(&mut root, "verify", &self.verify);
insert_path(&mut root, "settle", &self.settle);
insert_path(&mut root, "supported", &self.supported);
serde_json::Value::Object(root)
}
}
fn insert_path(
root: &mut serde_json::Map<String, serde_json::Value>,
path: &str,
headers: &indexmap::IndexMap<String, crate::config::Secret<String>>,
) {
if headers.is_empty() {
return;
}
let mut object = serde_json::Map::new();
for (name, value) in headers {
object.insert(
name.clone(),
serde_json::Value::String(value.expose().clone()),
);
}
root.insert(path.to_owned(), serde_json::Value::Object(object));
}