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,28 @@ 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 = format ! ( "0x97ad09eb{}" , hex:: encode( root_hash. as_slice( ) ) ) ;
288+
289+ return Ok ( AnchorResult {
290+ tx_hash,
291+ block_hash : format ! ( "0x{:x}" , block_hash) ,
292+ timestamp : block. header . timestamp ,
293+ tx_input,
294+ } ) ;
259295 }
260296 Err ( e) => {
261297 warn ! ( "Transaction confirmation failed: {}" , e) ;
@@ -287,7 +323,7 @@ impl EvmTransactionManager {
287323 || error_str. contains ( "replacement transaction underpriced" ) )
288324 {
289325 for prev_tx in previous_tx_hashes. iter ( ) . rev ( ) {
290- if let Ok ( Some ( _ ) ) = provider
326+ if let Ok ( Some ( prev_receipt ) ) = provider
291327 . get_transaction_receipt ( prev_tx. parse ( ) . unwrap_or_default ( ) )
292328 . await
293329 {
@@ -297,7 +333,32 @@ impl EvmTransactionManager {
297333 {
298334 info ! ( "Ending wallet balance: {} wei" , ending_balance) ;
299335 }
300- return Ok ( prev_tx. clone ( ) ) ;
336+
337+ // Get block info from the previous receipt
338+ let block_hash = prev_receipt. block_hash . ok_or_else ( || {
339+ anyhow ! ( "Previous transaction receipt missing block hash" )
340+ } ) ?;
341+ let block_number = prev_receipt. block_number . ok_or_else ( || {
342+ anyhow ! ( "Previous transaction receipt missing block number" )
343+ } ) ?;
344+
345+ // Fetch block to get timestamp (false = don't include full transactions)
346+ let block = provider
347+ . get_block_by_number ( block_number. into ( ) , false )
348+ . await ?
349+ . ok_or_else ( || anyhow ! ( "Block {} not found" , block_number) ) ?;
350+
351+ // Construct tx_input from root_cid
352+ let root_hash = Self :: cid_to_bytes32 ( & root_cid) ?;
353+ let tx_input =
354+ format ! ( "0x97ad09eb{}" , hex:: encode( root_hash. as_slice( ) ) ) ;
355+
356+ return Ok ( AnchorResult {
357+ tx_hash : prev_tx. clone ( ) ,
358+ block_hash : format ! ( "0x{:x}" , block_hash) ,
359+ timestamp : block. header . timestamp ,
360+ tx_input,
361+ } ) ;
301362 }
302363 }
303364 }
@@ -428,10 +489,11 @@ impl EvmTransactionManager {
428489impl TransactionManager for EvmTransactionManager {
429490 async fn anchor_root ( & self , root : Cid ) -> Result < RootTimeEvent > {
430491 // Submit transaction and wait for confirmation
431- let tx_hash = self . submit_and_wait ( root) . await ?;
492+ let anchor_result = self . submit_and_wait ( root) . await ?;
432493
433494 // Build anchor proof from transaction details
434- let proof = ProofBuilder :: build_proof ( self . config . chain_id , tx_hash, root) ?;
495+ let proof =
496+ ProofBuilder :: build_proof ( self . config . chain_id , anchor_result. tx_hash . clone ( ) , root) ?;
435497 let proof_cid = proof. to_cid ( ) ?;
436498
437499 // Create detached time event
@@ -441,12 +503,22 @@ impl TransactionManager for EvmTransactionManager {
441503 proof : proof_cid,
442504 } ;
443505
506+ // Create chain inclusion data for persistence
507+ let chain_inclusion = ChainInclusionData {
508+ chain_id : format ! ( "eip155:{}" , self . config. chain_id) ,
509+ transaction_hash : anchor_result. tx_hash ,
510+ transaction_input : anchor_result. tx_input ,
511+ block_hash : anchor_result. block_hash ,
512+ timestamp : anchor_result. timestamp ,
513+ } ;
514+
444515 // Return root time event with no additional remote Merkle nodes
445516 // (all nodes are local since we built the entire tree)
446517 Ok ( RootTimeEvent {
447518 proof,
448519 detached_time_event,
449520 remote_merkle_nodes : MerkleNodes :: default ( ) ,
521+ chain_inclusion : Some ( chain_inclusion) ,
450522 } )
451523 }
452524}
0 commit comments