1mod cached;
2pub use cached::CachedSmartContractSignatureVerifier;
3mod chain_rpc_verifier;
4mod remote_signature_verifier;
5use crate::associations::AccountId;
6use alloy::{
7 primitives::{BlockNumber, Bytes},
8 providers::DynProvider,
9};
10pub use chain_rpc_verifier::*;
11pub use remote_signature_verifier::*;
12use std::{collections::HashMap, fs, path::Path, sync::Arc};
13use thiserror::Error;
14use tracing::info;
15use url::Url;
16use xmtp_common::{ErrorCode, MaybeSend, MaybeSync, RetryableError};
17
18static DEFAULT_CHAIN_URLS: &str = include_str!("chain_urls_default.json");
19
20#[derive(Debug, Error, ErrorCode)]
21pub enum VerifierError {
22 #[error("unexpected result from ERC-6492 {0}")]
26 UnexpectedERC6492Result(String),
27 #[error(transparent)]
28 #[error_code(inherit)]
29 FromHex(#[from] hex::FromHexError),
30 #[error(transparent)]
34 Provider(#[from] alloy::transports::RpcError<alloy::transports::TransportErrorKind>),
35 #[error(transparent)]
39 Url(#[from] url::ParseError),
40 #[error(transparent)]
44 Io(#[from] std::io::Error),
45 #[error(transparent)]
49 Serde(#[from] serde_json::Error),
50 #[error("Chain IDs must be preceded with eip155:")]
54 MalformedEipUrl,
55 #[error("verifier not present for chain ID {0}")]
59 NoVerifier(String),
60 #[error("hash was invalid length or otherwise malformed")]
64 InvalidHash(Vec<u8>),
65 #[error("{0}")]
69 Other(Box<dyn RetryableError>),
70}
71
72impl RetryableError for VerifierError {
73 fn is_retryable(&self) -> bool {
74 use VerifierError::*;
75 match self {
76 Io(_) => true,
77 NoVerifier(_) => true,
78 Provider(_) => true,
79 Other(o) => o.is_retryable(),
80 _ => false,
81 }
82 }
83}
84
85#[xmtp_common::async_trait]
86pub trait SmartContractSignatureVerifier: MaybeSend + MaybeSync {
87 async fn is_valid_signature(
95 &self,
96 account_id: AccountId,
97 hash: [u8; 32],
98 signature: Bytes,
99 block_number: Option<BlockNumber>,
100 ) -> Result<ValidationResponse, VerifierError>;
101}
102
103#[xmtp_common::async_trait]
104impl<T> SmartContractSignatureVerifier for Arc<T>
105where
106 T: SmartContractSignatureVerifier,
107{
108 async fn is_valid_signature(
109 &self,
110 account_id: AccountId,
111 hash: [u8; 32],
112 signature: Bytes,
113 block_number: Option<BlockNumber>,
114 ) -> Result<ValidationResponse, VerifierError> {
115 (**self)
116 .is_valid_signature(account_id, hash, signature, block_number)
117 .await
118 }
119}
120
121#[xmtp_common::async_trait]
122impl<T> SmartContractSignatureVerifier for &T
123where
124 T: SmartContractSignatureVerifier,
125{
126 async fn is_valid_signature(
127 &self,
128 account_id: AccountId,
129 hash: [u8; 32],
130 signature: Bytes,
131 block_number: Option<BlockNumber>,
132 ) -> Result<ValidationResponse, VerifierError> {
133 (*self)
134 .is_valid_signature(account_id, hash, signature, block_number)
135 .await
136 }
137}
138
139#[xmtp_common::async_trait]
140impl<T> SmartContractSignatureVerifier for Box<T>
141where
142 T: SmartContractSignatureVerifier + ?Sized,
143{
144 async fn is_valid_signature(
145 &self,
146 account_id: AccountId,
147 hash: [u8; 32],
148 signature: Bytes,
149 block_number: Option<BlockNumber>,
150 ) -> Result<ValidationResponse, VerifierError> {
151 (**self)
152 .is_valid_signature(account_id, hash, signature, block_number)
153 .await
154 }
155}
156
157#[derive(Clone)]
158pub struct ValidationResponse {
163 pub is_valid: bool,
165 pub block_number: Option<u64>,
167 pub error: Option<String>,
169}
170
171pub struct MultiSmartContractSignatureVerifier {
177 verifiers: HashMap<String, Box<dyn SmartContractSignatureVerifier>>,
178}
179
180impl std::fmt::Debug for MultiSmartContractSignatureVerifier {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 f.debug_struct("MultiSmartContractSignatureVerifier")
183 .field("verifiers", &self.verifiers.keys().collect::<Vec<_>>())
184 .finish()
185 }
186}
187
188impl MultiSmartContractSignatureVerifier {
189 pub fn new(urls: HashMap<String, url::Url>) -> Result<Self, VerifierError> {
194 let verifiers = urls
195 .into_iter()
196 .map(|(chain_id, url)| {
197 Ok::<_, VerifierError>((
198 chain_id,
199 Box::new(RpcSmartContractWalletVerifier::new(url.to_string())?) as Box<_>,
200 ))
201 })
202 .collect::<Result<HashMap<_, _>, _>>()?;
203
204 Ok(Self { verifiers })
205 }
206
207 pub fn new_providers(providers: HashMap<String, DynProvider>) -> Result<Self, VerifierError> {
211 let verifiers = providers
212 .into_iter()
213 .map(|(chain_id, provider)| {
214 (
215 chain_id,
216 Box::new(RpcSmartContractWalletVerifier::new_from_provider(provider)) as Box<_>,
217 )
218 })
219 .collect();
220 Ok(Self { verifiers })
221 }
222
223 pub fn new_from_env() -> Result<Self, VerifierError> {
225 let urls: HashMap<String, Url> = serde_json::from_str(DEFAULT_CHAIN_URLS)?;
226 Self::new(urls)?.upgrade()
227 }
228
229 pub fn new_from_file(path: impl AsRef<Path>) -> Result<Self, VerifierError> {
234 let json = fs::read_to_string(path.as_ref())?;
235 let urls: HashMap<String, Url> = serde_json::from_str(&json)?;
236
237 Self::new(urls)
238 }
239
240 pub fn upgrade(mut self) -> Result<Self, VerifierError> {
245 for (id, verifier) in self.verifiers.iter_mut() {
246 let eip_id = id.split(":").nth(1).ok_or(VerifierError::MalformedEipUrl)?;
248 if let Ok(url) = std::env::var(format!("CHAIN_RPC_{eip_id}")) {
249 *verifier = Box::new(RpcSmartContractWalletVerifier::new(url)?);
250 } else {
251 info!("No upgraded chain url for chain {id}, using default.");
252 };
253 }
254
255 if let Ok(url) = std::env::var("ANVIL_URL") {
256 info!("Adding anvil from env to the verifiers: {url}");
257 self.add_anvil(url)?;
258 } else {
259 use xmtp_configuration::DockerUrls;
260 let url = DockerUrls::anvil();
261 info!("adding default anvil url @{url}");
262 self.add_anvil(url)?;
263 }
264 Ok(self)
265 }
266
267 pub fn add_verifier(&mut self, id: String, url: String) -> Result<(), VerifierError> {
269 self.verifiers
270 .insert(id, Box::new(RpcSmartContractWalletVerifier::new(url)?));
271 Ok(())
272 }
273
274 pub fn add_anvil(&mut self, url: String) -> Result<(), VerifierError> {
276 self.verifiers.insert(
277 "eip155:31337".to_string(),
278 Box::new(RpcSmartContractWalletVerifier::new(url)?),
279 );
280 Ok(())
281 }
282}
283
284#[xmtp_common::async_trait]
285impl SmartContractSignatureVerifier for MultiSmartContractSignatureVerifier {
286 async fn is_valid_signature(
287 &self,
288 account_id: AccountId,
289 hash: [u8; 32],
290 signature: Bytes,
291 block_number: Option<BlockNumber>,
292 ) -> Result<ValidationResponse, VerifierError> {
293 if let Some(verifier) = self.verifiers.get(&account_id.chain_id) {
294 return verifier
295 .is_valid_signature(account_id, hash, signature, block_number)
296 .await;
297 }
298
299 Err(VerifierError::NoVerifier(account_id.chain_id))
300 }
301}