foundry_primitives/transaction/
request.rs1use std::ops::{Deref, DerefMut};
2
3use alloy_consensus::constants::{
4 EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
5 LEGACY_TX_TYPE_ID,
6};
7use alloy_network::{BuildResult, NetworkWallet, TransactionBuilder, TransactionBuilderError};
8use alloy_primitives::{Address, ChainId, TxKind, U256};
9use alloy_rpc_types::{AccessList, TransactionInputKind, TransactionRequest};
10use alloy_serde::WithOtherFields;
11use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, OpTypedTransaction};
12use op_alloy_rpc_types::OpTransactionRequest;
13use serde::{Deserialize, Serialize};
14
15use super::{FoundryTxEnvelope, FoundryTxType, FoundryTypedTx};
16use crate::FoundryNetwork;
17
18#[derive(Clone, Debug, Default, PartialEq, Eq)]
23pub struct FoundryTransactionRequest(OpTransactionRequest);
24
25impl Deref for FoundryTransactionRequest {
26 type Target = OpTransactionRequest;
27
28 fn deref(&self) -> &Self::Target {
29 &self.0
30 }
31}
32
33impl DerefMut for FoundryTransactionRequest {
34 fn deref_mut(&mut self) -> &mut Self::Target {
35 &mut self.0
36 }
37}
38
39impl Serialize for FoundryTransactionRequest {
40 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
41 where
42 S: serde::Serializer,
43 {
44 self.0.serialize(serializer)
45 }
46}
47
48impl<'de> Deserialize<'de> for FoundryTransactionRequest {
49 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
50 where
51 D: serde::Deserializer<'de>,
52 {
53 OpTransactionRequest::deserialize(deserializer).map(Self)
54 }
55}
56
57impl From<OpTransactionRequest> for FoundryTransactionRequest {
58 fn from(req: OpTransactionRequest) -> Self {
59 Self(req)
60 }
61}
62
63impl From<FoundryTransactionRequest> for OpTransactionRequest {
64 fn from(req: FoundryTransactionRequest) -> Self {
65 req.0
66 }
67}
68
69impl From<TransactionRequest> for FoundryTransactionRequest {
70 fn from(req: TransactionRequest) -> Self {
71 Self(req.into())
72 }
73}
74
75impl From<FoundryTransactionRequest> for TransactionRequest {
76 fn from(req: FoundryTransactionRequest) -> Self {
77 req.0.into()
78 }
79}
80
81impl From<WithOtherFields<TransactionRequest>> for FoundryTransactionRequest {
82 fn from(req: WithOtherFields<TransactionRequest>) -> Self {
83 Self(req.inner.into())
84 }
85}
86
87impl FoundryTransactionRequest {
88 #[inline]
90 pub fn new() -> Self {
91 Self::default()
92 }
93
94 #[inline]
96 pub fn is_deposit(&self) -> bool {
97 self.as_ref().transaction_type == Some(0x7E)
98 }
99
100 #[inline]
102 pub fn set_deposit_type(&mut self) {
103 self.as_mut().transaction_type = Some(0x7E);
104 }
105
106 #[inline]
108 pub fn as_deposit(mut self) -> Self {
109 self.set_deposit_type();
110 self
111 }
112
113 pub fn build_typed_tx(self) -> Result<FoundryTypedTx, Self> {
118 let op_typed = self.0.build_typed_tx().map_err(Self)?;
120
121 Ok(match op_typed {
123 OpTypedTransaction::Legacy(tx) => FoundryTypedTx::Legacy(tx),
124 OpTypedTransaction::Eip2930(tx) => FoundryTypedTx::Eip2930(tx),
125 OpTypedTransaction::Eip1559(tx) => FoundryTypedTx::Eip1559(tx),
126 OpTypedTransaction::Eip7702(tx) => FoundryTypedTx::Eip7702(tx),
127 OpTypedTransaction::Deposit(tx) => FoundryTypedTx::Deposit(tx),
128 })
129 }
130}
131
132impl From<FoundryTypedTx> for FoundryTransactionRequest {
133 fn from(tx: FoundryTypedTx) -> Self {
134 match tx {
135 FoundryTypedTx::Legacy(tx) => Self(Into::<TransactionRequest>::into(tx).into()),
136 FoundryTypedTx::Eip2930(tx) => Self(Into::<TransactionRequest>::into(tx).into()),
137 FoundryTypedTx::Eip1559(tx) => Self(Into::<TransactionRequest>::into(tx).into()),
138 FoundryTypedTx::Eip4844(tx) => Self(Into::<TransactionRequest>::into(tx).into()),
139 FoundryTypedTx::Eip7702(tx) => Self(Into::<TransactionRequest>::into(tx).into()),
140 FoundryTypedTx::Deposit(tx) => Self(tx.into()),
141 }
142 }
143}
144
145impl From<FoundryTxEnvelope> for FoundryTransactionRequest {
146 fn from(tx: FoundryTxEnvelope) -> Self {
147 FoundryTypedTx::from(tx).into()
148 }
149}
150
151impl TransactionBuilder<FoundryNetwork> for FoundryTransactionRequest {
153 fn chain_id(&self) -> Option<ChainId> {
154 self.as_ref().chain_id
155 }
156
157 fn set_chain_id(&mut self, chain_id: ChainId) {
158 self.as_mut().chain_id = Some(chain_id);
159 }
160
161 fn nonce(&self) -> Option<u64> {
162 self.as_ref().nonce
163 }
164
165 fn set_nonce(&mut self, nonce: u64) {
166 self.as_mut().nonce = Some(nonce);
167 }
168
169 fn take_nonce(&mut self) -> Option<u64> {
170 self.as_mut().nonce.take()
171 }
172
173 fn input(&self) -> Option<&alloy_primitives::Bytes> {
174 self.as_ref().input.input()
175 }
176
177 fn set_input<T: Into<alloy_primitives::Bytes>>(&mut self, input: T) {
178 self.as_mut().input.input = Some(input.into());
179 }
180
181 fn set_input_kind<T: Into<alloy_primitives::Bytes>>(
182 &mut self,
183 input: T,
184 kind: TransactionInputKind,
185 ) {
186 let inner = self.as_mut();
187 match kind {
188 TransactionInputKind::Input => inner.input.input = Some(input.into()),
189 TransactionInputKind::Data => inner.input.data = Some(input.into()),
190 TransactionInputKind::Both => {
191 let bytes = input.into();
192 inner.input.input = Some(bytes.clone());
193 inner.input.data = Some(bytes);
194 }
195 }
196 }
197
198 fn from(&self) -> Option<Address> {
199 self.as_ref().from
200 }
201
202 fn set_from(&mut self, from: Address) {
203 self.as_mut().from = Some(from);
204 }
205
206 fn kind(&self) -> Option<TxKind> {
207 self.as_ref().to
208 }
209
210 fn clear_kind(&mut self) {
211 self.as_mut().to = None;
212 }
213
214 fn set_kind(&mut self, kind: TxKind) {
215 self.as_mut().to = Some(kind);
216 }
217
218 fn value(&self) -> Option<U256> {
219 self.as_ref().value
220 }
221
222 fn set_value(&mut self, value: U256) {
223 self.as_mut().value = Some(value);
224 }
225
226 fn gas_price(&self) -> Option<u128> {
227 self.as_ref().gas_price
228 }
229
230 fn set_gas_price(&mut self, gas_price: u128) {
231 self.as_mut().gas_price = Some(gas_price);
232 }
233
234 fn max_fee_per_gas(&self) -> Option<u128> {
235 self.as_ref().max_fee_per_gas
236 }
237
238 fn set_max_fee_per_gas(&mut self, max_fee_per_gas: u128) {
239 self.as_mut().max_fee_per_gas = Some(max_fee_per_gas);
240 }
241
242 fn max_priority_fee_per_gas(&self) -> Option<u128> {
243 self.as_ref().max_priority_fee_per_gas
244 }
245
246 fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: u128) {
247 self.as_mut().max_priority_fee_per_gas = Some(max_priority_fee_per_gas);
248 }
249
250 fn gas_limit(&self) -> Option<u64> {
251 self.as_ref().gas
252 }
253
254 fn set_gas_limit(&mut self, gas_limit: u64) {
255 self.as_mut().gas = Some(gas_limit);
256 }
257
258 fn access_list(&self) -> Option<&AccessList> {
259 self.as_ref().access_list.as_ref()
260 }
261
262 fn set_access_list(&mut self, access_list: AccessList) {
263 self.as_mut().access_list = Some(access_list);
264 }
265
266 fn complete_type(&self, ty: FoundryTxType) -> Result<(), Vec<&'static str>> {
267 match ty {
268 FoundryTxType::Legacy => {
269 let mut missing = Vec::new();
270 if self.from().is_none() {
271 missing.push("from");
272 }
273 if self.gas_limit().is_none() {
274 missing.push("gas");
275 }
276 if self.gas_price().is_none() {
277 missing.push("gasPrice");
278 }
279 if missing.is_empty() { Ok(()) } else { Err(missing) }
280 }
281 FoundryTxType::Eip2930 => {
282 let mut missing = Vec::new();
283 if self.from().is_none() {
284 missing.push("from");
285 }
286 if self.gas_limit().is_none() {
287 missing.push("gas");
288 }
289 if self.gas_price().is_none() {
290 missing.push("gasPrice");
291 }
292 if self.access_list().is_none() {
293 missing.push("accessList");
294 }
295 if missing.is_empty() { Ok(()) } else { Err(missing) }
296 }
297 FoundryTxType::Eip1559 => {
298 let mut missing = Vec::new();
299 if self.from().is_none() {
300 missing.push("from");
301 }
302 if self.gas_limit().is_none() {
303 missing.push("gas");
304 }
305 if self.max_fee_per_gas().is_none() {
306 missing.push("maxFeePerGas");
307 }
308 if self.max_priority_fee_per_gas().is_none() {
309 missing.push("maxPriorityFeePerGas");
310 }
311 if missing.is_empty() { Ok(()) } else { Err(missing) }
312 }
313 FoundryTxType::Eip4844 => {
314 let mut missing = Vec::new();
315 if self.from().is_none() {
316 missing.push("from");
317 }
318 if self.kind().is_none() {
319 missing.push("to");
320 }
321 if self.gas_limit().is_none() {
322 missing.push("gas");
323 }
324 if self.max_fee_per_gas().is_none() {
325 missing.push("maxFeePerGas");
326 }
327 if self.max_priority_fee_per_gas().is_none() {
328 missing.push("maxPriorityFeePerGas");
329 }
330 if self.as_ref().sidecar.is_none() {
331 missing.push("blobVersionedHashes or sidecar");
332 }
333 if missing.is_empty() { Ok(()) } else { Err(missing) }
334 }
335 FoundryTxType::Eip7702 => {
336 let mut missing = Vec::new();
337 if self.from().is_none() {
338 missing.push("from");
339 }
340 if self.kind().is_none() {
341 missing.push("to");
342 }
343 if self.gas_limit().is_none() {
344 missing.push("gas");
345 }
346 if self.max_fee_per_gas().is_none() {
347 missing.push("maxFeePerGas");
348 }
349 if self.max_priority_fee_per_gas().is_none() {
350 missing.push("maxPriorityFeePerGas");
351 }
352 if self.as_ref().authorization_list.is_none() {
353 missing.push("authorizationList");
354 }
355 if missing.is_empty() { Ok(()) } else { Err(missing) }
356 }
357 FoundryTxType::Deposit => {
358 let mut missing = Vec::new();
359 if self.from().is_none() {
360 missing.push("from");
361 }
362 if self.kind().is_none() {
363 missing.push("to");
364 }
365 if missing.is_empty() { Ok(()) } else { Err(missing) }
366 }
367 }
368 }
369
370 fn can_submit(&self) -> bool {
371 self.from().is_some()
372 }
373
374 fn can_build(&self) -> bool {
375 let inner = self.as_ref();
376 let common = inner.gas.is_some() && inner.nonce.is_some();
377
378 let legacy = inner.gas_price.is_some();
379 let eip2930 = legacy && inner.access_list.is_some();
380 let eip1559 = inner.max_fee_per_gas.is_some() && inner.max_priority_fee_per_gas.is_some();
381 let eip4844 = eip1559 && inner.sidecar.is_some() && inner.to.is_some();
382 let eip7702 = eip1559 && inner.authorization_list.is_some();
383
384 let deposit =
385 inner.transaction_type == Some(0x7E) && inner.from.is_some() && inner.to.is_some();
386
387 (common && (legacy || eip2930 || eip1559 || eip4844 || eip7702)) || deposit
388 }
389
390 fn output_tx_type(&self) -> FoundryTxType {
391 if self.as_ref().transaction_type == Some(DEPOSIT_TX_TYPE_ID) {
392 return FoundryTxType::Deposit;
393 }
394
395 if self.as_ref().transaction_type.is_some() {
397 match self.as_ref().transaction_type.unwrap() {
398 LEGACY_TX_TYPE_ID => FoundryTxType::Legacy,
399 EIP2930_TX_TYPE_ID => FoundryTxType::Eip2930,
400 EIP1559_TX_TYPE_ID => FoundryTxType::Eip1559,
401 EIP4844_TX_TYPE_ID => FoundryTxType::Eip4844,
402 EIP7702_TX_TYPE_ID => FoundryTxType::Eip7702,
403 _ => FoundryTxType::Eip1559,
404 }
405 } else if self.max_fee_per_gas().is_some() {
406 FoundryTxType::Eip1559
407 } else if self.gas_price().is_some() {
408 FoundryTxType::Legacy
409 } else {
410 FoundryTxType::Eip1559
411 }
412 }
413
414 fn output_tx_type_checked(&self) -> Option<FoundryTxType> {
415 if self.can_build() { Some(self.output_tx_type()) } else { None }
416 }
417
418 fn prep_for_submission(&mut self) {
419 if self.as_ref().transaction_type.is_none() {
421 self.as_mut().transaction_type = Some(match self.output_tx_type() {
422 FoundryTxType::Legacy => LEGACY_TX_TYPE_ID,
423 FoundryTxType::Eip2930 => EIP2930_TX_TYPE_ID,
424 FoundryTxType::Eip1559 => EIP1559_TX_TYPE_ID,
425 FoundryTxType::Eip4844 => EIP4844_TX_TYPE_ID,
426 FoundryTxType::Eip7702 => EIP7702_TX_TYPE_ID,
427 FoundryTxType::Deposit => DEPOSIT_TX_TYPE_ID,
428 });
429 }
430 }
431
432 fn build_unsigned(self) -> BuildResult<FoundryTypedTx, FoundryNetwork> {
433 match self.build_typed_tx() {
435 Ok(tx) => Ok(tx),
436 Err(err_self) => {
437 let tx_type = err_self.output_tx_type();
439 let mut missing = Vec::new();
440 if let Err(m) = err_self.complete_type(tx_type) {
441 missing.extend(m);
442 }
443 Err(TransactionBuilderError::InvalidTransactionRequest(tx_type, missing)
445 .into_unbuilt(err_self))
446 }
447 }
448 }
449
450 async fn build<W: NetworkWallet<FoundryNetwork>>(
451 self,
452 wallet: &W,
453 ) -> Result<FoundryTxEnvelope, TransactionBuilderError<FoundryNetwork>> {
454 Ok(wallet.sign_request(self).await?)
455 }
456}