r402_core/scheme/
client.rs1use std::fmt::{self, Debug, Formatter};
4use std::future::Future;
5
6use compact_str::CompactString;
7
8use crate::chain::{ChainId, ChainIdPattern};
9use crate::error::ClientError;
10use crate::scheme::sealed::Sealed;
11use crate::wire::PaymentRequired;
12
13pub trait SchemeClient: super::SchemeId + Sealed + Send + Sync {
17 fn accept(&self, payment_required: &PaymentRequired) -> Vec<PaymentCandidate>;
20}
21
22pub struct PaymentCandidate {
24 pub chain_id: ChainId,
26 pub asset: CompactString,
28 pub amount: CompactString,
30 pub scheme: CompactString,
32 pub pay_to: CompactString,
34 pub signer: Box<dyn PaymentCandidateSigner + Send + Sync>,
36}
37
38impl Debug for PaymentCandidate {
39 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
40 f.debug_struct("PaymentCandidate")
41 .field("chain_id", &self.chain_id)
42 .field("asset", &self.asset)
43 .field("amount", &self.amount)
44 .field("scheme", &self.scheme)
45 .field("pay_to", &self.pay_to)
46 .finish_non_exhaustive()
47 }
48}
49
50impl PaymentCandidate {
51 pub async fn sign(&self) -> Result<String, ClientError> {
57 self.signer.sign_payment().await
58 }
59}
60
61pub trait PaymentCandidateSigner: Send + Sync {
64 fn sign_payment<'a>(
66 &'a self,
67 ) -> std::pin::Pin<Box<dyn Future<Output = Result<String, ClientError>> + Send + 'a>>;
68}
69
70pub trait PaymentSelector: Send + Sync {
72 fn select<'a>(&self, candidates: &[&'a PaymentCandidate]) -> Option<&'a PaymentCandidate>;
74}
75
76#[derive(Debug, Clone, Copy, Default)]
78pub struct FirstMatch;
79
80impl PaymentSelector for FirstMatch {
81 fn select<'a>(&self, candidates: &[&'a PaymentCandidate]) -> Option<&'a PaymentCandidate> {
82 candidates.first().copied()
83 }
84}
85
86#[derive(Debug, Default)]
88pub struct PreferChain(Vec<ChainIdPattern>);
89
90impl PreferChain {
91 pub fn new<P: Into<Vec<ChainIdPattern>>>(patterns: P) -> Self {
93 Self(patterns.into())
94 }
95
96 #[must_use]
98 pub fn or_chain<P: Into<Vec<ChainIdPattern>>>(mut self, patterns: P) -> Self {
99 self.0.extend(patterns.into());
100 self
101 }
102}
103
104impl PaymentSelector for PreferChain {
105 fn select<'a>(&self, candidates: &[&'a PaymentCandidate]) -> Option<&'a PaymentCandidate> {
106 for pattern in &self.0 {
107 if let Some(hit) = candidates.iter().find(|c| pattern.matches(&c.chain_id)) {
108 return Some(*hit);
109 }
110 }
111 candidates.first().copied()
112 }
113}
114
115#[derive(Debug, Clone, Copy)]
117pub struct MaxAmount(pub u128);
118
119impl PaymentSelector for MaxAmount {
120 fn select<'a>(&self, candidates: &[&'a PaymentCandidate]) -> Option<&'a PaymentCandidate> {
121 candidates
122 .iter()
123 .find(|c| c.amount.parse::<u128>().is_ok_and(|a| a <= self.0))
124 .copied()
125 }
126}
127
128pub trait PaymentPolicy: Send + Sync {
130 fn apply<'a>(&self, candidates: Vec<&'a PaymentCandidate>) -> Vec<&'a PaymentCandidate>;
132}
133
134#[derive(Debug, Default)]
136pub struct NetworkPolicy(Vec<ChainIdPattern>);
137
138impl NetworkPolicy {
139 pub fn new<P: Into<Vec<ChainIdPattern>>>(patterns: P) -> Self {
141 Self(patterns.into())
142 }
143}
144
145impl PaymentPolicy for NetworkPolicy {
146 fn apply<'a>(&self, candidates: Vec<&'a PaymentCandidate>) -> Vec<&'a PaymentCandidate> {
147 candidates
148 .into_iter()
149 .filter(|c| self.0.iter().any(|p| p.matches(&c.chain_id)))
150 .collect()
151 }
152}
153
154#[derive(Debug, Default)]
156pub struct SchemePolicy(Vec<CompactString>);
157
158impl SchemePolicy {
159 pub fn new<S: Into<CompactString>, I: IntoIterator<Item = S>>(schemes: I) -> Self {
161 Self(schemes.into_iter().map(Into::into).collect())
162 }
163}
164
165impl PaymentPolicy for SchemePolicy {
166 fn apply<'a>(&self, candidates: Vec<&'a PaymentCandidate>) -> Vec<&'a PaymentCandidate> {
167 candidates
168 .into_iter()
169 .filter(|c| self.0.iter().any(|s| s.as_str() == c.scheme.as_str()))
170 .collect()
171 }
172}
173
174#[derive(Debug, Clone, Copy)]
176pub struct MaxAmountPolicy(pub u128);
177
178impl PaymentPolicy for MaxAmountPolicy {
179 fn apply<'a>(&self, candidates: Vec<&'a PaymentCandidate>) -> Vec<&'a PaymentCandidate> {
180 candidates
181 .into_iter()
182 .filter(|c| c.amount.parse::<u128>().is_ok_and(|a| a <= self.0))
183 .collect()
184 }
185}