x402_kit/
config.rs

1use bon::Builder;
2use url::Url;
3
4use crate::{
5    concepts::{Address, Asset, NetworkFamily, Scheme},
6    transport::PaymentRequirements,
7    types::{AmountValue, AnyJson, OutputSchema},
8};
9
10/// Resource configuration.
11#[derive(Builder, Debug, Clone, PartialEq, Eq)]
12pub struct Resource {
13    /// Optional resource URL.
14    pub url: Url,
15    /// Optional description of the resource.
16    #[builder(into)]
17    pub description: String,
18    /// Optional MIME type of the resource.
19    #[builder(into)]
20    pub mime_type: String,
21    /// Optional output schema for the payment payload.
22    pub output_schema: Option<OutputSchema>,
23}
24
25/// Per transport configuration options.
26#[derive(Builder, Debug, Clone)]
27pub struct TransportConfig<A: Address> {
28    /// The address to use for payments.
29    #[builder(into)]
30    pub pay_to: A,
31    /// The asset for the payment
32    #[builder(into)]
33    pub asset: Asset<A>,
34    /// The amount of the asset to pay, in smallest units.
35    #[builder(into)]
36    pub amount: AmountValue,
37    /// Maximum timeout in seconds for the payment to be completed.
38    pub max_timeout_seconds: u64,
39    /// Optional resource configuration.
40    pub resource: Resource,
41}
42
43/// Payment requirements configuration for a given scheme and transport.
44#[derive(Builder, Debug, Clone)]
45pub struct PaymentRequirementsConfig<S, A>
46where
47    S: Scheme,
48    A: Address<Network = S::Network>,
49{
50    pub scheme: S,
51    pub transport: TransportConfig<A>,
52    pub extra: Option<AnyJson>,
53}
54
55impl<S, A> From<PaymentRequirementsConfig<S, A>> for PaymentRequirements
56where
57    S: Scheme,
58    A: Address<Network = S::Network>,
59{
60    fn from(config: PaymentRequirementsConfig<S, A>) -> Self {
61        PaymentRequirements {
62            scheme: S::SCHEME_NAME.to_string(),
63            network: config.scheme.network().network_name().to_string(),
64            max_amount_required: config.transport.amount,
65            resource: config.transport.resource.url,
66            description: config.transport.resource.description,
67            mime_type: config.transport.resource.mime_type,
68            pay_to: config.transport.pay_to.to_string(),
69            max_timeout_seconds: config.transport.max_timeout_seconds,
70            asset: config.transport.asset.address.to_string(),
71            output_schema: config.transport.resource.output_schema,
72            extra: config.extra,
73        }
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use alloy_primitives::address;
80    use serde_json::Value;
81
82    use crate::networks::evm::{
83        EvmNetwork, ExplicitEvmAsset, ExplicitEvmNetwork, assets::UsdcBaseSepolia,
84        networks::BaseSepolia,
85    };
86
87    struct ExampleExactEvmScheme(EvmNetwork);
88
89    impl Scheme for ExampleExactEvmScheme {
90        type Network = EvmNetwork;
91        type Payload = Value;
92        const SCHEME_NAME: &'static str = "exact";
93
94        fn network(&self) -> &Self::Network {
95            &self.0
96        }
97    }
98
99    use super::*;
100
101    #[test]
102    fn test_configure_payment_requirements() {
103        let resource = Resource::builder()
104            .url(Url::parse("https://example.com/payment").unwrap())
105            .description("Payment for services".to_string())
106            .mime_type("application/json".to_string())
107            .build();
108
109        let config = PaymentRequirementsConfig::builder()
110            .transport(
111                TransportConfig::builder()
112                    .amount(1000u64)
113                    .asset(UsdcBaseSepolia)
114                    .max_timeout_seconds(300)
115                    .pay_to(address!("0x3CB9B3bBfde8501f411bB69Ad3DC07908ED0dE20"))
116                    .resource(resource)
117                    .build(),
118            )
119            .scheme(ExampleExactEvmScheme(BaseSepolia::NETWORK))
120            .build();
121
122        let payment_requirements = PaymentRequirements::from(config);
123
124        assert_eq!(payment_requirements.scheme, "exact");
125        assert_eq!(payment_requirements.network, "base-sepolia");
126        assert_eq!(payment_requirements.max_amount_required, 1000u64.into());
127        assert_eq!(
128            payment_requirements.resource,
129            Url::parse("https://example.com/payment").unwrap()
130        );
131        assert_eq!(payment_requirements.description, "Payment for services");
132        assert_eq!(payment_requirements.mime_type, "application/json");
133        assert_eq!(
134            payment_requirements.pay_to,
135            "0x3CB9B3bBfde8501f411bB69Ad3DC07908ED0dE20"
136        );
137        assert_eq!(payment_requirements.max_timeout_seconds, 300);
138        assert_eq!(
139            payment_requirements.asset,
140            UsdcBaseSepolia::ASSET.address.to_string()
141        );
142    }
143}