o402 0.1.2

OpenAI-compatible gateway, paid with x402.
//! ccd exact accepts (`concordium` feature). Named usdr / ccd only.

use r402_protocol::network::ChainId;
use r402_protocol::payment::PriceTag;

#[cfg(not(feature = "concordium"))]
use super::feature_missing;
#[cfg(feature = "concordium")]
use super::{
    amount_u64, invalid_network, invalid_pay_to, named_ticker, no_deployment, reject_evm_fields,
    require_exact,
};
use crate::config::{AcceptConfig, ConfigError};

#[cfg(not(feature = "concordium"))]
pub(super) fn build(
    _accept: &AcceptConfig,
    _network: &ChainId,
    _amount: u128,
    _pay_to: &str,
) -> Result<PriceTag, ConfigError> {
    Err(feature_missing("ccd", "concordium"))
}

#[cfg(not(feature = "concordium"))]
pub(super) fn decimals(_accept: &AcceptConfig, _network: &ChainId) -> Result<u8, ConfigError> {
    Err(feature_missing("ccd", "concordium"))
}

#[cfg(feature = "concordium")]
pub(super) fn decimals(accept: &AcceptConfig, network: &ChainId) -> Result<u8, ConfigError> {
    Ok(token(accept, network)?.decimals)
}

#[cfg(feature = "concordium")]
pub(super) fn build(
    accept: &AcceptConfig,
    network: &ChainId,
    amount: u128,
    pay_to: &str,
) -> Result<PriceTag, ConfigError> {
    use std::str::FromStr as _;

    use r402_concordium::ConcordiumExact;
    use r402_concordium::chain::ConcordiumAddress;

    require_exact(accept)?;
    reject_evm_fields(accept, "ccd")?;
    if accept.asset_address.is_some() {
        return Err(custom_rejected());
    }
    let amount = amount_u64(amount, "ccd")?;
    let pay_to = ConcordiumAddress::from_str(pay_to)
        .map_err(|error| invalid_pay_to("Concordium", pay_to, error))?;
    let token = token(accept, network)?;
    Ok(ConcordiumExact::price_tag(pay_to, token.amount(amount)))
}

#[cfg(feature = "concordium")]
fn token(
    accept: &AcceptConfig,
    network: &ChainId,
) -> Result<r402_concordium::chain::ConcordiumTokenDeployment, ConfigError> {
    use r402_concordium::chain::ConcordiumChainReference;
    use r402_concordium::{CCD, USDR};

    let chain = ConcordiumChainReference::try_from(network.clone())
        .map_err(|error| invalid_network("ccd", &accept.network, error))?;
    let ticker = named_ticker(accept, &["usdr", "ccd"])?.ok_or_else(custom_rejected)?;
    match ticker {
        "usdr" if chain == ConcordiumChainReference::MAINNET => Ok(*USDR::concordium()),
        "usdr" if chain == ConcordiumChainReference::TESTNET => Ok(*USDR::concordium_testnet()),
        "ccd" if chain == ConcordiumChainReference::MAINNET => Ok(*CCD::concordium()),
        "ccd" if chain == ConcordiumChainReference::TESTNET => Ok(*CCD::concordium_testnet()),
        "usdr" => Err(no_deployment("USDR", &accept.network)),
        "ccd" => Err(no_deployment("CCD", &accept.network)),
        _ => Err(custom_rejected()),
    }
}

#[cfg(feature = "concordium")]
fn custom_rejected() -> ConfigError {
    ConfigError::Validation(
        "ccd accepts do not support custom asset_address; use asset = \"usdr\" or \"ccd\""
            .to_owned(),
    )
}