alkahest_rs/
lib.rs

1use alloy::{
2    primitives::{Address, FixedBytes, Log},
3    providers::Provider,
4    rpc::types::{Filter, TransactionReceipt},
5    signers::local::PrivateKeySigner,
6    sol_types::SolEvent,
7};
8use clients::{
9    arbiters::{ArbitersAddresses, ArbitersClient},
10    attestation::{AttestationAddresses, AttestationClient},
11    erc20::{Erc20Addresses, Erc20Client},
12    erc721::{Erc721Addresses, Erc721Client},
13    erc1155::{Erc1155Addresses, Erc1155Client},
14    string_obligation::{StringObligationAddresses, StringObligationClient},
15    token_bundle::{TokenBundleAddresses, TokenBundleClient},
16};
17use futures_util::StreamExt;
18use sol_types::EscrowClaimed;
19use types::{PublicProvider, WalletProvider};
20
21pub mod addresses;
22pub mod clients;
23pub mod contracts;
24pub mod fixtures;
25pub mod sol_types;
26pub mod types;
27pub mod utils;
28
29/// Configuration for contract addresses used by the AlkahestClient.
30/// Each field is optional and will use default addresses if not provided.
31#[derive(Debug, Clone)]
32pub struct AddressConfig {
33    pub arbiters_addresses: Option<ArbitersAddresses>,
34    pub erc20_addresses: Option<Erc20Addresses>,
35    pub erc721_addresses: Option<Erc721Addresses>,
36    pub erc1155_addresses: Option<Erc1155Addresses>,
37    pub token_bundle_addresses: Option<TokenBundleAddresses>,
38    pub attestation_addresses: Option<AttestationAddresses>,
39    pub string_obligation_addresses: Option<StringObligationAddresses>,
40}
41
42/// The main client for interacting with token trading and attestation functionality.
43///
44/// This client provides a unified interface for:
45/// - Trading ERC20, ERC721, and ERC1155 tokens
46/// - Managing token bundles
47/// - Creating and managing attestations
48/// - Setting up escrow arrangements
49/// - Handling trade fulfillment
50#[derive(Clone)]
51pub struct AlkahestClient {
52    pub wallet_provider: WalletProvider,
53    pub public_provider: PublicProvider,
54    pub address: Address,
55
56    pub arbiters: ArbitersClient,
57    pub erc20: Erc20Client,
58    pub erc721: Erc721Client,
59    pub erc1155: Erc1155Client,
60    pub token_bundle: TokenBundleClient,
61    pub attestation: AttestationClient,
62    pub string_obligation: StringObligationClient,
63}
64
65impl AlkahestClient {
66    /// Creates a new AlkahestClient instance.
67    ///
68    /// # Arguments
69    /// * `private_key` - The private key for signing transactions
70    /// * `rpc_url` - The RPC endpoint URL
71    /// * `addresses` - Optional custom contract addresses, uses defaults if None
72    ///
73    /// # Returns
74    /// * `Result<Self>` - The initialized client instance with all sub-clients configured
75    pub async fn new(
76        private_key: PrivateKeySigner,
77        rpc_url: impl ToString + Clone,
78        addresses: Option<AddressConfig>,
79    ) -> eyre::Result<Self> {
80        let wallet_provider =
81            utils::get_wallet_provider(private_key.clone(), rpc_url.clone()).await?;
82        let public_provider = utils::get_public_provider(rpc_url.clone()).await?;
83
84        macro_rules! make_client {
85            ($client:ident, $addresses:ident) => {
86                $client::new(
87                    private_key.clone(),
88                    rpc_url.clone(),
89                    addresses.clone().and_then(|a| a.$addresses),
90                )
91            };
92        }
93
94        Ok(AlkahestClient {
95            wallet_provider: wallet_provider.clone(),
96            public_provider: public_provider.clone(),
97            address: private_key.address(),
98            arbiters: make_client!(ArbitersClient, arbiters_addresses).await?,
99            erc20: make_client!(Erc20Client, erc20_addresses).await?,
100            erc721: make_client!(Erc721Client, erc721_addresses).await?,
101            erc1155: make_client!(Erc1155Client, erc1155_addresses).await?,
102            token_bundle: make_client!(TokenBundleClient, token_bundle_addresses).await?,
103            attestation: make_client!(AttestationClient, attestation_addresses).await?,
104            string_obligation: make_client!(StringObligationClient, string_obligation_addresses)
105                .await?,
106        })
107    }
108
109    /// Extracts an Attested event from a transaction receipt.
110    ///
111    /// # Arguments
112    /// * `receipt` - The transaction receipt to extract the event from
113    ///
114    /// # Returns
115    /// * `Result<Log<Attested>>` - The decoded Attested event log
116    pub fn get_attested_event(
117        receipt: TransactionReceipt,
118    ) -> eyre::Result<Log<contracts::IEAS::Attested>> {
119        let attested_event = receipt
120            .inner
121            .logs()
122            .iter()
123            .filter(|log| log.topic0() == Some(&contracts::IEAS::Attested::SIGNATURE_HASH))
124            .collect::<Vec<_>>()
125            .first()
126            .map(|log| log.log_decode::<contracts::IEAS::Attested>())
127            .ok_or_else(|| eyre::eyre!("No Attested event found"))??;
128
129        Ok(attested_event.inner)
130    }
131
132    /// Waits for a fulfillment event for a specific escrow arrangement.
133    ///
134    /// This function will:
135    /// 1. Check for existing fulfillment events from the specified block
136    /// 2. If none found, subscribe to new events and wait for fulfillment
137    ///
138    /// # Arguments
139    /// * `contract_address` - The address of the contract to monitor
140    /// * `buy_attestation` - The attestation UID of the buy order
141    /// * `from_block` - Optional block number to start searching from
142    ///
143    /// # Returns
144    /// * `Result<Log<EscrowClaimed>>` - The fulfillment event log when found
145    pub async fn wait_for_fulfillment(
146        &self,
147        contract_address: Address,
148        buy_attestation: FixedBytes<32>,
149        from_block: Option<u64>,
150    ) -> eyre::Result<Log<EscrowClaimed>> {
151        let filter = Filter::new()
152            .from_block(from_block.unwrap_or(0))
153            .address(contract_address)
154            .event_signature(EscrowClaimed::SIGNATURE_HASH)
155            .topic1(buy_attestation);
156
157        let logs = self.public_provider.get_logs(&filter).await?;
158        println!("initial logs: {:?}", logs);
159        if let Some(log) = logs
160            .iter()
161            .collect::<Vec<_>>()
162            .first()
163            .map(|log| log.log_decode::<EscrowClaimed>())
164        {
165            return Ok(log?.inner);
166        }
167
168        let sub = self.public_provider.subscribe_logs(&filter).await?;
169        let mut stream = sub.into_stream();
170
171        if let Some(log) = stream.next().await {
172            let log = log.log_decode::<EscrowClaimed>()?;
173            return Ok(log.inner);
174        }
175
176        Err(eyre::eyre!("No EscrowClaimed event found"))
177    }
178}