o402 0.1.4

OpenAI-compatible gateway, paid with x402.
//! tron exact accepts (`experimental-tron` feature).

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

#[cfg(not(feature = "experimental-tron"))]
use super::feature_missing;
#[cfg(feature = "experimental-tron")]
use super::{
    accept_custom_decimals, custom_asset_address, invalid_asset_address, invalid_network,
    invalid_pay_to, named_ticker, no_deployment, require_exact,
};
use crate::config::{AcceptConfig, ConfigError};

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

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

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

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

    use alloy_primitives::U256;
    use r402_tron::TronExact;
    use r402_tron::chain::Address;

    require_exact(accept)?;
    let pay_to =
        Address::from_str(pay_to).map_err(|error| invalid_pay_to("Tron", pay_to, error))?;
    let token = token(accept, network)?;
    let method = transfer_method(accept, token.tip712.is_some())?;
    let deployed = token.amount(U256::from(amount));
    Ok(TronExact::price_tag(pay_to, &deployed, method))
}

#[cfg(feature = "experimental-tron")]
fn token(
    accept: &AcceptConfig,
    network: &ChainId,
) -> Result<r402_tron::chain::TronTokenDeployment, ConfigError> {
    use std::str::FromStr as _;

    use r402_tron::USDT;
    use r402_tron::chain::{Address, TronChainReference, TronTokenDeployment};

    let chain = TronChainReference::try_from(network.clone())
        .map_err(|error| invalid_network("tron", &accept.network, error))?;
    if named_ticker(accept, &["usdt"])?.is_some() {
        if accept.eip712_name.is_some() || accept.eip712_version.is_some() {
            return Err(ConfigError::Validation(
                "eip712_name is only valid on custom tron accepts".to_owned(),
            ));
        }
        return USDT::on(chain)
            .cloned()
            .ok_or_else(|| no_deployment("USDT", &accept.network));
    }
    let address = custom_asset_address(accept)?;
    let address =
        Address::from_str(address).map_err(|error| invalid_asset_address(address, error))?;
    let decimals = accept_custom_decimals(accept)?;
    let deployment = TronTokenDeployment::new(chain, address, decimals);
    match (
        accept.eip712_name.as_deref(),
        accept.eip712_version.as_deref(),
    ) {
        (None, None) => Ok(deployment),
        (Some(name), Some(version)) => Ok(deployment.with_tip712(name, version)),
        _ => Err(ConfigError::Validation(
            "custom Tron accepts need both eip712_name and eip712_version".to_owned(),
        )),
    }
}

#[cfg(feature = "experimental-tron")]
fn transfer_method(
    accept: &AcceptConfig,
    has_tip712: bool,
) -> Result<Option<r402_tron::AssetTransferMethod>, ConfigError> {
    match accept.transfer_method.as_deref() {
        None | Some("permit2") => Ok(Some(r402_tron::AssetTransferMethod::Permit2)),
        Some("eip3009") => {
            if !has_tip712 {
                return Err(ConfigError::Validation(
                    "transfer_method = \"eip3009\" requires eip712_name and eip712_version on tron accepts"
                        .to_owned(),
                ));
            }
            Ok(Some(r402_tron::AssetTransferMethod::Eip3009))
        }
        Some(other) => Err(ConfigError::Validation(format!(
            "unsupported transfer_method '{other}'"
        ))),
    }
}