tempo_alloy/rpc/
compat.rs

1use crate::rpc::{TempoHeaderResponse, TempoTransactionRequest};
2use alloy_consensus::{EthereumTxEnvelope, TxEip4844, error::ValueError};
3use alloy_network::{TransactionBuilder, TxSigner};
4use alloy_primitives::{B256, Bytes, Signature};
5use reth_evm::EvmEnv;
6use reth_primitives_traits::SealedHeader;
7use reth_rpc_convert::{
8    SignTxRequestError, SignableTxRequest, TryIntoSimTx, TryIntoTxEnv,
9    transaction::FromConsensusHeader,
10};
11use reth_rpc_eth_types::EthApiError;
12use tempo_evm::TempoBlockEnv;
13use tempo_primitives::{
14    SignatureType, TempoHeader, TempoSignature, TempoTxEnvelope, TempoTxType,
15    transaction::{Call, RecoveredTempoAuthorization},
16};
17use tempo_revm::{TempoBatchCallEnv, TempoTxEnv};
18
19impl TryIntoSimTx<TempoTxEnvelope> for TempoTransactionRequest {
20    fn try_into_sim_tx(self) -> Result<TempoTxEnvelope, ValueError<Self>> {
21        match self.output_tx_type() {
22            TempoTxType::AA => {
23                let tx = self.build_aa()?;
24
25                // Create an empty signature for the transaction.
26                let signature = TempoSignature::default();
27
28                Ok(tx.into_signed(signature).into())
29            }
30            TempoTxType::FeeToken => {
31                let tx = self.build_fee_token()?;
32
33                // Create an empty signature for the transaction.
34                let signature = Signature::new(Default::default(), Default::default(), false);
35
36                Ok(tx.into_signed(signature).into())
37            }
38            TempoTxType::Legacy
39            | TempoTxType::Eip2930
40            | TempoTxType::Eip1559
41            | TempoTxType::Eip7702 => {
42                let Self {
43                    inner,
44                    fee_token,
45                    nonce_key,
46                    calls,
47                    key_type,
48                    key_data,
49                    tempo_authorization_list,
50                } = self;
51                let envelope = match TryIntoSimTx::<EthereumTxEnvelope<TxEip4844>>::try_into_sim_tx(
52                    inner.clone(),
53                ) {
54                    Ok(inner) => inner,
55                    Err(e) => {
56                        return Err(e.map(|inner| Self {
57                            inner,
58                            fee_token,
59                            nonce_key,
60                            calls,
61                            key_type,
62                            key_data,
63                            tempo_authorization_list,
64                        }));
65                    }
66                };
67
68                Ok(envelope.try_into().map_err(
69                    |e: ValueError<EthereumTxEnvelope<TxEip4844>>| {
70                        e.map(|_inner| Self {
71                            inner,
72                            fee_token,
73                            nonce_key,
74                            calls,
75                            key_type,
76                            key_data,
77                            tempo_authorization_list,
78                        })
79                    },
80                )?)
81            }
82        }
83    }
84}
85
86impl TryIntoTxEnv<TempoTxEnv, TempoBlockEnv> for TempoTransactionRequest {
87    type Err = EthApiError;
88
89    fn try_into_tx_env<Spec>(
90        self,
91        evm_env: &EvmEnv<Spec, TempoBlockEnv>,
92    ) -> Result<TempoTxEnv, Self::Err> {
93        let Self {
94            inner,
95            fee_token,
96            calls,
97            key_type,
98            key_data,
99            tempo_authorization_list,
100            nonce_key,
101        } = self;
102        Ok(TempoTxEnv {
103            fee_token,
104            is_system_tx: false,
105            fee_payer: None,
106            tempo_tx_env: if !calls.is_empty()
107                || !tempo_authorization_list.is_empty()
108                || nonce_key.is_some()
109            {
110                // Create mock signature for gas estimation
111                // If key_type is not provided, default to secp256k1
112                let mock_signature = key_type
113                    .as_ref()
114                    .map(|kt| create_mock_tempo_signature(kt, key_data.as_ref()))
115                    .unwrap_or_else(|| {
116                        create_mock_tempo_signature(&SignatureType::Secp256k1, None)
117                    });
118
119                let calls = if !calls.is_empty() {
120                    calls
121                } else if let Some(to) = &inner.to {
122                    vec![Call {
123                        to: *to,
124                        value: inner.value.unwrap_or_default(),
125                        input: inner.input.clone().into_input().unwrap_or_default(),
126                    }]
127                } else {
128                    return Err(EthApiError::InvalidParams("empty calls list".to_string()));
129                };
130
131                Some(Box::new(TempoBatchCallEnv {
132                    aa_calls: calls,
133                    signature: mock_signature,
134                    tempo_authorization_list: tempo_authorization_list
135                        .into_iter()
136                        .map(RecoveredTempoAuthorization::new)
137                        .collect(),
138                    nonce_key: nonce_key.unwrap_or_default(),
139                    key_authorization: None,
140                    signature_hash: B256::ZERO,
141                    valid_before: None,
142                    valid_after: None,
143                    subblock_transaction: false,
144                }))
145            } else {
146                None
147            },
148            inner: inner.try_into_tx_env(evm_env)?,
149        })
150    }
151}
152
153/// Creates a mock AA signature for gas estimation based on key type hints
154fn create_mock_tempo_signature(
155    key_type: &SignatureType,
156    key_data: Option<&Bytes>,
157) -> TempoSignature {
158    use tempo_primitives::transaction::tt_signature::{
159        P256SignatureWithPreHash, PrimitiveSignature, TempoSignature, WebAuthnSignature,
160    };
161
162    match key_type {
163        SignatureType::Secp256k1 => {
164            // Create a dummy secp256k1 signature (65 bytes)
165            TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::new(
166                alloy_primitives::U256::ZERO,
167                alloy_primitives::U256::ZERO,
168                false,
169            )))
170        }
171        SignatureType::P256 => {
172            // Create a dummy P256 signature
173            TempoSignature::Primitive(PrimitiveSignature::P256(P256SignatureWithPreHash {
174                r: alloy_primitives::B256::ZERO,
175                s: alloy_primitives::B256::ZERO,
176                pub_key_x: alloy_primitives::B256::ZERO,
177                pub_key_y: alloy_primitives::B256::ZERO,
178                pre_hash: false,
179            }))
180        }
181        SignatureType::WebAuthn => {
182            // Create a dummy WebAuthn signature with the specified size
183            // key_data contains the total size of webauthn_data (excluding 128 bytes for public keys)
184            // Default: 200 bytes if no key_data provided
185
186            // Base clientDataJSON template (50 bytes): {"type":"webauthn.get","challenge":"","origin":""}
187            // Authenticator data (37 bytes): 32 rpIdHash + 1 flags + 4 signCount
188            // Minimum total: 87 bytes
189            const BASE_CLIENT_JSON: &str = r#"{"type":"webauthn.get","challenge":"","origin":""}"#;
190            const AUTH_DATA_SIZE: usize = 37;
191            const MIN_WEBAUTHN_SIZE: usize = AUTH_DATA_SIZE + BASE_CLIENT_JSON.len(); // 87 bytes
192            const DEFAULT_WEBAUTHN_SIZE: usize = 800; // Default when no key_data provided
193
194            // Parse size from key_data, or use default
195            let size = if let Some(data) = key_data {
196                match data.len() {
197                    1 => data[0] as usize,
198                    2 => u16::from_be_bytes([data[0], data[1]]) as usize,
199                    4 => u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize,
200                    _ => DEFAULT_WEBAUTHN_SIZE, // Fallback default
201                }
202            } else {
203                DEFAULT_WEBAUTHN_SIZE // Default size when no key_data provided
204            };
205
206            // Ensure size is at least minimum
207            let size = size.max(MIN_WEBAUTHN_SIZE);
208
209            // Construct authenticatorData (37 bytes)
210            let mut webauthn_data = vec![0u8; AUTH_DATA_SIZE];
211            webauthn_data[32] = 0x01; // UP flag set
212
213            // Construct clientDataJSON with padding in origin field if needed
214            let additional_bytes = size - MIN_WEBAUTHN_SIZE;
215            let client_json = if additional_bytes > 0 {
216                // Add padding bytes to origin field
217                // {"type":"webauthn.get","challenge":"","origin":"XXXXX"}
218                let padding = "x".repeat(additional_bytes);
219                format!(r#"{{"type":"webauthn.get","challenge":"","origin":"{padding}"}}"#,)
220            } else {
221                BASE_CLIENT_JSON.to_string()
222            };
223
224            webauthn_data.extend_from_slice(client_json.as_bytes());
225            let webauthn_data = Bytes::from(webauthn_data);
226
227            TempoSignature::Primitive(PrimitiveSignature::WebAuthn(WebAuthnSignature {
228                webauthn_data,
229                r: alloy_primitives::B256::ZERO,
230                s: alloy_primitives::B256::ZERO,
231                pub_key_x: alloy_primitives::B256::ZERO,
232                pub_key_y: alloy_primitives::B256::ZERO,
233            }))
234        }
235    }
236}
237
238impl SignableTxRequest<TempoTxEnvelope> for TempoTransactionRequest {
239    async fn try_build_and_sign(
240        self,
241        signer: impl TxSigner<Signature> + Send,
242    ) -> Result<TempoTxEnvelope, SignTxRequestError> {
243        SignableTxRequest::<TempoTxEnvelope>::try_build_and_sign(self.inner, signer).await
244    }
245}
246
247impl FromConsensusHeader<TempoHeader> for TempoHeaderResponse {
248    fn from_consensus_header(header: SealedHeader<TempoHeader>, block_size: usize) -> Self {
249        Self {
250            timestamp_millis: header.timestamp_millis(),
251            inner: FromConsensusHeader::from_consensus_header(header, block_size),
252        }
253    }
254}