1pub mod admin;
2pub mod amm;
3pub mod dex;
4pub mod error;
5pub mod eth_ext;
6pub mod policy;
7pub mod token;
8
9pub use admin::{TempoAdminApi, TempoAdminApiServer};
10use alloy_primitives::{Address, B256};
11use alloy_rpc_types_eth::{Log, ReceiptWithBloom};
12pub use amm::{TempoAmm, TempoAmmApiServer};
13pub use dex::{TempoDex, api::TempoDexApiServer};
14pub use eth_ext::{TempoEthExt, TempoEthExtApiServer};
15use futures::{TryFutureExt, future::Either};
16pub use policy::{TempoPolicy, TempoPolicyApiServer};
17use reth_errors::RethError;
18use reth_primitives_traits::{Recovered, TransactionMeta, WithEncoded, transaction::TxHashRef};
19use reth_transaction_pool::PoolPooledTx;
20use std::sync::Arc;
21pub use tempo_alloy::rpc::TempoTransactionRequest;
22use tempo_chainspec::{TempoChainSpec, hardfork::TempoHardfork};
23use tempo_evm::TempoStateAccess;
24use tempo_precompiles::{NONCE_PRECOMPILE_ADDRESS, nonce::NonceManager};
25pub use token::{TempoToken, TempoTokenApiServer};
26
27use crate::{node::TempoNode, rpc::error::TempoEthApiError};
28use alloy::{
29 consensus::TxReceipt,
30 primitives::{U256, uint},
31};
32use reth_ethereum::tasks::{
33 TaskSpawner,
34 pool::{BlockingTaskGuard, BlockingTaskPool},
35};
36use reth_evm::{
37 EvmEnvFor, TxEnvFor,
38 revm::{Database, context::result::EVMError},
39};
40use reth_node_api::{FullNodeComponents, FullNodeTypes, HeaderTy, PrimitivesTy};
41use reth_node_builder::{
42 NodeAdapter,
43 rpc::{EthApiBuilder, EthApiCtx},
44};
45use reth_provider::{ChainSpecProvider, ProviderError};
46use reth_rpc::{DynRpcConverter, eth::EthApi};
47use reth_rpc_eth_api::{
48 EthApiTypes, RpcConverter, RpcNodeCore, RpcNodeCoreExt,
49 helpers::{
50 Call, EthApiSpec, EthBlocks, EthCall, EthFees, EthState, EthTransactions, LoadBlock,
51 LoadFee, LoadPendingBlock, LoadReceipt, LoadState, LoadTransaction, SpawnBlocking, Trace,
52 estimate::EstimateCall, pending_block::PendingEnvBuilder, spec::SignersForRpc,
53 },
54 transaction::{ConvertReceiptInput, ReceiptConverter},
55};
56use reth_rpc_eth_types::{
57 EthApiError, EthStateCache, FeeHistoryCache, GasPriceOracle, PendingBlock,
58 builder::config::PendingBlockKind, receipt::EthReceiptConverter,
59};
60use tempo_alloy::{TempoNetwork, rpc::TempoTransactionReceipt};
61use tempo_evm::TempoEvmConfig;
62use tempo_primitives::{
63 TEMPO_GAS_PRICE_SCALING_FACTOR, TempoPrimitives, TempoReceipt, TempoTxEnvelope,
64};
65use tokio::sync::{Mutex, broadcast};
66
67pub const NATIVE_BALANCE_PLACEHOLDER: U256 =
70 uint!(4242424242424242424242424242424242424242424242424242424242424242424242424242_U256);
71
72#[derive(Clone)]
83pub struct TempoEthApi<N: FullNodeTypes<Types = TempoNode>> {
84 inner: EthApi<NodeAdapter<N>, DynRpcConverter<TempoEvmConfig, TempoNetwork>>,
86
87 subblock_transactions_tx: broadcast::Sender<Recovered<TempoTxEnvelope>>,
89}
90
91impl<N: FullNodeTypes<Types = TempoNode>> TempoEthApi<N> {
92 pub fn new(
94 eth_api: EthApi<NodeAdapter<N>, DynRpcConverter<TempoEvmConfig, TempoNetwork>>,
95 ) -> Self {
96 Self {
97 inner: eth_api,
98 subblock_transactions_tx: broadcast::channel(100).0,
99 }
100 }
101
102 pub fn subblock_transactions_rx(&self) -> broadcast::Receiver<Recovered<TempoTxEnvelope>> {
104 self.subblock_transactions_tx.subscribe()
105 }
106}
107
108impl<N: FullNodeTypes<Types = TempoNode>> EthApiTypes for TempoEthApi<N> {
109 type Error = TempoEthApiError;
110 type NetworkTypes = TempoNetwork;
111 type RpcConvert = DynRpcConverter<TempoEvmConfig, TempoNetwork>;
112
113 fn converter(&self) -> &Self::RpcConvert {
114 self.inner.converter()
115 }
116}
117
118impl<N: FullNodeTypes<Types = TempoNode>> RpcNodeCore for TempoEthApi<N> {
119 type Primitives = PrimitivesTy<N::Types>;
120 type Provider = N::Provider;
121 type Pool = <NodeAdapter<N> as FullNodeComponents>::Pool;
122 type Evm = <NodeAdapter<N> as FullNodeComponents>::Evm;
123 type Network = <NodeAdapter<N> as FullNodeComponents>::Network;
124
125 #[inline]
126 fn pool(&self) -> &Self::Pool {
127 self.inner.pool()
128 }
129
130 #[inline]
131 fn evm_config(&self) -> &Self::Evm {
132 self.inner.evm_config()
133 }
134
135 #[inline]
136 fn network(&self) -> &Self::Network {
137 self.inner.network()
138 }
139
140 #[inline]
141 fn provider(&self) -> &Self::Provider {
142 self.inner.provider()
143 }
144}
145
146impl<N: FullNodeTypes<Types = TempoNode>> RpcNodeCoreExt for TempoEthApi<N> {
147 #[inline]
148 fn cache(&self) -> &EthStateCache<PrimitivesTy<N::Types>> {
149 self.inner.cache()
150 }
151}
152
153impl<N: FullNodeTypes<Types = TempoNode>> EthApiSpec for TempoEthApi<N> {
154 #[inline]
155 fn starting_block(&self) -> U256 {
156 self.inner.starting_block()
157 }
158}
159
160impl<N: FullNodeTypes<Types = TempoNode>> SpawnBlocking for TempoEthApi<N> {
161 #[inline]
162 fn io_task_spawner(&self) -> impl TaskSpawner {
163 self.inner.task_spawner()
164 }
165
166 #[inline]
167 fn tracing_task_pool(&self) -> &BlockingTaskPool {
168 self.inner.blocking_task_pool()
169 }
170
171 #[inline]
172 fn tracing_task_guard(&self) -> &BlockingTaskGuard {
173 self.inner.blocking_task_guard()
174 }
175
176 #[inline]
177 fn blocking_io_task_guard(&self) -> &Arc<tokio::sync::Semaphore> {
178 self.inner.blocking_io_task_guard()
179 }
180}
181
182impl<N: FullNodeTypes<Types = TempoNode>> LoadPendingBlock for TempoEthApi<N> {
183 #[inline]
184 fn pending_block(&self) -> &Mutex<Option<PendingBlock<Self::Primitives>>> {
185 self.inner.pending_block()
186 }
187
188 #[inline]
189 fn pending_env_builder(&self) -> &dyn PendingEnvBuilder<Self::Evm> {
190 self.inner.pending_env_builder()
191 }
192
193 #[inline]
194 fn pending_block_kind(&self) -> PendingBlockKind {
195 self.inner.pending_block_kind()
196 }
197}
198
199impl<N: FullNodeTypes<Types = TempoNode>> LoadFee for TempoEthApi<N> {
200 #[inline]
201 fn gas_oracle(&self) -> &GasPriceOracle<Self::Provider> {
202 self.inner.gas_oracle()
203 }
204
205 #[inline]
206 fn fee_history_cache(&self) -> &FeeHistoryCache<HeaderTy<N::Types>> {
207 self.inner.fee_history_cache()
208 }
209}
210
211impl<N: FullNodeTypes<Types = TempoNode>> LoadState for TempoEthApi<N> {}
212
213impl<N: FullNodeTypes<Types = TempoNode>> EthState for TempoEthApi<N> {
214 #[inline]
215 async fn balance(
216 &self,
217 _address: alloy_primitives::Address,
218 _block_id: Option<alloy_eips::BlockId>,
219 ) -> Result<U256, Self::Error> {
220 Ok(NATIVE_BALANCE_PLACEHOLDER)
221 }
222
223 #[inline]
224 fn max_proof_window(&self) -> u64 {
225 self.inner.eth_proof_window()
226 }
227}
228
229impl<N: FullNodeTypes<Types = TempoNode>> EthFees for TempoEthApi<N> {}
230
231impl<N: FullNodeTypes<Types = TempoNode>> Trace for TempoEthApi<N> {}
232
233impl<N: FullNodeTypes<Types = TempoNode>> EthCall for TempoEthApi<N> {}
234
235impl<N: FullNodeTypes<Types = TempoNode>> Call for TempoEthApi<N> {
236 #[inline]
237 fn call_gas_limit(&self) -> u64 {
238 self.inner.gas_cap()
239 }
240
241 #[inline]
242 fn max_simulate_blocks(&self) -> u64 {
243 self.inner.max_simulate_blocks()
244 }
245
246 #[inline]
247 fn evm_memory_limit(&self) -> u64 {
248 self.inner.evm_memory_limit()
249 }
250
251 fn caller_gas_allowance(
253 &self,
254 mut db: impl Database<Error: Into<EthApiError>>,
255 _evm_env: &EvmEnvFor<Self::Evm>,
256 tx_env: &TxEnvFor<Self::Evm>,
257 ) -> Result<u64, Self::Error> {
258 let fee_payer = tx_env
259 .fee_payer()
260 .map_err(EVMError::<ProviderError, _>::from)?;
261 let fee_token = db
262 .get_fee_token(tx_env, Address::ZERO, fee_payer, TempoHardfork::default())
263 .map_err(Into::into)?;
264 let fee_token_balance = db
265 .get_token_balance(fee_token, fee_payer)
266 .map_err(Into::into)?;
267
268 Ok(fee_token_balance
269 .saturating_mul(TEMPO_GAS_PRICE_SCALING_FACTOR)
271 .checked_div(U256::from(tx_env.inner.gas_price))
273 .unwrap_or_default()
275 .saturating_to())
276 }
277
278 fn create_txn_env(
279 &self,
280 evm_env: &EvmEnvFor<Self::Evm>,
281 mut request: TempoTransactionRequest,
282 mut db: impl Database<Error: Into<EthApiError>>,
283 ) -> Result<TxEnvFor<Self::Evm>, Self::Error> {
284 if let Some(nonce_key) = request.nonce_key
286 && request.nonce.is_none()
287 && !nonce_key.is_zero()
288 {
289 let slot = NonceManager::new()
290 .nonces
291 .at(request.from.unwrap_or_default())
292 .at(nonce_key)
293 .slot();
294 request.nonce = Some(
295 db.storage(NONCE_PRECOMPILE_ADDRESS, slot)
296 .map_err(Into::into)?
297 .saturating_to(),
298 );
299 }
300
301 Ok(self.inner.create_txn_env(evm_env, request, db)?)
302 }
303}
304
305impl<N: FullNodeTypes<Types = TempoNode>> EstimateCall for TempoEthApi<N> {}
306impl<N: FullNodeTypes<Types = TempoNode>> LoadBlock for TempoEthApi<N> {}
307impl<N: FullNodeTypes<Types = TempoNode>> LoadReceipt for TempoEthApi<N> {}
308impl<N: FullNodeTypes<Types = TempoNode>> EthBlocks for TempoEthApi<N> {}
309impl<N: FullNodeTypes<Types = TempoNode>> LoadTransaction for TempoEthApi<N> {}
310
311impl<N: FullNodeTypes<Types = TempoNode>> EthTransactions for TempoEthApi<N> {
312 fn signers(&self) -> &SignersForRpc<Self::Provider, Self::NetworkTypes> {
313 self.inner.signers()
314 }
315
316 fn send_raw_transaction_sync_timeout(&self) -> std::time::Duration {
317 self.inner.send_raw_transaction_sync_timeout()
318 }
319
320 fn send_transaction(
321 &self,
322 tx: WithEncoded<Recovered<PoolPooledTx<Self::Pool>>>,
323 ) -> impl Future<Output = Result<B256, Self::Error>> + Send {
324 if tx.value().inner().subblock_proposer().is_some() {
325 Either::Left(async move {
327 let tx_hash = *tx.value().tx_hash();
328
329 self.subblock_transactions_tx
330 .send(tx.into_value())
331 .map_err(|_| {
332 EthApiError::from(RethError::msg("subblocks service channel closed"))
333 })?;
334
335 Ok(tx_hash)
336 })
337 } else {
338 Either::Right(self.inner.send_transaction(tx).map_err(Into::into))
340 }
341 }
342}
343
344#[derive(Debug, Clone)]
346#[expect(clippy::type_complexity)]
347pub struct TempoReceiptConverter {
348 inner: EthReceiptConverter<
349 TempoChainSpec,
350 fn(TempoReceipt, usize, TransactionMeta) -> ReceiptWithBloom<TempoReceipt<Log>>,
351 >,
352}
353
354impl TempoReceiptConverter {
355 pub fn new(chain_spec: Arc<TempoChainSpec>) -> Self {
356 Self {
357 inner: EthReceiptConverter::new(chain_spec).with_builder(
358 |receipt: TempoReceipt, next_log_index, meta| {
359 receipt.into_rpc(next_log_index, meta).into_with_bloom()
360 },
361 ),
362 }
363 }
364}
365
366impl ReceiptConverter<TempoPrimitives> for TempoReceiptConverter {
367 type RpcReceipt = TempoTransactionReceipt;
368 type Error = EthApiError;
369
370 fn convert_receipts(
371 &self,
372 receipts: Vec<ConvertReceiptInput<'_, TempoPrimitives>>,
373 ) -> Result<Vec<Self::RpcReceipt>, Self::Error> {
374 let txs = receipts.iter().map(|r| r.tx).collect::<Vec<_>>();
375 self.inner
376 .convert_receipts(receipts)?
377 .into_iter()
378 .zip(txs)
379 .map(|(inner, tx)| {
380 let mut receipt = TempoTransactionReceipt {
381 inner,
382 fee_token: None,
383 fee_payer: tx
385 .fee_payer(tx.signer())
386 .map_err(|_| EthApiError::InvalidTransactionSignature)?,
387 };
388 if receipt.effective_gas_price == 0 || receipt.gas_used == 0 {
389 return Ok(receipt);
390 }
391
392 receipt.fee_token = receipt.logs().last().map(|log| log.address());
397 Ok(receipt)
398 })
399 .collect()
400 }
401}
402
403#[derive(Debug, Default)]
404pub struct TempoEthApiBuilder;
405
406impl<N> EthApiBuilder<NodeAdapter<N>> for TempoEthApiBuilder
407where
408 N: FullNodeTypes<Types = TempoNode>,
409{
410 type EthApi = TempoEthApi<N>;
411
412 async fn build_eth_api(self, ctx: EthApiCtx<'_, NodeAdapter<N>>) -> eyre::Result<Self::EthApi> {
413 let chain_spec = ctx.components.provider.chain_spec();
414 let eth_api = ctx
415 .eth_api_builder()
416 .modify_gas_oracle_config(|config| config.default_suggested_fee = Some(U256::ZERO))
417 .map_converter(|_| RpcConverter::new(TempoReceiptConverter::new(chain_spec)).erased())
418 .build();
419
420 Ok(TempoEthApi::new(eth_api))
421 }
422}