1use alloy::{
2 dyn_abi::SolType,
3 primitives::{Address, FixedBytes, Log},
4 providers::Provider,
5 rpc::types::{Filter, TransactionReceipt},
6 signers::local::PrivateKeySigner,
7 sol_types::SolEvent,
8};
9use extensions::{
10 AlkahestExtension, BaseExtensions, HasArbiters, HasAttestation, HasCommitReveal, HasErc20,
11 HasErc721, HasErc1155, HasStringObligation, HasTokenBundle,
12};
13use futures_util::StreamExt;
14use serde::{Deserialize, Serialize};
15use types::EscrowClaimed;
16use std::sync::Arc;
17use types::{SharedPublicProvider, SharedWalletProvider};
18
19use crate::clients::{
20 arbiters::ArbitersAddresses, attestation::AttestationAddresses,
21 commit_reveal_obligation::CommitRevealObligationAddresses, erc20::Erc20Addresses,
22 erc721::Erc721Addresses, erc1155::Erc1155Addresses, native_token::NativeTokenAddresses,
23 string_obligation::StringObligationAddresses, token_bundle::TokenBundleAddresses,
24};
25
26pub type DefaultAlkahestClient = AlkahestClient<BaseExtensions>;
28
29pub mod addresses;
30pub mod clients;
31pub mod contracts;
32pub mod extensions;
33pub mod fixtures;
34
35pub mod types;
36pub mod utils;
37
38pub use clients::arbiters::ArbitersContract;
40pub use clients::attestation::AttestationContract;
41pub use clients::erc20::Erc20Contract;
42pub use clients::erc721::Erc721Contract;
43pub use clients::erc1155::Erc1155Contract;
44pub use clients::native_token::NativeTokenContract;
45pub use clients::commit_reveal_obligation::CommitRevealObligationContract;
46pub use clients::string_obligation::StringObligationContract;
47pub use clients::token_bundle::TokenBundleContract;
48pub use extensions::ContractModule;
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct DefaultExtensionConfig {
81 pub arbiters_addresses: ArbitersAddresses,
83 pub erc20_addresses: Erc20Addresses,
85 pub erc721_addresses: Erc721Addresses,
87 pub erc1155_addresses: Erc1155Addresses,
89 pub native_token_addresses: NativeTokenAddresses,
91 pub token_bundle_addresses: TokenBundleAddresses,
93 pub attestation_addresses: AttestationAddresses,
95 pub string_obligation_addresses: StringObligationAddresses,
97 pub commit_reveal_obligation_addresses: CommitRevealObligationAddresses,
99}
100
101impl Default for DefaultExtensionConfig {
102 fn default() -> Self {
106 crate::addresses::BASE_SEPOLIA_ADDRESSES
108 }
109}
110
111#[derive(Clone)]
112pub struct AlkahestClient<Extensions: AlkahestExtension = extensions::NoExtension> {
113 pub wallet_provider: SharedWalletProvider,
114 pub public_provider: SharedPublicProvider,
115 pub address: Address,
116 pub extensions: Extensions,
117 private_key: PrivateKeySigner,
118 rpc_url: String,
119}
120
121impl AlkahestClient<extensions::NoExtension> {
122 pub async fn new(
124 private_key: PrivateKeySigner,
125 rpc_url: impl ToString + Clone + Send,
126 ) -> eyre::Result<Self> {
127 let wallet_provider =
128 Arc::new(utils::get_wallet_provider(private_key.clone(), rpc_url.clone()).await?);
129 let public_provider = Arc::new(utils::get_public_provider(rpc_url.clone()).await?);
130
131 Ok(AlkahestClient {
132 wallet_provider,
133 public_provider,
134 address: private_key.address(),
135 extensions: extensions::NoExtension,
136 private_key,
137 rpc_url: rpc_url.to_string(),
138 })
139 }
140}
141
142impl AlkahestClient<BaseExtensions> {
143 pub async fn with_base_extensions(
146 private_key: PrivateKeySigner,
147 rpc_url: impl ToString + Clone + Send,
148 config: Option<DefaultExtensionConfig>,
149 ) -> eyre::Result<Self> {
150 let wallet_provider =
151 Arc::new(utils::get_wallet_provider(private_key.clone(), rpc_url.clone()).await?);
152 let public_provider = Arc::new(utils::get_public_provider(rpc_url.clone()).await?);
153
154 let providers = crate::types::ProviderContext {
155 wallet: wallet_provider.clone(),
156 public: public_provider.clone(),
157 signer: private_key.clone(),
158 };
159 let extensions = BaseExtensions::init(private_key.clone(), providers, config).await?;
160
161 Ok(AlkahestClient {
162 wallet_provider,
163 public_provider,
164 address: private_key.address(),
165 extensions,
166 private_key,
167 rpc_url: rpc_url.to_string(),
168 })
169 }
170}
171
172impl<Extensions: AlkahestExtension> AlkahestClient<Extensions> {
173 pub async fn extend<NewExt: AlkahestExtension>(
175 self,
176 config: Option<NewExt::Config>,
177 ) -> eyre::Result<AlkahestClient<extensions::JoinExtension<Extensions, NewExt>>> {
178 let providers = crate::types::ProviderContext {
179 wallet: self.wallet_provider.clone(),
180 public: self.public_provider.clone(),
181 signer: self.private_key.clone(),
182 };
183 let new_extension = NewExt::init(self.private_key.clone(), providers, config).await?;
184
185 let joined_extensions = extensions::JoinExtension {
186 left: self.extensions,
187 right: new_extension,
188 };
189
190 Ok(AlkahestClient {
191 wallet_provider: self.wallet_provider,
192 public_provider: self.public_provider,
193 address: self.address,
194 extensions: joined_extensions,
195 private_key: self.private_key,
196 rpc_url: self.rpc_url,
197 })
198 }
199
200 pub async fn extend_default<NewExt: AlkahestExtension>(
202 self,
203 ) -> eyre::Result<AlkahestClient<extensions::JoinExtension<Extensions, NewExt>>>
204 where
205 NewExt::Config: Default,
206 {
207 self.extend::<NewExt>(Some(NewExt::Config::default())).await
208 }
209
210 pub fn erc20_address(&self, contract: Erc20Contract) -> Address
219 where
220 Extensions: extensions::HasErc20,
221 {
222 match contract {
223 Erc20Contract::Eas => self.erc20().addresses.eas,
224 Erc20Contract::BarterUtils => self.erc20().addresses.barter_utils,
225 Erc20Contract::EscrowObligation => self.erc20().addresses.escrow_obligation_nontierable,
226 Erc20Contract::PaymentObligation => self.erc20().addresses.payment_obligation,
227 }
228 }
229
230 pub fn erc721_address(&self, contract: Erc721Contract) -> Address
232 where
233 Extensions: extensions::HasErc721,
234 {
235 match contract {
236 Erc721Contract::Eas => self.erc721().addresses.eas,
237 Erc721Contract::BarterUtils => self.erc721().addresses.barter_utils,
238 Erc721Contract::EscrowObligation => self.erc721().addresses.escrow_obligation_nontierable,
239 Erc721Contract::PaymentObligation => self.erc721().addresses.payment_obligation,
240 }
241 }
242
243 pub fn erc1155_address(&self, contract: Erc1155Contract) -> Address
245 where
246 Extensions: extensions::HasErc1155,
247 {
248 match contract {
249 Erc1155Contract::Eas => self.erc1155().addresses.eas,
250 Erc1155Contract::BarterUtils => self.erc1155().addresses.barter_utils,
251 Erc1155Contract::EscrowObligation => self.erc1155().addresses.escrow_obligation_nontierable,
252 Erc1155Contract::PaymentObligation => self.erc1155().addresses.payment_obligation,
253 }
254 }
255
256 pub fn token_bundle_address(&self, contract: TokenBundleContract) -> Address
258 where
259 Extensions: extensions::HasTokenBundle,
260 {
261 match contract {
262 TokenBundleContract::Eas => self.token_bundle().addresses.eas,
263 TokenBundleContract::BarterUtils => self.token_bundle().addresses.barter_utils,
264 TokenBundleContract::EscrowObligation => {
265 self.token_bundle().addresses.escrow_obligation_nontierable
266 }
267 TokenBundleContract::PaymentObligation => {
268 self.token_bundle().addresses.payment_obligation
269 }
270 }
271 }
272
273 pub fn attestation_address(&self, contract: AttestationContract) -> Address
275 where
276 Extensions: extensions::HasAttestation,
277 {
278 match contract {
279 AttestationContract::Eas => self.attestation().addresses.eas,
280 AttestationContract::EasSchemaRegistry => {
281 self.attestation().addresses.eas_schema_registry
282 }
283 AttestationContract::BarterUtils => self.attestation().addresses.barter_utils,
284 AttestationContract::EscrowObligation => self.attestation().addresses.escrow_obligation_nontierable,
285 AttestationContract::EscrowObligation2 => {
286 self.attestation().addresses.escrow_obligation_2_nontierable
287 }
288 }
289 }
290
291 pub fn string_obligation_address(&self, contract: StringObligationContract) -> Address
293 where
294 Extensions: extensions::HasStringObligation,
295 {
296 match contract {
297 StringObligationContract::Eas => self.string_obligation().addresses.eas,
298 StringObligationContract::Obligation => self.string_obligation().addresses.obligation,
299 }
300 }
301
302 pub fn commit_reveal_obligation_address(
304 &self,
305 contract: CommitRevealObligationContract,
306 ) -> Address
307 where
308 Extensions: extensions::HasCommitReveal,
309 {
310 match contract {
311 CommitRevealObligationContract::Eas => self.commit_reveal().addresses.eas,
312 CommitRevealObligationContract::Obligation => {
313 self.commit_reveal().addresses.obligation
314 }
315 }
316 }
317
318 pub fn arbiters_address(&self, contract: ArbitersContract) -> Address
320 where
321 Extensions: extensions::HasArbiters,
322 {
323 match contract {
324 ArbitersContract::Eas => self.arbiters().addresses.eas,
325 ArbitersContract::TrivialArbiter => self.arbiters().addresses.trivial_arbiter,
326 ArbitersContract::TrustedOracleArbiter => {
327 self.arbiters().addresses.trusted_oracle_arbiter
328 }
329 ArbitersContract::IntrinsicsArbiter => self.arbiters().addresses.intrinsics_arbiter,
330 ArbitersContract::IntrinsicsArbiter2 => self.arbiters().addresses.intrinsics_arbiter_2,
331 ArbitersContract::ERC8004Arbiter => self.arbiters().addresses.erc8004_arbiter,
332 ArbitersContract::AnyArbiter => self.arbiters().addresses.any_arbiter,
333 ArbitersContract::AllArbiter => self.arbiters().addresses.all_arbiter,
334 ArbitersContract::RecipientArbiter => self.arbiters().addresses.recipient_arbiter,
335 ArbitersContract::UidArbiter => self.arbiters().addresses.uid_arbiter,
336 }
337 }
338
339 pub fn get_attested_event(
347 receipt: TransactionReceipt,
348 ) -> eyre::Result<Log<contracts::IEAS::Attested>> {
349 let attested_event = receipt
350 .inner
351 .logs()
352 .iter()
353 .filter(|log| log.topic0() == Some(&contracts::IEAS::Attested::SIGNATURE_HASH))
354 .collect::<Vec<_>>()
355 .first()
356 .map(|log| log.log_decode::<contracts::IEAS::Attested>())
357 .ok_or_else(|| eyre::eyre!("No Attested event found"))??;
358
359 Ok(attested_event.inner)
360 }
361
362 pub async fn wait_for_fulfillment(
376 &self,
377 contract_address: Address,
378 buy_attestation: FixedBytes<32>,
379 from_block: Option<u64>,
380 ) -> eyre::Result<Log<EscrowClaimed>> {
381 let filter = Filter::new()
382 .from_block(from_block.unwrap_or(0))
383 .address(contract_address)
384 .event_signature(EscrowClaimed::SIGNATURE_HASH)
385 .topic1(buy_attestation);
386
387 let logs = self.public_provider.get_logs(&filter).await?;
388 println!("initial logs: {:?}", logs);
389 if let Some(log) = logs
390 .iter()
391 .collect::<Vec<_>>()
392 .first()
393 .map(|log| log.log_decode::<EscrowClaimed>())
394 {
395 return Ok(log?.inner);
396 }
397
398 let sub = self.public_provider.subscribe_logs(&filter).await?;
399 let mut stream = sub.into_stream();
400
401 if let Some(log) = stream.next().await {
402 let log = log.log_decode::<EscrowClaimed>()?;
403 return Ok(log.inner);
404 }
405
406 Err(eyre::eyre!("No EscrowClaimed event found"))
407 }
408
409 pub fn extract_obligation_data<ObligationData: SolType>(
418 &self,
419 attestation: &contracts::IEAS::Attestation,
420 ) -> eyre::Result<ObligationData::RustType> {
421 ObligationData::abi_decode(&attestation.data).map_err(Into::into)
422 }
423
424 pub async fn get_escrow_attestation(
431 &self,
432 fulfillment: &contracts::IEAS::Attestation,
433 ) -> eyre::Result<contracts::IEAS::Attestation>
434 where
435 Extensions: extensions::HasAttestation,
436 {
437 let eas = contracts::IEAS::new(self.attestation().addresses.eas, &self.wallet_provider);
438 let escrow = eas.getAttestation(fulfillment.refUID).call().await?;
439 Ok(escrow)
440 }
441
442 pub fn extract_demand_data<DemandData: SolType>(
451 &self,
452 escrow_attestation: &contracts::IEAS::Attestation,
453 ) -> eyre::Result<DemandData::RustType> {
454 use alloy::sol;
455 sol! {
456 struct ArbiterDemand {
457 address oracle;
458 bytes demand;
459 }
460 }
461 let arbiter_demand = ArbiterDemand::abi_decode(&escrow_attestation.data)?;
462 DemandData::abi_decode(&arbiter_demand.demand).map_err(Into::into)
463 }
464
465 pub async fn get_escrow_and_demand<DemandData: SolType>(
474 &self,
475 fulfillment: &contracts::IEAS::Attestation,
476 ) -> eyre::Result<(contracts::IEAS::Attestation, DemandData::RustType)>
477 where
478 Extensions: extensions::HasAttestation,
479 {
480 let escrow = self.get_escrow_attestation(fulfillment).await?;
481 let demand = self.extract_demand_data::<DemandData>(&escrow)?;
482 Ok((escrow, demand))
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489 use crate::addresses::{BASE_SEPOLIA_ADDRESSES, FILECOIN_CALIBRATION_ADDRESSES};
490
491 #[test]
492 fn test_default_extension_config_uses_base_sepolia() {
493 let default_config = DefaultExtensionConfig::default();
494
495 assert_eq!(
497 default_config.arbiters_addresses.eas,
498 BASE_SEPOLIA_ADDRESSES.arbiters_addresses.eas
499 );
500 assert_eq!(
501 default_config.erc20_addresses.barter_utils,
502 BASE_SEPOLIA_ADDRESSES.erc20_addresses.barter_utils
503 );
504 assert_eq!(
505 default_config.attestation_addresses.eas,
506 BASE_SEPOLIA_ADDRESSES.attestation_addresses.eas
507 );
508 }
509
510 #[test]
511 fn test_config_clone() {
512 let config = DefaultExtensionConfig::default();
513 let cloned = config.clone();
514
515 assert_eq!(config.arbiters_addresses.eas, cloned.arbiters_addresses.eas);
516 assert_eq!(
517 config.erc20_addresses.barter_utils,
518 cloned.erc20_addresses.barter_utils
519 );
520 }
521
522 #[test]
523 fn test_custom_config_with_struct_update_syntax() {
524 let custom_config = DefaultExtensionConfig {
525 arbiters_addresses: FILECOIN_CALIBRATION_ADDRESSES.arbiters_addresses,
526 ..BASE_SEPOLIA_ADDRESSES
527 };
528
529 assert_eq!(
531 custom_config.arbiters_addresses.eas,
532 FILECOIN_CALIBRATION_ADDRESSES.arbiters_addresses.eas
533 );
534
535 assert_eq!(
537 custom_config.erc20_addresses.eas,
538 BASE_SEPOLIA_ADDRESSES.erc20_addresses.eas
539 );
540 }
541
542 #[test]
543 fn test_all_address_fields_populated() {
544 let config = DefaultExtensionConfig::default();
545
546 assert_ne!(config.arbiters_addresses.eas, Address::ZERO);
548 assert_ne!(config.erc20_addresses.eas, Address::ZERO);
549 assert_ne!(config.erc721_addresses.eas, Address::ZERO);
550 assert_ne!(config.erc1155_addresses.eas, Address::ZERO);
551 assert_ne!(config.token_bundle_addresses.eas, Address::ZERO);
552 assert_ne!(config.attestation_addresses.eas, Address::ZERO);
553
554 assert_ne!(config.erc20_addresses.barter_utils, Address::ZERO);
556 assert_ne!(config.erc20_addresses.escrow_obligation_nontierable, Address::ZERO);
557 assert_ne!(config.erc20_addresses.payment_obligation, Address::ZERO);
558 }
559
560 #[test]
561 fn test_serialize_deserialize_default_extension_config() {
562 let original_config = DefaultExtensionConfig::default();
563
564 let json = serde_json::to_string(&original_config).expect("Failed to serialize");
566
567 let deserialized_config: DefaultExtensionConfig =
569 serde_json::from_str(&json).expect("Failed to deserialize");
570
571 assert_eq!(
573 original_config.arbiters_addresses.eas,
574 deserialized_config.arbiters_addresses.eas
575 );
576 assert_eq!(
577 original_config.arbiters_addresses.recipient_arbiter,
578 deserialized_config.arbiters_addresses.recipient_arbiter
579 );
580 assert_eq!(
581 original_config.erc20_addresses.eas,
582 deserialized_config.erc20_addresses.eas
583 );
584 assert_eq!(
585 original_config.erc20_addresses.barter_utils,
586 deserialized_config.erc20_addresses.barter_utils
587 );
588 assert_eq!(
589 original_config.erc721_addresses.eas,
590 deserialized_config.erc721_addresses.eas
591 );
592 assert_eq!(
593 original_config.erc1155_addresses.eas,
594 deserialized_config.erc1155_addresses.eas
595 );
596 assert_eq!(
597 original_config.token_bundle_addresses.eas,
598 deserialized_config.token_bundle_addresses.eas
599 );
600 assert_eq!(
601 original_config.attestation_addresses.eas,
602 deserialized_config.attestation_addresses.eas
603 );
604 assert_eq!(
605 original_config.string_obligation_addresses.eas,
606 deserialized_config.string_obligation_addresses.eas
607 );
608 }
609
610 #[test]
611 fn test_serialize_custom_config() {
612 let custom_config = DefaultExtensionConfig {
614 arbiters_addresses: FILECOIN_CALIBRATION_ADDRESSES.arbiters_addresses,
615 erc20_addresses: BASE_SEPOLIA_ADDRESSES.erc20_addresses,
616 ..FILECOIN_CALIBRATION_ADDRESSES
617 };
618
619 let json = serde_json::to_string(&custom_config).expect("Failed to serialize");
621
622 let deserialized_config: DefaultExtensionConfig =
624 serde_json::from_str(&json).expect("Failed to deserialize");
625
626 assert_eq!(
628 custom_config.arbiters_addresses.eas,
629 deserialized_config.arbiters_addresses.eas
630 );
631 assert_eq!(
632 FILECOIN_CALIBRATION_ADDRESSES.arbiters_addresses.eas,
633 deserialized_config.arbiters_addresses.eas
634 );
635 assert_eq!(
636 custom_config.erc20_addresses.eas,
637 deserialized_config.erc20_addresses.eas
638 );
639 assert_eq!(
640 BASE_SEPOLIA_ADDRESSES.erc20_addresses.eas,
641 deserialized_config.erc20_addresses.eas
642 );
643 }
644
645 #[test]
646 fn test_json_roundtrip_preserves_all_fields() {
647 let config = BASE_SEPOLIA_ADDRESSES;
648
649 let json = serde_json::to_value(&config).expect("Failed to serialize to value");
651 let roundtrip_config: DefaultExtensionConfig =
652 serde_json::from_value(json).expect("Failed to deserialize from value");
653
654 assert_eq!(
657 config.arbiters_addresses.eas,
658 roundtrip_config.arbiters_addresses.eas
659 );
660 assert_eq!(
661 config.arbiters_addresses.recipient_arbiter,
662 roundtrip_config.arbiters_addresses.recipient_arbiter
663 );
664 assert_eq!(
665 config.arbiters_addresses.trivial_arbiter,
666 roundtrip_config.arbiters_addresses.trivial_arbiter
667 );
668
669 assert_eq!(
671 config.erc20_addresses.eas,
672 roundtrip_config.erc20_addresses.eas
673 );
674 assert_eq!(
675 config.erc20_addresses.barter_utils,
676 roundtrip_config.erc20_addresses.barter_utils
677 );
678 assert_eq!(
679 config.erc20_addresses.escrow_obligation_nontierable,
680 roundtrip_config.erc20_addresses.escrow_obligation_nontierable
681 );
682 assert_eq!(
683 config.erc20_addresses.payment_obligation,
684 roundtrip_config.erc20_addresses.payment_obligation
685 );
686
687 assert_eq!(
689 config.erc721_addresses.eas,
690 roundtrip_config.erc721_addresses.eas
691 );
692 assert_eq!(
693 config.erc721_addresses.barter_utils,
694 roundtrip_config.erc721_addresses.barter_utils
695 );
696
697 assert_eq!(
699 config.erc1155_addresses.eas,
700 roundtrip_config.erc1155_addresses.eas
701 );
702 assert_eq!(
703 config.erc1155_addresses.barter_utils,
704 roundtrip_config.erc1155_addresses.barter_utils
705 );
706
707 assert_eq!(
709 config.token_bundle_addresses.eas,
710 roundtrip_config.token_bundle_addresses.eas
711 );
712 assert_eq!(
713 config.token_bundle_addresses.barter_utils,
714 roundtrip_config.token_bundle_addresses.barter_utils
715 );
716
717 assert_eq!(
719 config.attestation_addresses.eas,
720 roundtrip_config.attestation_addresses.eas
721 );
722 assert_eq!(
723 config.attestation_addresses.eas_schema_registry,
724 roundtrip_config.attestation_addresses.eas_schema_registry
725 );
726
727 assert_eq!(
729 config.string_obligation_addresses.eas,
730 roundtrip_config.string_obligation_addresses.eas
731 );
732 assert_eq!(
733 config.string_obligation_addresses.obligation,
734 roundtrip_config.string_obligation_addresses.obligation
735 );
736 }
737}