1use alloy_consensus::{TxEnvelope, TxType, TypedTransaction};
2use alloy_eips::eip2930::AccessList;
3use alloy_network::{
4 BuildResult, Network, NetworkWallet, ReceiptResponse, TransactionBuilder,
5 TransactionBuilderError,
6};
7use alloy_primitives::{
8 Address, BlockHash, Bytes, ChainId, Log, Signature, TxHash, TxKind, B256, U256,
9};
10use alloy_provider::fillers::{
11 ChainIdFiller, GasFiller, JoinFill, NonceFiller, RecommendedFillers,
12};
13
14use pod_types::{
15 consensus::{attestation::AttestedTx, committee::CommitteeError},
16 ledger::Transaction,
17 metadata::DetailedReceiptMetadata,
18};
19
20use alloy_rpc_types::{TransactionReceipt, TransactionRequest};
21use pod_types::{Committee, Hashable, Merkleizable, Receipt, Timestamp};
22use serde::{Deserialize, Serialize};
23use std::ops::{Deref, DerefMut};
24
25#[derive(Debug, Clone, Copy)]
26pub struct PodNetwork;
27
28#[derive(Debug, Serialize, Deserialize, Clone)]
29pub struct PodTransactionRequest {
30 #[serde(flatten)]
31 pub inner: TransactionRequest,
32}
33
34impl Default for PodTransactionRequest {
35 fn default() -> Self {
36 let mut inner = TransactionRequest::default();
37 inner.set_max_fee_per_gas(1_000_000_000);
38 inner.set_max_priority_fee_per_gas(1_000_000_000);
39 Self { inner }
40 }
41}
42
43impl Deref for PodTransactionRequest {
44 type Target = TransactionRequest;
45
46 fn deref(&self) -> &Self::Target {
47 &self.inner
48 }
49}
50
51impl DerefMut for PodTransactionRequest {
52 fn deref_mut(&mut self) -> &mut Self::Target {
53 &mut self.inner
54 }
55}
56
57impl From<TypedTransaction> for PodTransactionRequest {
58 fn from(value: TypedTransaction) -> Self {
59 Self {
60 inner: value.into(),
61 }
62 }
63}
64
65impl From<TxEnvelope> for PodTransactionRequest {
66 fn from(value: TxEnvelope) -> Self {
67 Self {
68 inner: value.into(),
69 }
70 }
71}
72
73impl TransactionBuilder<PodNetwork> for PodTransactionRequest {
74 fn chain_id(&self) -> Option<ChainId> {
75 self.chain_id
76 }
77
78 fn set_chain_id(&mut self, chain_id: ChainId) {
79 self.chain_id = Some(chain_id);
80 }
81
82 fn nonce(&self) -> Option<u64> {
83 self.nonce
84 }
85
86 fn set_nonce(&mut self, nonce: u64) {
87 self.nonce = Some(nonce);
88 }
89
90 fn take_nonce(&mut self) -> Option<u64> {
91 self.nonce.take()
92 }
93
94 fn input(&self) -> Option<&Bytes> {
95 self.input.input()
96 }
97
98 fn set_input<T: Into<Bytes>>(&mut self, input: T) {
99 self.input.input = Some(input.into());
100 }
101
102 fn from(&self) -> Option<Address> {
103 self.from
104 }
105
106 fn set_from(&mut self, from: Address) {
107 self.from = Some(from);
108 }
109
110 fn kind(&self) -> Option<TxKind> {
111 self.to
112 }
113
114 fn clear_kind(&mut self) {
115 self.to = None;
116 }
117
118 fn set_kind(&mut self, kind: TxKind) {
119 self.to = Some(kind);
120 }
121
122 fn value(&self) -> Option<U256> {
123 self.value
124 }
125
126 fn set_value(&mut self, value: U256) {
127 self.value = Some(value)
128 }
129
130 fn gas_price(&self) -> Option<u128> {
131 self.gas_price
132 }
133
134 fn set_gas_price(&mut self, gas_price: u128) {
135 self.gas_price = Some(gas_price);
136 }
137
138 fn max_fee_per_gas(&self) -> Option<u128> {
139 self.max_fee_per_gas
140 }
141
142 fn set_max_fee_per_gas(&mut self, max_fee_per_gas: u128) {
143 self.max_fee_per_gas = Some(max_fee_per_gas);
144 }
145
146 fn max_priority_fee_per_gas(&self) -> Option<u128> {
147 self.max_priority_fee_per_gas
148 }
149
150 fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: u128) {
151 self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas);
152 }
153
154 fn gas_limit(&self) -> Option<u64> {
155 self.gas
156 }
157
158 fn set_gas_limit(&mut self, gas_limit: u64) {
159 self.gas = Some(gas_limit);
160 }
161
162 fn access_list(&self) -> Option<&AccessList> {
163 self.access_list.as_ref()
164 }
165
166 fn set_access_list(&mut self, access_list: AccessList) {
167 self.access_list = Some(access_list);
168 }
169
170 fn complete_type(&self, ty: TxType) -> Result<(), Vec<&'static str>> {
171 match ty {
172 TxType::Eip1559 => self.complete_1559(),
173 _ => unimplemented!(), }
175 }
176
177 fn can_submit(&self) -> bool {
178 self.from.is_some()
182 }
183
184 fn can_build(&self) -> bool {
185 self.max_fee_per_gas.is_some() && self.max_priority_fee_per_gas.is_some()
187 }
188
189 #[doc(alias = "output_transaction_type")]
190 fn output_tx_type(&self) -> TxType {
191 TxType::Eip1559
192 }
193
194 #[doc(alias = "output_transaction_type_checked")]
195 fn output_tx_type_checked(&self) -> Option<TxType> {
196 self.buildable_type()
197 }
198
199 fn prep_for_submission(&mut self) {
200 self.transaction_type = Some(self.preferred_type() as u8);
201 self.trim_conflicting_keys();
202 self.populate_blob_hashes();
203 }
204
205 fn build_unsigned(self) -> BuildResult<TypedTransaction, PodNetwork> {
206 if let Err((tx_type, missing)) = self.missing_keys() {
207 return Err(
208 TransactionBuilderError::InvalidTransactionRequest(tx_type, missing)
209 .into_unbuilt(self),
210 );
211 }
212 Ok(self
213 .inner
214 .build_typed_tx()
215 .expect("checked by missing_keys"))
216 }
217
218 async fn build<W: NetworkWallet<PodNetwork>>(
219 self,
220 wallet: &W,
221 ) -> Result<<PodNetwork as Network>::TxEnvelope, TransactionBuilderError<PodNetwork>> {
222 Ok(wallet.sign_request(self).await?)
223 }
224}
225
226impl ReceiptResponse for PodReceiptResponse {
227 fn contract_address(&self) -> Option<Address> {
228 None
230 }
231
232 fn status(&self) -> bool {
233 self.receipt.status()
234 }
235
236 fn block_hash(&self) -> Option<BlockHash> {
237 Some(BlockHash::default())
239 }
240
241 fn block_number(&self) -> Option<u64> {
242 None
244 }
245
246 fn transaction_hash(&self) -> TxHash {
247 self.receipt.transaction_hash()
248 }
249
250 fn transaction_index(&self) -> Option<u64> {
251 None
253 }
254
255 fn gas_used(&self) -> u64 {
256 self.receipt.gas_used()
257 }
258
259 fn effective_gas_price(&self) -> u128 {
260 self.receipt.effective_gas_price()
261 }
262
263 fn blob_gas_used(&self) -> Option<u64> {
264 None
266 }
267
268 fn blob_gas_price(&self) -> Option<u128> {
269 None
271 }
272
273 fn from(&self) -> Address {
274 self.receipt.from()
275 }
276
277 fn to(&self) -> Option<Address> {
278 self.receipt.to()
279 }
280
281 fn cumulative_gas_used(&self) -> u64 {
282 self.receipt.cumulative_gas_used()
284 }
285
286 fn state_root(&self) -> Option<B256> {
287 None
289 }
290}
291
292#[derive(Debug, Serialize, Deserialize, Clone)]
293pub struct AttestationData {
294 pub public_key: Address,
295 pub signature: Signature,
296 pub timestamp: Timestamp,
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct PodReceiptResponse {
301 #[serde(flatten)]
302 pub receipt: TransactionReceipt,
303 pub pod_metadata: DetailedReceiptMetadata,
304}
305
306impl PodReceiptResponse {
307 pub fn verify_receipt(&self, committee: &Committee) -> Result<(), CommitteeError> {
308 let logs = self
309 .receipt
310 .inner
311 .logs()
312 .iter()
313 .map(|l| l.inner.clone())
314 .collect::<Vec<Log>>();
315
316 let logs_root = logs.to_merkle_tree().hash_custom();
317 let tx_hash = self.pod_metadata.transaction.hash_custom();
318 let to = match self.pod_metadata.transaction.to {
319 TxKind::Create => None,
320 TxKind::Call(address) => Some(address),
321 };
322
323 let receipt = Receipt {
324 status: self.status(),
325 actual_gas_used: self.receipt.gas_used,
326 logs,
327 logs_root,
328 attested_tx: AttestedTx {
329 tx_hash,
330 success: self.pod_metadata.tx_attestation_status,
331 committee_epoch: self.pod_metadata.committee_epoch,
332 },
333 max_fee_per_gas: self.pod_metadata.transaction.max_fee_per_gas,
334 signer: self.pod_metadata.transaction.signer,
335 to,
336 contract_address: self.receipt.contract_address,
337 };
338
339 committee.verify_aggregate_attestation(
340 receipt.attested_tx.hash_custom(),
341 &self
342 .pod_metadata
343 .attestations
344 .iter()
345 .map(|a| a.signature)
346 .collect(),
347 )?;
348
349 committee.verify_aggregate_attestation(
350 receipt.hash_custom(),
351 &self
352 .pod_metadata
353 .receipt_attestations
354 .iter()
355 .map(|a| a.signature)
356 .collect(),
357 )?;
358
359 Ok(())
360 }
361
362 pub fn transaction(&self) -> &pod_types::Signed<Transaction> {
363 &self.pod_metadata.transaction
364 }
365}
366
367impl Deref for PodReceiptResponse {
368 type Target = TransactionReceipt;
369 fn deref(&self) -> &TransactionReceipt {
370 &self.receipt
371 }
372}
373
374impl Network for PodNetwork {
375 type TxType = TxType;
376 type TxEnvelope = alloy_consensus::TxEnvelope;
377 type UnsignedTx = TypedTransaction;
378 type ReceiptEnvelope = alloy_consensus::ReceiptEnvelope;
379 type Header = alloy_consensus::Header;
380 type TransactionRequest = PodTransactionRequest;
381 type TransactionResponse = alloy_rpc_types::Transaction;
382 type ReceiptResponse = PodReceiptResponse;
383 type HeaderResponse = alloy_rpc_types::Header;
384 type BlockResponse = alloy_rpc_types::Block;
385}
386
387impl RecommendedFillers for PodNetwork {
388 type RecommendedFillers = JoinFill<GasFiller, JoinFill<NonceFiller, ChainIdFiller>>;
389
390 fn recommended_fillers() -> Self::RecommendedFillers {
391 JoinFill::new(
392 GasFiller,
393 JoinFill::new(NonceFiller::default(), ChainIdFiller::default()),
394 )
395 }
396}