11use std:: time:: Duration ;
22
33use alloy:: {
4+ hex,
45 network:: EthereumWallet ,
56 primitives:: { Address , FixedBytes , U256 } ,
67 providers:: { Provider , ProviderBuilder } ,
@@ -9,7 +10,9 @@ use alloy::{
910} ;
1011use anyhow:: { anyhow, Result } ;
1112use async_trait:: async_trait;
12- use ceramic_anchor_service:: { DetachedTimeEvent , MerkleNodes , RootTimeEvent , TransactionManager } ;
13+ use ceramic_anchor_service:: {
14+ ChainInclusionData , DetachedTimeEvent , MerkleNodes , RootTimeEvent , TransactionManager ,
15+ } ;
1316use ceramic_core:: { Cid , SerializeExt } ;
1417use tokio:: time:: { interval, sleep} ;
1518use tracing:: { debug, info, warn} ;
@@ -82,6 +85,18 @@ pub struct EvmTransactionManager {
8285 config : EvmConfig ,
8386}
8487
88+ /// Result of submitting and confirming an anchor transaction
89+ struct AnchorResult {
90+ /// The transaction hash (0x-prefixed)
91+ tx_hash : String ,
92+ /// The block hash containing the transaction
93+ block_hash : String ,
94+ /// The block timestamp (Unix timestamp in seconds)
95+ timestamp : u64 ,
96+ /// The transaction input data (0x-prefixed function selector + hash)
97+ tx_input : String ,
98+ }
99+
85100impl EvmTransactionManager {
86101 /// Create a new EVM transaction manager
87102 pub async fn new ( config : EvmConfig ) -> Result < Self > {
@@ -134,7 +149,7 @@ impl EvmTransactionManager {
134149 }
135150
136151 /// Submit an anchor transaction and wait for confirmation with retry logic
137- async fn submit_and_wait ( & self , root_cid : Cid ) -> Result < String > {
152+ async fn submit_and_wait ( & self , root_cid : Cid ) -> Result < AnchorResult > {
138153 info ! (
139154 "Anchoring root CID: {} on chain {}" ,
140155 root_cid, self . config. chain_id
@@ -255,7 +270,29 @@ impl EvmTransactionManager {
255270 let gas_used = starting_balance. saturating_sub ( ending_balance) ;
256271 info ! ( "Total gas cost: {} wei" , gas_used) ;
257272 }
258- return Ok ( tx_hash) ;
273+
274+ // Get block hash from receipt
275+ let block_hash = receipt
276+ . block_hash
277+ . ok_or_else ( || anyhow ! ( "Transaction receipt missing block hash" ) ) ?;
278+
279+ // Fetch block to get timestamp (false = don't include full transactions)
280+ let block = provider
281+ . get_block_by_number ( block_number. into ( ) , false )
282+ . await ?
283+ . ok_or_else ( || anyhow ! ( "Block {} not found" , block_number) ) ?;
284+
285+ // Construct tx_input: function selector (0x97ad09eb) + 32-byte hash
286+ let root_hash = Self :: cid_to_bytes32 ( & root_cid) ?;
287+ let tx_input =
288+ format ! ( "0x97ad09eb{}" , hex:: encode( root_hash. as_slice( ) ) ) ;
289+
290+ return Ok ( AnchorResult {
291+ tx_hash,
292+ block_hash : format ! ( "0x{:x}" , block_hash) ,
293+ timestamp : block. header . timestamp ,
294+ tx_input,
295+ } ) ;
259296 }
260297 Err ( e) => {
261298 warn ! ( "Transaction confirmation failed: {}" , e) ;
@@ -287,7 +324,7 @@ impl EvmTransactionManager {
287324 || error_str. contains ( "replacement transaction underpriced" ) )
288325 {
289326 for prev_tx in previous_tx_hashes. iter ( ) . rev ( ) {
290- if let Ok ( Some ( _ ) ) = provider
327+ if let Ok ( Some ( prev_receipt ) ) = provider
291328 . get_transaction_receipt ( prev_tx. parse ( ) . unwrap_or_default ( ) )
292329 . await
293330 {
@@ -297,7 +334,32 @@ impl EvmTransactionManager {
297334 {
298335 info ! ( "Ending wallet balance: {} wei" , ending_balance) ;
299336 }
300- return Ok ( prev_tx. clone ( ) ) ;
337+
338+ // Get block info from the previous receipt
339+ let block_hash = prev_receipt. block_hash . ok_or_else ( || {
340+ anyhow ! ( "Previous transaction receipt missing block hash" )
341+ } ) ?;
342+ let block_number = prev_receipt. block_number . ok_or_else ( || {
343+ anyhow ! ( "Previous transaction receipt missing block number" )
344+ } ) ?;
345+
346+ // Fetch block to get timestamp (false = don't include full transactions)
347+ let block = provider
348+ . get_block_by_number ( block_number. into ( ) , false )
349+ . await ?
350+ . ok_or_else ( || anyhow ! ( "Block {} not found" , block_number) ) ?;
351+
352+ // Construct tx_input from root_cid
353+ let root_hash = Self :: cid_to_bytes32 ( & root_cid) ?;
354+ let tx_input =
355+ format ! ( "0x97ad09eb{}" , hex:: encode( root_hash. as_slice( ) ) ) ;
356+
357+ return Ok ( AnchorResult {
358+ tx_hash : prev_tx. clone ( ) ,
359+ block_hash : format ! ( "0x{:x}" , block_hash) ,
360+ timestamp : block. header . timestamp ,
361+ tx_input,
362+ } ) ;
301363 }
302364 }
303365 }
@@ -428,10 +490,11 @@ impl EvmTransactionManager {
428490impl TransactionManager for EvmTransactionManager {
429491 async fn anchor_root ( & self , root : Cid ) -> Result < RootTimeEvent > {
430492 // Submit transaction and wait for confirmation
431- let tx_hash = self . submit_and_wait ( root) . await ?;
493+ let anchor_result = self . submit_and_wait ( root) . await ?;
432494
433495 // Build anchor proof from transaction details
434- let proof = ProofBuilder :: build_proof ( self . config . chain_id , tx_hash, root) ?;
496+ let proof =
497+ ProofBuilder :: build_proof ( self . config . chain_id , anchor_result. tx_hash . clone ( ) , root) ?;
435498 let proof_cid = proof. to_cid ( ) ?;
436499
437500 // Create detached time event
@@ -441,12 +504,22 @@ impl TransactionManager for EvmTransactionManager {
441504 proof : proof_cid,
442505 } ;
443506
507+ // Create chain inclusion data for persistence
508+ let chain_inclusion = ChainInclusionData {
509+ chain_id : format ! ( "eip155:{}" , self . config. chain_id) ,
510+ transaction_hash : anchor_result. tx_hash ,
511+ transaction_input : anchor_result. tx_input ,
512+ block_hash : anchor_result. block_hash ,
513+ timestamp : anchor_result. timestamp ,
514+ } ;
515+
444516 // Return root time event with no additional remote Merkle nodes
445517 // (all nodes are local since we built the entire tree)
446518 Ok ( RootTimeEvent {
447519 proof,
448520 detached_time_event,
449521 remote_merkle_nodes : MerkleNodes :: default ( ) ,
522+ chain_inclusion : Some ( chain_inclusion) ,
450523 } )
451524 }
452525}
0 commit comments