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#[derive(Builder, Debug, Clone, PartialEq, Eq)]
12pub struct Resource {
13 pub url: Url,
15 #[builder(into)]
17 pub description: String,
18 #[builder(into)]
20 pub mime_type: String,
21 pub output_schema: Option<OutputSchema>,
23}
24
25#[derive(Builder, Debug, Clone)]
27pub struct TransportConfig<A: Address> {
28 #[builder(into)]
30 pub pay_to: A,
31 #[builder(into)]
33 pub asset: Asset<A>,
34 #[builder(into)]
36 pub amount: AmountValue,
37 pub max_timeout_seconds: u64,
39 pub resource: Resource,
41}
42
43#[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}