1use std::sync::Arc;
2
3pub use alloy_provider;
4use alloy_rpc_types::TransactionReceipt;
5use anyhow::Context;
6
7use crate::network::{PodNetwork, PodTransactionRequest};
8use alloy_json_rpc::{RpcRecv, RpcSend};
9use alloy_network::{EthereumWallet, Network, NetworkWallet, TransactionBuilder};
10use alloy_provider::{
11 fillers::{JoinFill, RecommendedFillers, TxFiller, WalletFiller},
12 Identity, PendingTransactionBuilder, Provider, ProviderBuilder, ProviderLayer, RootProvider,
13 SendableTx,
14};
15use alloy_pubsub::Subscription;
16use async_trait::async_trait;
17
18use alloy_transport::{TransportError, TransportResult};
19use futures::StreamExt;
20use pod_types::{
21 consensus::Committee,
22 ledger::log::VerifiableLog,
23 metadata::{MetadataWrappedItem, RegularReceiptMetadata},
24 pagination::{ApiPaginatedResult, CursorPaginationRequest},
25 rpc::filter::LogFilter,
26};
27
28use alloy_primitives::{Address, U256};
29
30use pod_types::Timestamp;
31
32pub struct PodProviderBuilder<L, F>(ProviderBuilder<L, F, PodNetwork>);
33
34impl
35 PodProviderBuilder<
36 Identity,
37 JoinFill<Identity, <PodNetwork as RecommendedFillers>::RecommendedFillers>,
38 >
39{
40 pub fn with_recommended_settings() -> Self {
47 Self(PodProviderBuilder::default().0.with_recommended_fillers())
48 }
49}
50
51impl Default for PodProviderBuilder<Identity, Identity> {
52 fn default() -> Self {
53 Self(ProviderBuilder::<_, _, PodNetwork>::default())
54 }
55}
56
57impl PodProviderBuilder<Identity, Identity> {
58 pub fn new() -> Self {
59 Self::default()
60 }
61}
62
63impl<L, F> PodProviderBuilder<L, F> {
64 pub async fn on_url<U: AsRef<str>>(self, url: U) -> Result<PodProvider, TransportError>
68 where
69 L: ProviderLayer<RootProvider<PodNetwork>, PodNetwork>,
70 F: TxFiller<PodNetwork> + ProviderLayer<L::Provider, PodNetwork>,
71 F::Provider: 'static,
72 {
73 let alloy_provider = self.0.connect(url.as_ref()).await?;
74 Ok(PodProvider::new(alloy_provider))
75 }
76
77 pub fn wallet<W>(self, wallet: W) -> PodProviderBuilder<L, JoinFill<F, WalletFiller<W>>>
79 where
80 W: NetworkWallet<PodNetwork>,
81 {
82 PodProviderBuilder::<_, _>(self.0.wallet(wallet))
83 }
84
85 pub fn with_private_key(
86 self,
87 key: crate::SigningKey,
88 ) -> PodProviderBuilder<L, JoinFill<F, WalletFiller<EthereumWallet>>> {
89 let signer = crate::PrivateKeySigner::from_signing_key(key);
90
91 self.wallet(crate::EthereumWallet::new(signer))
92 }
93
94 pub async fn from_env(self) -> anyhow::Result<PodProvider>
101 where
102 L: ProviderLayer<RootProvider<PodNetwork>, PodNetwork>,
103 F: TxFiller<PodNetwork> + ProviderLayer<L::Provider, PodNetwork> + 'static,
104 L::Provider: 'static,
105 {
106 const PK_ENV: &str = "POD_PRIVATE_KEY";
107 fn load_private_key() -> anyhow::Result<crate::SigningKey> {
108 let pk_string = std::env::var(PK_ENV)?;
109 let pk_bytes = hex::decode(pk_string)?;
110 let pk = crate::SigningKey::from_slice(&pk_bytes)?;
111 Ok(pk)
112 }
113 let private_key = load_private_key()
114 .with_context(|| format!("{PK_ENV} env should contain hex-encoded ECDSA signer key"))?;
115
116 let rpc_url = std::env::var("POD_RPC_URL").unwrap_or("ws://127.0.0.1:8545".to_string());
117
118 let provider = self
119 .with_private_key(private_key)
120 .on_url(rpc_url.clone())
121 .await
122 .with_context(|| format!("attaching provider to URL {rpc_url}"))?;
123
124 Ok(provider)
125 }
126}
127
128pub struct PodProvider {
131 inner: Arc<dyn Provider<PodNetwork>>,
132}
133
134impl Clone for PodProvider {
135 fn clone(&self) -> Self {
136 Self {
137 inner: self.inner.clone(),
138 }
139 }
140}
141
142#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
143#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
144impl Provider<PodNetwork> for PodProvider {
145 fn root(&self) -> &RootProvider<PodNetwork> {
146 self.inner.root()
147 }
148
149 async fn send_transaction_internal(
154 &self,
155 tx: SendableTx<PodNetwork>,
156 ) -> TransportResult<PendingTransactionBuilder<PodNetwork>> {
157 self.inner.send_transaction_internal(tx).await
158 }
159}
160
161impl PodProvider {
162 pub fn new(provider: impl Provider<PodNetwork> + 'static) -> Self {
164 Self {
165 inner: Arc::new(provider),
166 }
167 }
168
169 pub async fn get_committee(&self) -> TransportResult<Committee> {
171 self.client().request_noparams("pod_getCommittee").await
172 }
173
174 pub async fn get_verifiable_logs(
175 &self,
176 filter: &LogFilter,
177 ) -> TransportResult<Vec<VerifiableLog>> {
178 self.client().request("eth_getLogs", (filter,)).await
179 }
180
181 pub async fn websocket_subscribe<Params, Resp>(
182 &self,
183 method: &str,
184 params: Params,
185 ) -> TransportResult<Subscription<Resp>>
186 where
187 Params: RpcSend,
188 Resp: RpcRecv,
189 {
190 let id = self
191 .client()
192 .request("eth_subscribe", (method, params))
193 .await?;
194 self.root().get_subscription(id).await
195 }
196
197 pub async fn subscribe_verifiable_logs(
198 &self,
199 filter: &LogFilter,
200 ) -> TransportResult<Subscription<VerifiableLog>> {
201 self.websocket_subscribe("logs", filter).await
202 }
203
204 pub async fn wait_past_perfect_time(&self, timestamp: Timestamp) -> TransportResult<()> {
205 loop {
206 let subscription: Subscription<String> = self
207 .websocket_subscribe("pod_pastPerfectTime", timestamp.as_micros())
208 .await?;
209 let first_notification = subscription.into_stream().next().await;
211 if first_notification.is_some() {
212 break;
213 }
214 }
215 Ok(())
216 }
217
218 pub async fn subscribe_receipts(
223 &self,
224 address: Option<Address>,
225 since: Timestamp,
226 ) -> TransportResult<
227 Subscription<MetadataWrappedItem<TransactionReceipt, RegularReceiptMetadata>>,
228 > {
229 self.websocket_subscribe("pod_receipts", (address, since))
230 .await
231 }
232
233 pub async fn get_receipts(
234 &self,
235 address: Option<Address>,
236 since_micros: u64,
237 paginator: Option<CursorPaginationRequest>,
238 ) -> TransportResult<ApiPaginatedResult<<PodNetwork as Network>::ReceiptResponse>> {
239 self.client()
240 .request("pod_listReceipts", &(address, since_micros, paginator))
241 .await
242 }
243
244 pub async fn transfer(
246 &self,
247 to: Address,
248 amount: U256,
249 ) -> Result<<PodNetwork as Network>::ReceiptResponse, Box<dyn std::error::Error>> {
250 let tx = PodTransactionRequest::default()
251 .with_to(to)
252 .with_value(amount);
253
254 let pending_tx = self.send_transaction(tx).await?;
255
256 let receipt = pending_tx.get_receipt().await?;
257
258 Ok(receipt)
259 }
260}