@@ -6,11 +6,28 @@ import "./TokenManager.sol";
66import "./EthscriptionsERC20.sol " ;
77import "./L2/L2ToL1MessagePasser.sol " ;
88import "./libraries/Predeploys.sol " ;
9+ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol " ;
910
1011/// @title EthscriptionsProver
1112/// @notice Proves Ethscription ownership and token balances to L1 via OP Stack
1213/// @dev Uses L2ToL1MessagePasser to send provable messages to L1
1314contract EthscriptionsProver {
15+ using EnumerableSet for EnumerableSet.Bytes32Set;
16+
17+ /// @notice Info stored when an ethscription is queued for proving
18+ struct QueuedProof {
19+ uint256 blockNumber;
20+ uint256 blockTimestamp;
21+ }
22+
23+ /// @notice Set of all ethscription transaction hashes queued for proving
24+ EnumerableSet.Bytes32Set private queuedEthscriptions;
25+
26+ /// @notice Mapping from ethscription tx hash to its queued proof info
27+ mapping (bytes32 => QueuedProof) private queuedProofInfo;
28+
29+ /// @notice L1Block contract address for access control
30+ address public constant L1_BLOCK = Predeploys.L1_BLOCK_ATTRIBUTES;
1431 /// @notice L2ToL1MessagePasser predeploy address on OP Stack
1532 L2ToL1MessagePasser constant L2_TO_L1_MESSAGE_PASSER =
1633 L2ToL1MessagePasser (Predeploys.L2_TO_L1_MESSAGE_PASSER);
@@ -60,6 +77,9 @@ contract EthscriptionsProver {
6077 uint256 indexed l2BlockNumber ,
6178 uint256 l2Timestamp
6279 );
80+
81+ /// @notice Emitted when a batch of proofs is flushed
82+ event ProofBatchFlushed (uint256 count , uint256 blockNumber );
6383
6484 /// @notice Prove token balance for an address
6585 /// @param holder The address to prove balance for
@@ -92,20 +112,64 @@ contract EthscriptionsProver {
92112 emit TokenBalanceProofSent (holder, tokenInfo.tick, block .number , block .timestamp );
93113 }
94114
95- /// @notice Prove ethscription existence and metadata
115+ /// @notice Queue an ethscription for proving
116+ /// @dev Only callable by the Ethscriptions contract
117+ /// @param txHash The transaction hash of the ethscription
118+ function queueEthscription (bytes32 txHash ) external virtual {
119+ require (msg .sender == address (ethscriptions), "Only Ethscriptions contract can queue " );
120+
121+ // Add to the set (deduplicates automatically)
122+ if (queuedEthscriptions.add (txHash)) {
123+ // Only store info if this is the first time we're queueing this txHash
124+ queuedProofInfo[txHash] = QueuedProof ({
125+ blockNumber: block .number ,
126+ blockTimestamp: block .timestamp
127+ });
128+ }
129+ }
130+
131+ /// @notice Flush all queued proofs
132+ /// @dev Only callable by the L1Block contract at the start of each new block
133+ function flushAllProofs () external {
134+ require (msg .sender == L1_BLOCK, "Only L1Block can flush " );
135+
136+ uint256 count = queuedEthscriptions.length ();
137+
138+ // Process and remove each ethscription from the set
139+ // We iterate backwards to avoid index shifting during removal
140+ for (uint256 i = count; i > 0 ; i-- ) {
141+ bytes32 txHash = queuedEthscriptions.at (i - 1 );
142+
143+ // Get the stored proof info to know which block this was from
144+ QueuedProof memory proofInfo = queuedProofInfo[txHash];
145+
146+ // Create and send proof for current state with stored block info
147+ _createAndSendProof (txHash, proofInfo.blockNumber, proofInfo.blockTimestamp);
148+
149+ // Clean up: remove from set and delete the proof info
150+ queuedEthscriptions.remove (txHash);
151+ delete queuedProofInfo[txHash];
152+ }
153+
154+ emit ProofBatchFlushed (count, block .number - 1 );
155+ }
156+
157+ /// @notice Internal function to create and send proof for an ethscription
96158 /// @param ethscriptionTxHash The transaction hash of the ethscription
97- function proveEthscriptionData (bytes32 ethscriptionTxHash ) external virtual {
159+ /// @param blockNumber The L2 block number being proved
160+ /// @param blockTimestamp The timestamp of the L2 block being proved
161+ function _createAndSendProof (bytes32 ethscriptionTxHash , uint256 blockNumber , uint256 blockTimestamp ) internal {
98162 // Get ethscription data including previous owner
99163 Ethscriptions.Ethscription memory etsc = ethscriptions.getEthscription (ethscriptionTxHash);
100164 address currentOwner = ethscriptions.currentOwner (ethscriptionTxHash);
101-
165+
102166 // Check if it's a token item
103167 bool isToken = tokenManager.isTokenItem (ethscriptionTxHash);
104168 uint256 tokenAmount = 0 ;
105169 if (isToken) {
106170 tokenAmount = tokenManager.getTokenAmount (ethscriptionTxHash);
107171 }
108-
172+
109173 // Create proof struct with previous owner
110174 EthscriptionDataProof memory proof = EthscriptionDataProof ({
111175 ethscriptionTxHash: ethscriptionTxHash,
@@ -116,14 +180,15 @@ contract EthscriptionsProver {
116180 ethscriptionNumber: etsc.ethscriptionNumber,
117181 isToken: isToken,
118182 tokenAmount: tokenAmount,
119- l2BlockNumber: block . number ,
120- l2Timestamp: block . timestamp
183+ l2BlockNumber: blockNumber ,
184+ l2Timestamp: blockTimestamp
121185 });
122-
186+
123187 // Encode and send to L1 with zero address and gas (only for state recording)
124188 bytes memory proofData = abi.encode (proof);
125189 L2_TO_L1_MESSAGE_PASSER.initiateWithdrawal (address (0 ), 0 , proofData);
126-
127- emit EthscriptionDataProofSent (ethscriptionTxHash, block . number , block . timestamp );
190+
191+ emit EthscriptionDataProofSent (ethscriptionTxHash, blockNumber, blockTimestamp );
128192 }
193+
129194}
0 commit comments