22pragma solidity 0.8.24 ;
33
44import "./Ethscriptions.sol " ;
5- import "./TokenManager.sol " ;
6- import "./EthscriptionsERC20.sol " ;
75import "./L2/L2ToL1MessagePasser.sol " ;
6+ import "./L2/L1Block.sol " ;
87import "./libraries/Predeploys.sol " ;
8+ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol " ;
99
1010/// @title EthscriptionsProver
1111/// @notice Proves Ethscription ownership and token balances to L1 via OP Stack
1212/// @dev Uses L2ToL1MessagePasser to send provable messages to L1
1313contract EthscriptionsProver {
14+ using EnumerableSet for EnumerableSet.Bytes32Set;
15+
16+ /// @notice Info stored when an ethscription is queued for proving
17+ struct QueuedProof {
18+ uint256 l2BlockNumber;
19+ uint256 l2BlockTimestamp;
20+ bytes32 l1BlockHash;
21+ uint256 l1BlockNumber;
22+ }
23+
24+ /// @notice Set of all ethscription transaction hashes queued for proving
25+ EnumerableSet.Bytes32Set private queuedEthscriptions;
26+
27+ /// @notice Mapping from ethscription tx hash to its queued proof info
28+ mapping (bytes32 => QueuedProof) private queuedProofInfo;
29+
30+ /// @notice L1Block contract address for access control
31+ address public constant L1_BLOCK = Predeploys.L1_BLOCK_ATTRIBUTES;
1432 /// @notice L2ToL1MessagePasser predeploy address on OP Stack
1533 L2ToL1MessagePasser constant L2_TO_L1_MESSAGE_PASSER =
1634 L2ToL1MessagePasser (Predeploys.L2_TO_L1_MESSAGE_PASSER);
1735
1836 /// @notice The Ethscriptions contract (pre-deployed at known address)
1937 Ethscriptions public constant ethscriptions = Ethscriptions (Predeploys.ETHSCRIPTIONS);
2038
21- /// @notice The TokenManager contract (pre-deployed at known address)
22- TokenManager public constant tokenManager = TokenManager (Predeploys.TOKEN_MANAGER);
23-
24- /// @notice Struct for token balance proof data
25- struct TokenBalanceProof {
26- address holder;
27- string protocol;
28- string tick;
29- uint256 balance;
30- uint256 l2BlockNumber;
31- uint256 l2Timestamp;
32- // TODO: Add l1BlockNumber once we have L2->L1 block mapping
33- }
34-
3539 /// @notice Struct for ethscription data proof
3640 struct EthscriptionDataProof {
3741 bytes32 ethscriptionTxHash;
3842 bytes32 contentSha;
43+ bytes32 contentUriHash;
3944 address creator;
4045 address currentOwner;
4146 address previousOwner;
4247 uint256 ethscriptionNumber;
43- bool isToken;
44- uint256 tokenAmount;
48+ bool esip6;
49+ bytes32 l1BlockHash;
50+ uint256 l1BlockNumber;
4551 uint256 l2BlockNumber;
4652 uint256 l2Timestamp;
47- // TODO: Add l1BlockNumber once we have L2->L1 block mapping
4853 }
4954
5055 /// @notice Events for tracking proofs
51- event TokenBalanceProofSent (
52- address indexed holder ,
53- string tick ,
54- uint256 indexed l2BlockNumber ,
55- uint256 l2Timestamp
56- );
57-
5856 event EthscriptionDataProofSent (
5957 bytes32 indexed ethscriptionTxHash ,
6058 uint256 indexed l2BlockNumber ,
6159 uint256 l2Timestamp
6260 );
63-
64- /// @notice Prove token balance for an address
65- /// @param holder The address to prove balance for
66- /// @param deployTxHash The deploy transaction hash (identifies the token type)
67- function proveTokenBalance (
68- address holder ,
69- bytes32 deployTxHash
70- ) external {
71- // Get token info from TokenManager
72- TokenManager.TokenInfo memory tokenInfo = tokenManager.getTokenInfo (deployTxHash);
73-
74- // Get balance
75- EthscriptionsERC20 token = EthscriptionsERC20 (tokenInfo.tokenContract);
76- uint256 balance = token.balanceOf (holder);
77-
78- // Create proof struct
79- TokenBalanceProof memory proof = TokenBalanceProof ({
80- holder: holder,
81- protocol: tokenInfo.protocol,
82- tick: tokenInfo.tick,
83- balance: balance,
84- l2BlockNumber: block .number ,
85- l2Timestamp: block .timestamp
86- });
87-
88- // Encode and send to L1 with zero address and gas (only for state recording)
89- bytes memory proofData = abi.encode (proof);
90- L2_TO_L1_MESSAGE_PASSER.initiateWithdrawal (address (0 ), 0 , proofData);
91-
92- emit TokenBalanceProofSent (holder, tokenInfo.tick, block .number , block .timestamp );
61+
62+ /// @notice Queue an ethscription for proving
63+ /// @dev Only callable by the Ethscriptions contract
64+ /// @param txHash The transaction hash of the ethscription
65+ function queueEthscription (bytes32 txHash ) external virtual {
66+ require (msg .sender == address (ethscriptions), "Only Ethscriptions contract can queue " );
67+
68+ // Add to the set (deduplicates automatically)
69+ if (queuedEthscriptions.add (txHash)) {
70+ // Only store info if this is the first time we're queueing this txHash
71+ // Capture the L1 block hash and number at the time of queuing
72+ L1Block l1Block = L1Block (L1_BLOCK);
73+ queuedProofInfo[txHash] = QueuedProof ({
74+ l2BlockNumber: block .number ,
75+ l2BlockTimestamp: block .timestamp ,
76+ l1BlockHash: l1Block.hash (),
77+ l1BlockNumber: l1Block.number ()
78+ });
79+ }
9380 }
94-
95- /// @notice Prove ethscription existence and metadata
81+
82+ /// @notice Flush all queued proofs
83+ /// @dev Only callable by the L1Block contract at the start of each new block
84+ function flushAllProofs () external {
85+ require (msg .sender == L1_BLOCK, "Only L1Block can flush " );
86+
87+ uint256 count = queuedEthscriptions.length ();
88+
89+ // Process and remove each ethscription from the set
90+ // We iterate backwards to avoid index shifting during removal
91+ for (uint256 i = count; i > 0 ; i-- ) {
92+ bytes32 txHash = queuedEthscriptions.at (i - 1 );
93+
94+ // Create and send proof for current state with stored block info
95+ _createAndSendProof (txHash, queuedProofInfo[txHash]);
96+
97+ // Clean up: remove from set and delete the proof info
98+ queuedEthscriptions.remove (txHash);
99+ delete queuedProofInfo[txHash];
100+ }
101+ }
102+
103+ /// @notice Internal function to create and send proof for an ethscription
96104 /// @param ethscriptionTxHash The transaction hash of the ethscription
97- function proveEthscriptionData (bytes32 ethscriptionTxHash ) external virtual {
105+ /// @param proofInfo The queued proof info containing block data
106+ function _createAndSendProof (bytes32 ethscriptionTxHash , QueuedProof memory proofInfo ) internal {
98107 // Get ethscription data including previous owner
99108 Ethscriptions.Ethscription memory etsc = ethscriptions.getEthscription (ethscriptionTxHash);
100109 address currentOwner = ethscriptions.currentOwner (ethscriptionTxHash);
101-
102- // Check if it's a token item
103- bool isToken = tokenManager.isTokenItem (ethscriptionTxHash);
104- uint256 tokenAmount = 0 ;
105- if (isToken) {
106- tokenAmount = tokenManager.getTokenAmount (ethscriptionTxHash);
107- }
108-
109- // Create proof struct with previous owner
110+
111+ // Create proof struct with all ethscription data
110112 EthscriptionDataProof memory proof = EthscriptionDataProof ({
111113 ethscriptionTxHash: ethscriptionTxHash,
112114 contentSha: etsc.content.contentSha,
115+ contentUriHash: etsc.content.contentUriHash,
113116 creator: etsc.creator,
114117 currentOwner: currentOwner,
115118 previousOwner: etsc.previousOwner,
116119 ethscriptionNumber: etsc.ethscriptionNumber,
117- isToken: isToken,
118- tokenAmount: tokenAmount,
119- l2BlockNumber: block .number ,
120- l2Timestamp: block .timestamp
120+ esip6: etsc.content.esip6,
121+ l1BlockHash: proofInfo.l1BlockHash,
122+ l1BlockNumber: proofInfo.l1BlockNumber,
123+ l2BlockNumber: proofInfo.l2BlockNumber,
124+ l2Timestamp: proofInfo.l2BlockTimestamp
121125 });
122-
126+
123127 // Encode and send to L1 with zero address and gas (only for state recording)
124128 bytes memory proofData = abi.encode (proof);
125129 L2_TO_L1_MESSAGE_PASSER.initiateWithdrawal (address (0 ), 0 , proofData);
126-
127- emit EthscriptionDataProofSent (ethscriptionTxHash, block . number , block . timestamp );
130+
131+ emit EthscriptionDataProofSent (ethscriptionTxHash, proofInfo.l2BlockNumber, proofInfo.l2BlockTimestamp );
128132 }
133+
129134}
0 commit comments