diff --git a/app/models/ethscription_transaction.rb b/app/models/ethscription_transaction.rb index 4c70d9e..4c85109 100644 --- a/app/models/ethscription_transaction.rb +++ b/app/models/ethscription_transaction.rb @@ -199,7 +199,7 @@ def build_create_calldata # Encode parameters params = [ - tx_hash_bin, # bytes32 transactionHash + tx_hash_bin, # bytes32 ethscriptionId (L1 tx hash) content_uri_hash, # bytes32 contentUriHash owner_bin, # address raw_content, # bytes content diff --git a/contracts/script/L2Genesis.s.sol b/contracts/script/L2Genesis.s.sol index 57d09d6..b477898 100644 --- a/contracts/script/L2Genesis.s.sol +++ b/contracts/script/L2Genesis.s.sol @@ -13,11 +13,11 @@ import "forge-std/console.sol"; /// @dev Used only during genesis, then replaced with the real Ethscriptions contract contract GenesisEthscriptions is Ethscriptions { - /// @notice Store a genesis ethscription transaction hash for later event emission + /// @notice Store a genesis ethscription ID for later event emission /// @dev Internal function only used during genesis setup - /// @param transactionHash The transaction hash to store - function _storePendingGenesisEvent(bytes32 transactionHash) internal { - pendingGenesisEvents.push(transactionHash); + /// @param ethscriptionId The ethscription ID (L1 tx hash) to store + function _storePendingGenesisEvent(bytes32 ethscriptionId) internal { + pendingGenesisEvents.push(ethscriptionId); } /// @notice Create an ethscription with all values explicitly set for genesis @@ -29,7 +29,7 @@ contract GenesisEthscriptions is Ethscriptions { bytes32 l1BlockHash ) public returns (uint256 tokenId) { require(creator != address(0), "Invalid creator"); - require(ethscriptions[params.transactionHash].creator == address(0), "Ethscription already exists"); + require(ethscriptions[params.ethscriptionId].creator == address(0), "Ethscription already exists"); // Check protocol uniqueness using content URI hash if (firstEthscriptionByContentUri[params.contentUriHash] != bytes32(0)) { @@ -40,10 +40,10 @@ contract GenesisEthscriptions is Ethscriptions { bytes32 contentSha = _storeContent(params.content); // Mark content URI as used by storing this ethscription's tx hash - firstEthscriptionByContentUri[params.contentUriHash] = params.transactionHash; + firstEthscriptionByContentUri[params.contentUriHash] = params.ethscriptionId; // Set all values including genesis-specific ones - ethscriptions[params.transactionHash] = Ethscription({ + ethscriptions[params.ethscriptionId] = Ethscription({ // Fixed-size fields contentUriHash: params.contentUriHash, contentSha: contentSha, @@ -66,8 +66,8 @@ contract GenesisEthscriptions is Ethscriptions { // Use ethscription number as token ID tokenId = totalSupply(); - // Store the mapping from token ID to transaction hash - tokenIdToTransactionHash[tokenId] = params.transactionHash; + // Store the mapping from token ID to ethscription ID + tokenIdToEthscriptionId[tokenId] = params.ethscriptionId; // Token count is automatically tracked by enumerable's _update @@ -82,7 +82,7 @@ contract GenesisEthscriptions is Ethscriptions { // Store the transaction hash so all events can be emitted later // The emission logic in _emitPendingGenesisEvents will figure out // what events to emit based on the ethscription data - _storePendingGenesisEvent(params.transactionHash); + _storePendingGenesisEvent(params.ethscriptionId); // Skip token handling for genesis } @@ -333,7 +333,7 @@ contract L2Genesis is Script { // Create params struct with parsed data from JSON // The JSON already has all the properly processed data Ethscriptions.CreateEthscriptionParams memory params; - params.transactionHash = vm.parseJsonBytes32(json, string.concat(basePath, ".transaction_hash")); + params.ethscriptionId = vm.parseJsonBytes32(json, string.concat(basePath, ".transaction_hash")); params.contentUriHash = vm.parseJsonBytes32(json, string.concat(basePath, ".content_uri_hash")); params.initialOwner = initialOwner; params.content = vm.parseJsonBytes(json, string.concat(basePath, ".content")); diff --git a/contracts/script/TestTokenUri.s.sol b/contracts/script/TestTokenUri.s.sol index 76e7406..5fc7ab9 100644 --- a/contracts/script/TestTokenUri.s.sol +++ b/contracts/script/TestTokenUri.s.sol @@ -17,7 +17,7 @@ contract TestTokenUri is Script { // Test case 1: Plain text (should use viewer) vm.prank(address(0x1111)); eth.createEthscription(Ethscriptions.CreateEthscriptionParams({ - transactionHash: keccak256("text1"), + ethscriptionId: keccak256("text1"), contentUriHash: keccak256("data:text/plain,Hello World!"), initialOwner: address(0x1111), content: bytes("Hello World!"), @@ -29,7 +29,7 @@ contract TestTokenUri is Script { // Test case 2: JSON content (should use viewer with pretty print) vm.prank(address(0x2222)); eth.createEthscription(Ethscriptions.CreateEthscriptionParams({ - transactionHash: keccak256("json1"), + ethscriptionId: keccak256("json1"), contentUriHash: keccak256('data:application/json,{"p":"erc-20","op":"mint","tick":"test","amt":"1000"}'), initialOwner: address(0x2222), content: bytes('{"p":"erc-20","op":"mint","tick":"test","amt":"1000"}'), @@ -41,7 +41,7 @@ contract TestTokenUri is Script { // Test case 3: HTML content (should pass through directly) vm.prank(address(0x3333)); eth.createEthscription(Ethscriptions.CreateEthscriptionParams({ - transactionHash: keccak256("html1"), + ethscriptionId: keccak256("html1"), contentUriHash: keccak256('data:text/html,

Ethscriptions Rule!

'), initialOwner: address(0x3333), content: bytes('

Ethscriptions Rule!

'), @@ -54,7 +54,7 @@ contract TestTokenUri is Script { bytes memory redPixelPng = Base64.decode("iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAm0lEQVR42mNgGITgPxTTxvBleTo0swBsOK0s+N8aJkczC1AMR7KAKpb8v72xAY5hFsD4lFoCN+j56ZUoliBbSoklGIZjwxRbQAjT1YK7d+82kGUBeuQii5FrAYYrL81NwCpGFQtoEUT/6RoHWAyknQV0S6ZI5RE6Jt8CZIOOHTuGgR9Fq5FkCf19QM3wx5rZKHEtsRZQt5qkhgUAR6cGaUehOD4AAAAASUVORK5CYII="); vm.prank(address(0x4444)); eth.createEthscription(Ethscriptions.CreateEthscriptionParams({ - transactionHash: keccak256("image1"), + ethscriptionId: keccak256("image1"), contentUriHash: keccak256("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAm0lEQVR42mNgGITgPxTTxvBleTo0swBsOK0s+N8aJkczC1AMR7KAKpb8v72xAY5hFsD4lFoCN+j56ZUoliBbSoklGIZjwxRbQAjT1YK7d+82kGUBeuQii5FrAYYrL81NwCpGFQtoEUT/6RoHWAyknQV0S6ZI5RE6Jt8CZIOOHTuGgR9Fq5FkCf19QM3wx5rZKHEtsRZQt5qkhgUAR6cGaUehOD4AAAAASUVORK5CYII="), initialOwner: address(0x4444), content: redPixelPng, @@ -66,7 +66,7 @@ contract TestTokenUri is Script { // Test case 5: CSS content (should use viewer) vm.prank(address(0x5555)); eth.createEthscription(Ethscriptions.CreateEthscriptionParams({ - transactionHash: keccak256("css1"), + ethscriptionId: keccak256("css1"), contentUriHash: keccak256("data:text/css,body { background: #000; color: #0f0; font-family: 'Courier New'; }"), initialOwner: address(0x5555), content: bytes("body { background: #000; color: #0f0; font-family: 'Courier New'; }"), @@ -110,4 +110,4 @@ contract TestTokenUri is Script { console.log("=== PASTE ANY OF THE ABOVE data: URIs INTO YOUR BROWSER ==="); } -} \ No newline at end of file +} diff --git a/contracts/src/CollectionsManager.sol b/contracts/src/CollectionsManager.sol index 06b392e..975d7de 100644 --- a/contracts/src/CollectionsManager.sol +++ b/contracts/src/CollectionsManager.sol @@ -36,7 +36,7 @@ contract CollectionsManager is IProtocolHandler { // Runtime state for a collection (separate from metadata) struct CollectionState { address collectionContract; - bytes32 createTxHash; + bytes32 createEthscriptionId; uint256 currentSize; bool locked; } @@ -135,7 +135,7 @@ contract CollectionsManager is IProtocolHandler { event CollectionLocked(bytes32 indexed collectionId); // Mirror success signaling so indexers can detect success without relying on router - event ProtocolHandlerSuccess(bytes32 indexed transactionHash, string protocol); + event ProtocolHandlerSuccess(bytes32 indexed ethscriptionId, string protocol); modifier onlyEthscriptions() { require(msg.sender == ethscriptions, "Only Ethscriptions contract"); @@ -169,7 +169,7 @@ contract CollectionsManager is IProtocolHandler { // Store collection state collectionState[collectionId] = CollectionState({ collectionContract: collectionContract, - createTxHash: txHash, + createEthscriptionId: txHash, currentSize: 0, locked: false }); @@ -408,7 +408,7 @@ contract CollectionsManager is IProtocolHandler { /// @notice Handle transfer notification from Ethscriptions contract /// @dev When an ethscription that's part of a collection is transferred, sync the ERC721 function onTransfer( - bytes32 txHash, + bytes32 ethscriptionId, address from, address to ) external override onlyEthscriptions { diff --git a/contracts/src/Ethscriptions.sol b/contracts/src/Ethscriptions.sol index f23602a..877fd3a 100644 --- a/contracts/src/Ethscriptions.sol +++ b/contracts/src/Ethscriptions.sol @@ -50,7 +50,7 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { } struct CreateEthscriptionParams { - bytes32 transactionHash; + bytes32 ethscriptionId; bytes32 contentUriHash; // SHA256 of raw content URI (for protocol uniqueness) address initialOwner; bytes content; // Raw decoded bytes (not Base64) @@ -73,7 +73,7 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { // STATE VARIABLES // ============================================================= - /// @dev Transaction hash => Ethscription data + /// @dev Ethscription ID (L1 tx hash) => Ethscription data mapping(bytes32 => Ethscription) internal ethscriptions; /// @dev Content SHA => SSTORE2 pointers array @@ -83,8 +83,8 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { /// @dev bytes32(0) means unused, non-zero means the content URI has been used mapping(bytes32 => bytes32) public firstEthscriptionByContentUri; - /// @dev Mapping from ethscription number (token ID) to transaction hash - mapping(uint256 => bytes32) public tokenIdToTransactionHash; + /// @dev Mapping from token ID (ethscription number) to ethscription ID (L1 tx hash) + mapping(uint256 => bytes32) public tokenIdToEthscriptionId; /// @dev Protocol registry - maps protocol names to handler addresses mapping(string => address) public protocolHandlers; @@ -117,7 +117,7 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { /// @notice Emitted when a new ethscription is created event EthscriptionCreated( - bytes32 indexed transactionHash, + bytes32 indexed ethscriptionId, address indexed creator, address indexed initialOwner, bytes32 contentUriHash, @@ -129,7 +129,7 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { /// @dev This event matches the Ethscriptions protocol transfer semantics where 'from' is the initiator /// For creations, this shows transfer from creator to initial owner (not from address(0)) event EthscriptionTransferred( - bytes32 indexed transactionHash, + bytes32 indexed ethscriptionId, address indexed from, address indexed to, uint256 ethscriptionNumber @@ -140,14 +140,14 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { /// @notice Emitted when a protocol handler operation fails but ethscription continues event ProtocolHandlerFailed( - bytes32 indexed transactionHash, + bytes32 indexed ethscriptionId, string protocol, bytes revertData ); /// @notice Emitted when a protocol handler operation succeeds event ProtocolHandlerSuccess( - bytes32 indexed transactionHash, + bytes32 indexed ethscriptionId, string protocol, bytes returnData ); @@ -162,11 +162,17 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { _; } - /// @notice Modifier to require that an ethscription exists - /// @param transactionHash The transaction hash to check - modifier requireExists(bytes32 transactionHash) { - if (!_ethscriptionExists(transactionHash)) revert EthscriptionDoesNotExist(); - _; + /// @notice Resolve and validate an ethscription (by ID) or revert + function _getEthscriptionOrRevert(bytes32 ethscriptionId) internal view returns (Ethscription storage ethscription) { + if (!_ethscriptionExists(ethscriptionId)) revert EthscriptionDoesNotExist(); + ethscription = ethscriptions[ethscriptionId]; + } + + /// @notice Resolve and validate an ethscription (by tokenId) or revert + function _getEthscriptionOrRevert(uint256 tokenId) internal view returns (Ethscription storage ethscription) { + bytes32 id = tokenIdToEthscriptionId[tokenId]; + if (!_ethscriptionExists(id)) revert TokenDoesNotExist(); + ethscription = ethscriptions[id]; } // ============================================================= @@ -199,20 +205,20 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { address creator = msg.sender; if (creator == address(0)) revert InvalidCreator(); - if (_ethscriptionExists(params.transactionHash)) revert EthscriptionAlreadyExists(); + if (_ethscriptionExists(params.ethscriptionId)) revert EthscriptionAlreadyExists(); bool contentUriAlreadySeen = firstEthscriptionByContentUri[params.contentUriHash] != bytes32(0); if (contentUriAlreadySeen) { if (!params.esip6) revert DuplicateContentUri(); } else { - firstEthscriptionByContentUri[params.contentUriHash] = params.transactionHash; + firstEthscriptionByContentUri[params.contentUriHash] = params.ethscriptionId; } // Store content and get content SHA (of raw bytes) bytes32 contentSha = _storeContent(params.content); - ethscriptions[params.transactionHash] = Ethscription({ + ethscriptions[params.ethscriptionId] = Ethscription({ contentUriHash: params.contentUriHash, contentSha: contentSha, l1BlockHash: l1Block.hash(), @@ -230,8 +236,8 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { // Use ethscription number as token ID tokenId = totalSupply(); - // Store the mapping from token ID to transaction hash - tokenIdToTransactionHash[tokenId] = params.transactionHash; + // Store the mapping from token ID to ethscription ID + tokenIdToEthscriptionId[tokenId] = params.ethscriptionId; // Mint to initial owner (if address(0), mint to creator then transfer) if (params.initialOwner == address(0)) { @@ -242,7 +248,7 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { } emit EthscriptionCreated( - params.transactionHash, + params.ethscriptionId, creator, params.initialOwner, params.contentUriHash, @@ -251,20 +257,20 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { ); // Handle protocol operations (if any) - _callProtocolOperation(params.transactionHash, params.protocolParams); + _callProtocolOperation(params.ethscriptionId, params.protocolParams); } /// @notice Transfer an ethscription /// @dev Called via system transaction with msg.sender spoofed as 'from' /// @param to The recipient address (can be address(0) for burning) - /// @param transactionHash The ethscription to transfer (used to find token ID) + /// @param ethscriptionId The ethscription to transfer (used to find token ID) function transferEthscription( address to, - bytes32 transactionHash - ) external requireExists(transactionHash) { - // Get the ethscription number to use as token ID - Ethscription storage etsc = ethscriptions[transactionHash]; - uint256 tokenId = etsc.ethscriptionNumber; + bytes32 ethscriptionId + ) external { + // Load and validate + Ethscription storage ethscription = _getEthscriptionOrRevert(ethscriptionId); + uint256 tokenId = ethscription.ethscriptionNumber; // Standard ERC721 transfer will handle authorization transferFrom(msg.sender, to, tokenId); } @@ -272,39 +278,39 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { /// @notice Transfer an ethscription with previous owner validation (ESIP-2) /// @dev Called via system transaction with msg.sender spoofed as 'from' /// @param to The recipient address (can be address(0) for burning) - /// @param transactionHash The ethscription to transfer + /// @param ethscriptionId The ethscription to transfer /// @param previousOwner The required previous owner for validation function transferEthscriptionForPreviousOwner( address to, - bytes32 transactionHash, + bytes32 ethscriptionId, address previousOwner - ) external requireExists(transactionHash) { - Ethscription storage etsc = ethscriptions[transactionHash]; + ) external { + Ethscription storage ethscription = _getEthscriptionOrRevert(ethscriptionId); // Verify the previous owner matches - if (etsc.previousOwner != previousOwner) { + if (ethscription.previousOwner != previousOwner) { revert PreviousOwnerMismatch(); } // Use transferFrom which now handles burns when to == address(0) - transferFrom(msg.sender, to, etsc.ethscriptionNumber); + transferFrom(msg.sender, to, ethscription.ethscriptionNumber); } /// @notice Transfer multiple ethscriptions to a single recipient /// @dev Continues transferring even if individual transfers fail due to wrong ownership - /// @param transactionHashes Array of ethscription hashes to transfer + /// @param ethscriptionIds Array of ethscription IDs to transfer /// @param to The recipient address (can be address(0) for burning) /// @return successCount Number of successful transfers function transferEthscriptions( address to, - bytes32[] calldata transactionHashes + bytes32[] calldata ethscriptionIds ) external returns (uint256 successCount) { - for (uint256 i = 0; i < transactionHashes.length; i++) { + for (uint256 i = 0; i < ethscriptionIds.length; i++) { // Get the ethscription to find its token ID - if (!_ethscriptionExists(transactionHashes[i])) continue; // Skip non-existent ethscriptions - Ethscription storage etsc = ethscriptions[transactionHashes[i]]; + if (!_ethscriptionExists(ethscriptionIds[i])) continue; // Skip non-existent ethscriptions + Ethscription storage ethscription = ethscriptions[ethscriptionIds[i]]; - uint256 tokenId = etsc.ethscriptionNumber; + uint256 tokenId = ethscription.ethscriptionNumber; // Check if sender owns this token before attempting transfer // This prevents reverts and allows us to continue @@ -337,81 +343,108 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { /// @notice Returns the full data URI for a token function tokenURI(uint256 tokenId) public view override returns (string memory) { - // Find the transaction hash for this token ID (ethscription number) - bytes32 txHash = tokenIdToTransactionHash[tokenId]; - if (!_ethscriptionExists(txHash)) revert TokenDoesNotExist(); - Ethscription storage etsc = ethscriptions[txHash]; + // Find the ethscription for this token ID (ethscription number) + Ethscription storage ethscription = _getEthscriptionOrRevert(tokenId); + bytes32 id = tokenIdToEthscriptionId[tokenId]; // Get content - bytes memory content = getEthscriptionContent(txHash); + bytes memory content = getEthscriptionContent(id); // Build complete token URI using the library - it handles everything internally - return EthscriptionsRendererLib.buildTokenURI(etsc, txHash, content); + return EthscriptionsRendererLib.buildTokenURI(ethscription, id, content); } /// @notice Get the media URI for an ethscription (image or animation_url) - /// @param txHash The transaction hash of the ethscription + /// @param ethscriptionId The ethscription ID (L1 tx hash) of the ethscription /// @return mediaType Either "image" or "animation_url" /// @return mediaUri The data URI for the media - function getMediaUri(bytes32 txHash) external view requireExists(txHash) returns (string memory mediaType, string memory mediaUri) { - Ethscription storage etsc = ethscriptions[txHash]; - bytes memory content = getEthscriptionContent(txHash); - return etsc.getMediaUri(content); + function getMediaUri(bytes32 ethscriptionId) external view returns (string memory mediaType, string memory mediaUri) { + Ethscription storage ethscription = _getEthscriptionOrRevert(ethscriptionId); + bytes memory content = getEthscriptionContent(ethscriptionId); + return ethscription.getMediaUri(content); } // -------------------- Data Retrieval -------------------- /// @notice Get ethscription details (returns struct to avoid stack too deep) - function getEthscription(bytes32 transactionHash) external view requireExists(transactionHash) returns (Ethscription memory) { - return ethscriptions[transactionHash]; + function getEthscription(bytes32 ethscriptionId) external view returns (Ethscription memory) { + return _getEthscriptionOrRevert(ethscriptionId); } /// @notice Get content for an ethscription - function getEthscriptionContent(bytes32 txHash) public view requireExists(txHash) returns (bytes memory) { - Ethscription storage etsc = ethscriptions[txHash]; - address[] storage pointers = contentPointersBySha[etsc.contentSha]; + function getEthscriptionContent(bytes32 ethscriptionId) public view returns (bytes memory) { + Ethscription storage ethscription = _getEthscriptionOrRevert(ethscriptionId); + address[] storage pointers = contentPointersBySha[ethscription.contentSha]; // Empty content is valid - returns "" for empty pointers array return pointers.read(); } /// @notice Get ethscription details and content in a single call - /// @param txHash The transaction hash to look up + /// @param ethscriptionId The ethscription ID to look up /// @return ethscription The ethscription struct /// @return content The content bytes - function getEthscriptionWithContent(bytes32 txHash) external view requireExists(txHash) returns (Ethscription memory ethscription, bytes memory content) { - ethscription = ethscriptions[txHash]; - address[] storage pointers = contentPointersBySha[ethscription.contentSha]; - // Empty content is valid - returns "" for empty pointers array - content = pointers.read(); + function getEthscriptionWithContent(bytes32 ethscriptionId) external view returns (Ethscription memory ethscription, bytes memory content) { + ethscription = _getEthscriptionOrRevert(ethscriptionId); + content = getEthscriptionContent(ethscriptionId); + } + + /// @notice Overload: get ethscription by tokenId + function getEthscription(uint256 tokenId) external view returns (Ethscription memory) { + Ethscription storage ethscription = _getEthscriptionOrRevert(tokenId); + return ethscription; + } + + /// @notice Overload: get content by tokenId + function getEthscriptionContent(uint256 tokenId) external view returns (bytes memory) { + // Ensure it exists + _getEthscriptionOrRevert(tokenId); + bytes32 id = tokenIdToEthscriptionId[tokenId]; + return getEthscriptionContent(id); + } + + /// @notice Overload: get struct and content by tokenId + function getEthscriptionWithContent(uint256 tokenId) external view returns (Ethscription memory ethscription, bytes memory content) { + ethscription = _getEthscriptionOrRevert(tokenId); + bytes32 id = tokenIdToEthscriptionId[tokenId]; + content = getEthscriptionContent(id); } // ---------------- Ownership & Existence Checks ---------------- /// @notice Check if an ethscription exists - /// @param transactionHash The transaction hash to check + /// @param ethscriptionId The ethscription ID to check /// @return true if the ethscription exists - function exists(bytes32 transactionHash) external view returns (bool) { - return _ethscriptionExists(transactionHash); + function exists(bytes32 ethscriptionId) external view returns (bool) { + return _ethscriptionExists(ethscriptionId); } function exists(uint256 tokenId) external view returns (bool) { - return _ethscriptionExists(tokenIdToTransactionHash[tokenId]); + return _ethscriptionExists(tokenIdToEthscriptionId[tokenId]); } /// @notice Get owner of an ethscription by transaction hash /// @dev Overload of ownerOf that accepts transaction hash instead of token ID - function ownerOf(bytes32 transactionHash) external view requireExists(transactionHash) returns (address) { - Ethscription storage etsc = ethscriptions[transactionHash]; - uint256 tokenId = etsc.ethscriptionNumber; + function ownerOf(bytes32 ethscriptionId) external view returns (address) { + Ethscription storage ethscription = _getEthscriptionOrRevert(ethscriptionId); + uint256 tokenId = ethscription.ethscriptionNumber; return ownerOf(tokenId); } /// @notice Get the token ID (ethscription number) for a given transaction hash - /// @param transactionHash The transaction hash to look up + /// @param ethscriptionId The ethscription ID to look up /// @return The token ID (ethscription number) - function getTokenId(bytes32 transactionHash) external view requireExists(transactionHash) returns (uint256) { - return ethscriptions[transactionHash].ethscriptionNumber; + function getTokenId(bytes32 ethscriptionId) external view returns (uint256) { + Ethscription storage ethscription = _getEthscriptionOrRevert(ethscriptionId); + return ethscription.ethscriptionNumber; + } + + /// @notice Get the ethscription ID (bytes32) for a given tokenId + /// @dev Reverts if tokenId does not exist + function getEthscriptionId(uint256 tokenId) external view returns (bytes32) { + bytes32 id = tokenIdToEthscriptionId[tokenId]; + if (!_ethscriptionExists(id)) revert TokenDoesNotExist(); + return id; } // ============================================================= @@ -420,41 +453,41 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { /// @dev Override _update to track previous owner and handle token transfers function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address from) { - // Find the transaction hash for this token ID (ethscription number) - bytes32 txHash = tokenIdToTransactionHash[tokenId]; - Ethscription storage etsc = ethscriptions[txHash]; + // Find the ethscription ID for this token ID (ethscription number) + bytes32 id = tokenIdToEthscriptionId[tokenId]; + Ethscription storage ethscription = ethscriptions[id]; // Call parent implementation first to handle the actual update from = super._update(to, tokenId, auth); if (from == address(0)) { // Mint: emit once when minted directly to initial owner - if (to == etsc.initialOwner) { - emit EthscriptionTransferred(txHash, etsc.creator, to, tokenId); + if (to == ethscription.initialOwner) { + emit EthscriptionTransferred(id, ethscription.creator, to, tokenId); } // no previousOwner update or tokenManager call on mint } else { // Transfers (including creator -> address(0)) - emit EthscriptionTransferred(txHash, from, to, tokenId); - etsc.previousOwner = from; + emit EthscriptionTransferred(id, from, to, tokenId); + ethscription.previousOwner = from; // Notify protocol handler about the transfer if this ethscription has a protocol - _notifyProtocolTransfer(txHash, from, to); + _notifyProtocolTransfer(id, from, to); } // Queue ethscription for batch proving at block boundary once proving is live - _queueForProving(txHash); + _queueForProving(id); } /// @notice Check if an ethscription exists /// @dev An ethscription exists if it has been created (has a creator set) - /// @param transactionHash The transaction hash to check + /// @param ethscriptionId The ethscription ID to check /// @return True if the ethscription exists - function _ethscriptionExists(bytes32 transactionHash) internal view returns (bool) { + function _ethscriptionExists(bytes32 ethscriptionId) internal view returns (bool) { // Check if this ethscription has been created // We can't use _tokenExists here because we need the tokenId first // Instead, check if creator is set (ethscriptions are never created with zero creator) - return ethscriptions[transactionHash].creator != address(0); + return ethscriptions[ethscriptionId].creator != address(0); } /// @notice Internal helper to store content and return its SHA @@ -484,17 +517,17 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { return contentSha; } - function _queueForProving(bytes32 txHash) internal { + function _queueForProving(bytes32 ethscriptionId) internal { if (block.timestamp >= Constants.historicalBackfillApproxDoneAt) { - prover.queueEthscription(txHash); + prover.queueEthscription(ethscriptionId); } } /// @notice Call a protocol handler operation during ethscription creation - /// @param txHash The ethscription transaction hash + /// @param ethscriptionId The ethscription ID (L1 tx hash) /// @param protocolParams The protocol parameters struct function _callProtocolOperation( - bytes32 txHash, + bytes32 ethscriptionId, ProtocolParams calldata protocolParams ) internal { // Skip if no protocol specified @@ -503,7 +536,7 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { } // Track which protocol this ethscription uses - protocolOf[txHash] = protocolParams.protocolName; + protocolOf[ethscriptionId] = protocolParams.protocolName; address handler = protocolHandlers[protocolParams.protocolName]; @@ -515,7 +548,7 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { // Encode the function call with operation name bytes memory callData = abi.encodeWithSignature( string.concat("op_", protocolParams.operation, "(bytes32,bytes)"), - txHash, + ethscriptionId, protocolParams.data ); @@ -523,22 +556,22 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { (bool success, bytes memory returnData) = handler.call(callData); if (!success) { - emit ProtocolHandlerFailed(txHash, protocolParams.protocolName, returnData); + emit ProtocolHandlerFailed(ethscriptionId, protocolParams.protocolName, returnData); } else { - emit ProtocolHandlerSuccess(txHash, protocolParams.protocolName, returnData); + emit ProtocolHandlerSuccess(ethscriptionId, protocolParams.protocolName, returnData); } } /// @notice Notify protocol handler about an ethscription transfer - /// @param txHash The ethscription transaction hash + /// @param ethscriptionId The ethscription ID (L1 tx hash) /// @param from The address transferring from /// @param to The address transferring to function _notifyProtocolTransfer( - bytes32 txHash, + bytes32 ethscriptionId, address from, address to ) internal { - string memory protocol = protocolOf[txHash]; + string memory protocol = protocolOf[ethscriptionId]; // Skip if no protocol assigned if (bytes(protocol).length == 0) { @@ -553,11 +586,11 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { } // Use try/catch for cleaner error handling - try IProtocolHandler(handler).onTransfer(txHash, from, to) { + try IProtocolHandler(handler).onTransfer(ethscriptionId, from, to) { // onTransfer doesn't return data, so pass empty bytes - emit ProtocolHandlerSuccess(txHash, protocol, ""); + emit ProtocolHandlerSuccess(ethscriptionId, protocol, ""); } catch (bytes memory revertData) { - emit ProtocolHandlerFailed(txHash, protocol, revertData); + emit ProtocolHandlerFailed(ethscriptionId, protocol, revertData); } } @@ -573,48 +606,48 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable { // Emit events in the order they were created (FIFO) for (uint256 i = 0; i < count; i++) { - bytes32 txHash = pendingGenesisEvents[i]; + bytes32 ethscriptionId = pendingGenesisEvents[i]; // Get the ethscription data - Ethscription storage etsc = ethscriptions[txHash]; - uint256 tokenId = etsc.ethscriptionNumber; + Ethscription storage ethscription = ethscriptions[ethscriptionId]; + uint256 tokenId = ethscription.ethscriptionNumber; // Emit events in the same order as live mints: // 1. Transfer (mint), 2. EthscriptionTransferred, 3. EthscriptionCreated - if (etsc.initialOwner == address(0)) { + if (ethscription.initialOwner == address(0)) { // Token was minted to creator then burned // First emit mint to creator - emit Transfer(address(0), etsc.creator, tokenId); + emit Transfer(address(0), ethscription.creator, tokenId); // Then emit burn from creator to null address - emit Transfer(etsc.creator, address(0), tokenId); + emit Transfer(ethscription.creator, address(0), tokenId); // Emit Ethscriptions transfer event for the burn emit EthscriptionTransferred( - txHash, - etsc.creator, + ethscriptionId, + ethscription.creator, address(0), - etsc.ethscriptionNumber + ethscription.ethscriptionNumber ); } else { // Token was minted directly to initial owner - emit Transfer(address(0), etsc.initialOwner, tokenId); + emit Transfer(address(0), ethscription.initialOwner, tokenId); // Emit Ethscriptions transfer event emit EthscriptionTransferred( - txHash, - etsc.creator, - etsc.initialOwner, - etsc.ethscriptionNumber + ethscriptionId, + ethscription.creator, + ethscription.initialOwner, + ethscription.ethscriptionNumber ); } // Finally emit the creation event (matching the order of live mints) emit EthscriptionCreated( - txHash, - etsc.creator, - etsc.initialOwner, - etsc.contentUriHash, - etsc.contentSha, - etsc.ethscriptionNumber + ethscriptionId, + ethscription.creator, + ethscription.initialOwner, + ethscription.contentUriHash, + ethscription.contentSha, + ethscription.ethscriptionNumber ); } diff --git a/contracts/src/EthscriptionsERC20.sol b/contracts/src/EthscriptionsERC20.sol index 25afc47..7a4bf6a 100644 --- a/contracts/src/EthscriptionsERC20.sol +++ b/contracts/src/EthscriptionsERC20.sol @@ -20,8 +20,8 @@ contract EthscriptionsERC20 is ERC20NullOwnerCappedUpgradeable { // STATE VARIABLES // ============================================================= - /// @notice The ethscription hash that deployed this token - bytes32 public deployTxHash; + /// @notice The ethscription ID that deployed this token + bytes32 public deployEthscriptionId; // ============================================================= // CUSTOM ERRORS @@ -48,16 +48,16 @@ contract EthscriptionsERC20 is ERC20NullOwnerCappedUpgradeable { /// @param name_ The token name /// @param symbol_ The token symbol /// @param cap_ The maximum supply cap (in 18 decimals) - /// @param deployTxHash_ The ethscription hash that deployed this token + /// @param deployEthscriptionId_ The ethscription ID that deployed this token function initialize( string memory name_, string memory symbol_, uint256 cap_, - bytes32 deployTxHash_ + bytes32 deployEthscriptionId_ ) external initializer { __ERC20_init(name_, symbol_); __ERC20Capped_init(cap_); - deployTxHash = deployTxHash_; + deployEthscriptionId = deployEthscriptionId_; } /// @notice Mint tokens (TokenManager only) @@ -98,4 +98,4 @@ contract EthscriptionsERC20 is ERC20NullOwnerCappedUpgradeable { function approve(address, uint256) public pure override returns (bool) { revert ApprovalsNotAllowed(); } -} \ No newline at end of file +} diff --git a/contracts/src/EthscriptionsProver.sol b/contracts/src/EthscriptionsProver.sol index 09acd17..5346520 100644 --- a/contracts/src/EthscriptionsProver.sol +++ b/contracts/src/EthscriptionsProver.sol @@ -27,7 +27,7 @@ contract EthscriptionsProver { /// @notice Struct for ethscription data proof struct EthscriptionDataProof { - bytes32 ethscriptionTxHash; + bytes32 ethscriptionId; bytes32 contentSha; bytes32 contentUriHash; bytes32 l1BlockHash; @@ -78,7 +78,7 @@ contract EthscriptionsProver { /// @notice Emitted when an ethscription data proof is sent to L1 event EthscriptionDataProofSent( - bytes32 indexed ethscriptionTxHash, + bytes32 indexed ethscriptionId, uint256 indexed l2BlockNumber, uint256 l2Timestamp ); @@ -89,16 +89,16 @@ contract EthscriptionsProver { /// @notice Queue an ethscription for proving /// @dev Only callable by the Ethscriptions contract - /// @param txHash The transaction hash of the ethscription - function queueEthscription(bytes32 txHash) external virtual { + /// @param ethscriptionId The ID of the ethscription (L1 tx hash) + function queueEthscription(bytes32 ethscriptionId) external virtual { if (msg.sender != address(ethscriptions)) revert OnlyEthscriptions(); // Add to the set (deduplicates automatically) - if (queuedEthscriptions.add(txHash)) { - // Only store info if this is the first time we're queueing this txHash + if (queuedEthscriptions.add(ethscriptionId)) { + // Only store info if this is the first time we're queueing this ID // Capture the L1 block hash and number at the time of queuing L1Block l1Block = L1Block(L1_BLOCK); - queuedProofInfo[txHash] = QueuedProof({ + queuedProofInfo[ethscriptionId] = QueuedProof({ l1BlockHash: l1Block.hash(), l2BlockNumber: uint48(block.number), l2BlockTimestamp: uint48(block.timestamp), @@ -117,14 +117,14 @@ contract EthscriptionsProver { // Process and remove each ethscription from the set // We iterate backwards to avoid index shifting during removal for (uint256 i = count; i > 0; i--) { - bytes32 txHash = queuedEthscriptions.at(i - 1); + bytes32 ethscriptionId = queuedEthscriptions.at(i - 1); // Create and send proof for current state with stored block info - _createAndSendProof(txHash, queuedProofInfo[txHash]); + _createAndSendProof(ethscriptionId, queuedProofInfo[ethscriptionId]); // Clean up: remove from set and delete the proof info - queuedEthscriptions.remove(txHash); - delete queuedProofInfo[txHash]; + queuedEthscriptions.remove(ethscriptionId); + delete queuedProofInfo[ethscriptionId]; } } @@ -133,24 +133,24 @@ contract EthscriptionsProver { // ============================================================= /// @notice Internal function to create and send proof for an ethscription - /// @param ethscriptionTxHash The transaction hash of the ethscription + /// @param ethscriptionId The Ethscription ID (L1 tx hash) /// @param proofInfo The queued proof info containing block data - function _createAndSendProof(bytes32 ethscriptionTxHash, QueuedProof memory proofInfo) internal { + function _createAndSendProof(bytes32 ethscriptionId, QueuedProof memory proofInfo) internal { // Get ethscription data including previous owner - Ethscriptions.Ethscription memory etsc = ethscriptions.getEthscription(ethscriptionTxHash); - address currentOwner = ethscriptions.ownerOf(ethscriptionTxHash); + Ethscriptions.Ethscription memory ethscription = ethscriptions.getEthscription(ethscriptionId); + address currentOwner = ethscriptions.ownerOf(ethscriptionId); // Create proof struct with all ethscription data EthscriptionDataProof memory proof = EthscriptionDataProof({ - ethscriptionTxHash: ethscriptionTxHash, - contentSha: etsc.contentSha, - contentUriHash: etsc.contentUriHash, + ethscriptionId: ethscriptionId, + contentSha: ethscription.contentSha, + contentUriHash: ethscription.contentUriHash, l1BlockHash: proofInfo.l1BlockHash, - creator: etsc.creator, + creator: ethscription.creator, currentOwner: currentOwner, - previousOwner: etsc.previousOwner, - esip6: etsc.esip6, - ethscriptionNumber: etsc.ethscriptionNumber, + previousOwner: ethscription.previousOwner, + esip6: ethscription.esip6, + ethscriptionNumber: ethscription.ethscriptionNumber, l1BlockNumber: proofInfo.l1BlockNumber, l2BlockNumber: proofInfo.l2BlockNumber, l2Timestamp: proofInfo.l2BlockTimestamp @@ -160,6 +160,6 @@ contract EthscriptionsProver { bytes memory proofData = abi.encode(proof); L2_TO_L1_MESSAGE_PASSER.initiateWithdrawal(address(0), 0, proofData); - emit EthscriptionDataProofSent(ethscriptionTxHash, proofInfo.l2BlockNumber, proofInfo.l2BlockTimestamp); + emit EthscriptionDataProofSent(ethscriptionId, proofInfo.l2BlockNumber, proofInfo.l2BlockTimestamp); } -} \ No newline at end of file +} diff --git a/contracts/src/TokenManager.sol b/contracts/src/TokenManager.sol index 0f582ab..da23a31 100644 --- a/contracts/src/TokenManager.sol +++ b/contracts/src/TokenManager.sol @@ -21,7 +21,7 @@ contract TokenManager is IProtocolHandler { struct TokenInfo { address tokenContract; - bytes32 deployTxHash; + bytes32 deployEthscriptionId; string tick; uint256 maxSupply; uint256 mintAmount; @@ -29,8 +29,8 @@ contract TokenManager is IProtocolHandler { } struct TokenItem { - bytes32 deployTxHash; // Which token this ethscription belongs to - uint256 amount; // How many tokens this ethscription represents + bytes32 deployEthscriptionId; // Which token this ethscription belongs to + uint256 amount; // How many tokens this ethscription represents } // Protocol operation structs for cleaner decoding @@ -60,8 +60,8 @@ contract TokenManager is IProtocolHandler { /// @dev Track deployed tokens by protocol+tick for find-or-create mapping(bytes32 => TokenInfo) internal tokensByTick; // keccak256(abi.encode(protocol, tick)) => TokenInfo - /// @dev Map deploy transaction hash to tick key for lookups - mapping(bytes32 => bytes32) public deployToTick; // deployTxHash => tickKey + /// @dev Map deploy ethscription ID to tick key for lookups + mapping(bytes32 => bytes32) public deployToTick; // deployEthscriptionId => tickKey /// @dev Track which ethscription is a token item mapping(bytes32 => TokenItem) internal tokenItems; // ethscription tx hash => TokenItem @@ -84,7 +84,7 @@ contract TokenManager is IProtocolHandler { // ============================================================= event TokenDeployed( - bytes32 indexed deployTxHash, + bytes32 indexed deployEthscriptionId, address indexed tokenAddress, string tick, uint256 maxSupply, @@ -92,18 +92,18 @@ contract TokenManager is IProtocolHandler { ); event TokenMinted( - bytes32 indexed deployTxHash, + bytes32 indexed deployEthscriptionId, address indexed to, uint256 amount, - bytes32 ethscriptionTxHash + bytes32 ethscriptionId ); event TokenTransferred( - bytes32 indexed deployTxHash, + bytes32 indexed deployEthscriptionId, address indexed from, address indexed to, uint256 amount, - bytes32 ethscriptionTxHash + bytes32 ethscriptionId ); // ============================================================= @@ -120,9 +120,9 @@ contract TokenManager is IProtocolHandler { // ============================================================= /// @notice Handle deploy operation - /// @param txHash The ethscription transaction hash + /// @param ethscriptionId The ethscription ID /// @param data The encoded DeployOperation data - function op_deploy(bytes32 txHash, bytes calldata data) external virtual onlyEthscriptions { + function op_deploy(bytes32 ethscriptionId, bytes calldata data) external virtual onlyEthscriptions { // Decode the operation data DeployOperation memory deployOp = abi.decode(data, (DeployOperation)); @@ -130,7 +130,7 @@ contract TokenManager is IProtocolHandler { TokenInfo storage token = tokensByTick[tickKey]; // Revert if token already exists - if (token.deployTxHash != bytes32(0)) revert TokenAlreadyDeployed(); + if (token.deployEthscriptionId != bytes32(0)) revert TokenAlreadyDeployed(); // Validate deployment parameters if (deployOp.maxSupply == 0) revert InvalidMaxSupply(); @@ -150,29 +150,29 @@ contract TokenManager is IProtocolHandler { name, symbol, deployOp.maxSupply * 10**18, - txHash + ethscriptionId ); // Store token info tokensByTick[tickKey] = TokenInfo({ tokenContract: tokenAddress, - deployTxHash: txHash, + deployEthscriptionId: ethscriptionId, tick: deployOp.tick, maxSupply: deployOp.maxSupply, mintAmount: deployOp.mintAmount, totalMinted: 0 }); - // Map deploy hash to tick key for lookups - deployToTick[txHash] = tickKey; + // Map deploy ID to tick key for lookups + deployToTick[ethscriptionId] = tickKey; - emit TokenDeployed(txHash, tokenAddress, deployOp.tick, deployOp.maxSupply, deployOp.mintAmount); + emit TokenDeployed(ethscriptionId, tokenAddress, deployOp.tick, deployOp.maxSupply, deployOp.mintAmount); } /// @notice Handle mint operation - /// @param txHash The ethscription transaction hash + /// @param ethscriptionId The ethscription ID /// @param data The encoded MintOperation data - function op_mint(bytes32 txHash, bytes calldata data) external virtual onlyEthscriptions { + function op_mint(bytes32 ethscriptionId, bytes calldata data) external virtual onlyEthscriptions { // Decode the operation data MintOperation memory mintOp = abi.decode(data, (MintOperation)); @@ -180,7 +180,7 @@ contract TokenManager is IProtocolHandler { TokenInfo storage token = tokensByTick[tickKey]; // Token must exist to mint - if (token.deployTxHash == bytes32(0)) revert TokenNotDeployed(); + if (token.deployEthscriptionId == bytes32(0)) revert TokenNotDeployed(); // Validate mint amount matches token's configured limit if (mintOp.amount != token.mintAmount) revert MintAmountMismatch(); @@ -192,12 +192,12 @@ contract TokenManager is IProtocolHandler { // Get the initial owner from the Ethscriptions contract Ethscriptions ethscriptionsContract = Ethscriptions(ethscriptions); - Ethscriptions.Ethscription memory ethscription = ethscriptionsContract.getEthscription(txHash); + Ethscriptions.Ethscription memory ethscription = ethscriptionsContract.getEthscription(ethscriptionId); address initialOwner = ethscription.initialOwner; // Track this ethscription as a token item - tokenItems[txHash] = TokenItem({ - deployTxHash: token.deployTxHash, + tokenItems[ethscriptionId] = TokenItem({ + deployEthscriptionId: token.deployEthscriptionId, amount: mintOp.amount }); @@ -207,42 +207,42 @@ contract TokenManager is IProtocolHandler { // Update total minted after successful mint token.totalMinted += mintOp.amount; - emit TokenMinted(token.deployTxHash, initialOwner, mintOp.amount, txHash); + emit TokenMinted(token.deployEthscriptionId, initialOwner, mintOp.amount, ethscriptionId); } /// @notice Handle transfer notification from Ethscriptions contract /// @dev Implementation of IProtocolHandler interface - /// @param txHash The ethscription transaction hash being transferred + /// @param ethscriptionId The ethscription ID being transferred /// @param from The address transferring from /// @param to The address transferring to function onTransfer( - bytes32 txHash, + bytes32 ethscriptionId, address from, address to ) external virtual override onlyEthscriptions { - TokenItem memory item = tokenItems[txHash]; + TokenItem memory item = tokenItems[ethscriptionId]; // Not a token item, nothing to do - if (item.deployTxHash == bytes32(0)) return; + if (item.deployEthscriptionId == bytes32(0)) return; - bytes32 tickKey = deployToTick[item.deployTxHash]; + bytes32 tickKey = deployToTick[item.deployEthscriptionId]; TokenInfo storage token = tokensByTick[tickKey]; // Force transfer tokens (shadow transfer) - convert to 18 decimals EthscriptionsERC20(token.tokenContract).forceTransfer(from, to, item.amount * 10**18); - emit TokenTransferred(item.deployTxHash, from, to, item.amount, txHash); + emit TokenTransferred(item.deployEthscriptionId, from, to, item.amount, ethscriptionId); } // ============================================================= // EXTERNAL VIEW FUNCTIONS // ============================================================= - /// @notice Get token contract address by deploy transaction hash - /// @param deployTxHash The deployment transaction hash + /// @notice Get token contract address by deploy ethscription ID + /// @param deployEthscriptionId The deployment ethscription ID /// @return The token contract address - function getTokenAddress(bytes32 deployTxHash) external view returns (address) { - bytes32 tickKey = deployToTick[deployTxHash]; + function getTokenAddress(bytes32 deployEthscriptionId) external view returns (address) { + bytes32 tickKey = deployToTick[deployEthscriptionId]; return tokensByTick[tickKey].tokenContract; } @@ -254,11 +254,11 @@ contract TokenManager is IProtocolHandler { return tokensByTick[tickKey].tokenContract; } - /// @notice Get complete token information by deploy transaction hash - /// @param deployTxHash The deployment transaction hash + /// @notice Get complete token information by deploy ethscription ID + /// @param deployEthscriptionId The deployment ethscription ID /// @return The TokenInfo struct - function getTokenInfo(bytes32 deployTxHash) external view returns (TokenInfo memory) { - bytes32 tickKey = deployToTick[deployTxHash]; + function getTokenInfo(bytes32 deployEthscriptionId) external view returns (TokenInfo memory) { + bytes32 tickKey = deployToTick[deployEthscriptionId]; return tokensByTick[tickKey]; } @@ -286,24 +286,24 @@ contract TokenManager is IProtocolHandler { } /// @notice Check if an ethscription is a token item - /// @param ethscriptionTxHash The ethscription transaction hash + /// @param ethscriptionId The ethscription ID /// @return True if the ethscription represents tokens - function isTokenItem(bytes32 ethscriptionTxHash) external view returns (bool) { - return tokenItems[ethscriptionTxHash].deployTxHash != bytes32(0); + function isTokenItem(bytes32 ethscriptionId) external view returns (bool) { + return tokenItems[ethscriptionId].deployEthscriptionId != bytes32(0); } /// @notice Get token amount for an ethscription - /// @param ethscriptionTxHash The ethscription transaction hash + /// @param ethscriptionId The ethscription ID /// @return The amount of tokens this ethscription represents - function getTokenAmount(bytes32 ethscriptionTxHash) external view returns (uint256) { - return tokenItems[ethscriptionTxHash].amount; + function getTokenAmount(bytes32 ethscriptionId) external view returns (uint256) { + return tokenItems[ethscriptionId].amount; } /// @notice Get complete token item information - /// @param ethscriptionTxHash The ethscription transaction hash + /// @param ethscriptionId The ethscription ID /// @return The TokenItem struct - function getTokenItem(bytes32 ethscriptionTxHash) external view returns (TokenItem memory) { - return tokenItems[ethscriptionTxHash]; + function getTokenItem(bytes32 ethscriptionId) external view returns (TokenItem memory) { + return tokenItems[ethscriptionId]; } // ============================================================= @@ -327,4 +327,4 @@ contract TokenManager is IProtocolHandler { // Use the protocol name from this handler return keccak256(abi.encode("erc-20", tick)); } -} \ No newline at end of file +} diff --git a/contracts/src/interfaces/IProtocolHandler.sol b/contracts/src/interfaces/IProtocolHandler.sol index 2525452..a1bdbf0 100644 --- a/contracts/src/interfaces/IProtocolHandler.sol +++ b/contracts/src/interfaces/IProtocolHandler.sol @@ -6,11 +6,11 @@ pragma solidity 0.8.24; /// @dev Handlers process protocol-specific logic for Ethscriptions lifecycle events interface IProtocolHandler { /// @notice Called when an Ethscription with this protocol is transferred - /// @param txHash The transaction hash (Ethscription ID) + /// @param ethscriptionId The Ethscription ID (L1 tx hash) /// @param from The address transferring the Ethscription /// @param to The address receiving the Ethscription function onTransfer( - bytes32 txHash, + bytes32 ethscriptionId, address from, address to ) external; diff --git a/contracts/src/libraries/EthscriptionsRendererLib.sol b/contracts/src/libraries/EthscriptionsRendererLib.sol index f855689..4a8a067 100644 --- a/contracts/src/libraries/EthscriptionsRendererLib.sol +++ b/contracts/src/libraries/EthscriptionsRendererLib.sol @@ -13,17 +13,17 @@ library EthscriptionsRendererLib { /// @notice Build attributes JSON array from ethscription data /// @param etsc Storage pointer to the ethscription - /// @param txHash The transaction hash of the ethscription + /// @param ethscriptionId The ethscription ID (L1 tx hash) /// @return JSON string of attributes array - function buildAttributes(Ethscriptions.Ethscription storage etsc, bytes32 txHash) + function buildAttributes(Ethscriptions.Ethscription storage etsc, bytes32 ethscriptionId) internal view returns (string memory) { // Build in chunks to avoid stack too deep string memory part1 = string.concat( - '[{"trait_type":"Transaction Hash","value":"', - uint256(txHash).toHexString(), + '[{"trait_type":"Ethscription ID","value":"', + uint256(ethscriptionId).toHexString(), '"},{"trait_type":"Ethscription Number","display_type":"number","value":', etsc.ethscriptionNumber.toString(), '},{"trait_type":"Creator","value":"', @@ -84,19 +84,19 @@ library EthscriptionsRendererLib { /// @notice Build complete token URI JSON /// @param etsc Storage pointer to the ethscription - /// @param txHash The transaction hash of the ethscription + /// @param ethscriptionId The ethscription ID (L1 tx hash) /// @param content The content bytes /// @return The complete base64-encoded data URI function buildTokenURI( Ethscriptions.Ethscription storage etsc, - bytes32 txHash, + bytes32 ethscriptionId, bytes memory content ) internal view returns (string memory) { // Get media URI (string memory mediaType, string memory mediaUri) = getMediaUri(etsc, content); // Build attributes - string memory attributes = buildAttributes(etsc, txHash); + string memory attributes = buildAttributes(etsc, ethscriptionId); // Build JSON string memory json = string.concat( @@ -198,4 +198,4 @@ library EthscriptionsRendererLib { '' ); } -} \ No newline at end of file +} diff --git a/contracts/test/CollectionsManager.t.sol b/contracts/test/CollectionsManager.t.sol index f8d11bf..a7ffa56 100644 --- a/contracts/test/CollectionsManager.t.sol +++ b/contracts/test/CollectionsManager.t.sol @@ -41,7 +41,7 @@ contract CollectionsManagerTest is TestSetup { }); Ethscriptions.CreateEthscriptionParams memory params = Ethscriptions.CreateEthscriptionParams({ - transactionHash: COLLECTION_TX_HASH, + ethscriptionId: COLLECTION_TX_HASH, contentUriHash: sha256(bytes(collectionContent)), initialOwner: alice, content: bytes(collectionContent), @@ -121,7 +121,7 @@ contract CollectionsManagerTest is TestSetup { }); Ethscriptions.CreateEthscriptionParams memory itemParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: ITEM1_TX_HASH, + ethscriptionId: ITEM1_TX_HASH, contentUriHash: sha256(bytes(itemContent)), initialOwner: alice, content: bytes(itemContent), @@ -215,7 +215,7 @@ contract CollectionsManagerTest is TestSetup { }); Ethscriptions.CreateEthscriptionParams memory removeParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: bytes32(uint256(0xFEED)), + ethscriptionId: bytes32(uint256(0xFEED)), contentUriHash: sha256(bytes(removeContent)), initialOwner: alice, content: bytes(removeContent), @@ -256,7 +256,7 @@ contract CollectionsManagerTest is TestSetup { }); Ethscriptions.CreateEthscriptionParams memory removeParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: bytes32(uint256(0xBAD)), + ethscriptionId: bytes32(uint256(0xBAD)), contentUriHash: sha256(bytes("data:,remove")), initialOwner: bob, content: bytes("remove"), @@ -318,7 +318,7 @@ contract CollectionsManagerTest is TestSetup { }); Ethscriptions.CreateEthscriptionParams memory itemParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: itemHashes[i], + ethscriptionId: itemHashes[i], contentUriHash: sha256(abi.encodePacked("item", i)), initialOwner: owners[i], content: abi.encodePacked("item", i), @@ -393,7 +393,7 @@ contract CollectionsManagerTest is TestSetup { // Create the ethscription with image content Ethscriptions.CreateEthscriptionParams memory itemParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: ITEM1_TX_HASH, + ethscriptionId: ITEM1_TX_HASH, contentUriHash: sha256(bytes(imageContent)), initialOwner: alice, content: bytes(imageContent), @@ -462,7 +462,7 @@ contract CollectionsManagerTest is TestSetup { }); Ethscriptions.CreateEthscriptionParams memory editParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: bytes32(uint256(0xED171)), + ethscriptionId: bytes32(uint256(0xED171)), contentUriHash: sha256(bytes("edit")), initialOwner: alice, content: bytes("edit"), @@ -499,7 +499,7 @@ contract CollectionsManagerTest is TestSetup { // First create the ethscription that we'll add to the collection vm.prank(alice); Ethscriptions.CreateEthscriptionParams memory itemCreationParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: ITEM1_TX_HASH, + ethscriptionId: ITEM1_TX_HASH, contentUriHash: sha256(bytes("item content")), initialOwner: alice, content: bytes("item content"), @@ -535,7 +535,7 @@ contract CollectionsManagerTest is TestSetup { }); Ethscriptions.CreateEthscriptionParams memory addParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: bytes32(uint256(0xADD1733)), + ethscriptionId: bytes32(uint256(0xADD1733)), contentUriHash: sha256(bytes("add")), initialOwner: alice, content: bytes("add"), @@ -564,7 +564,7 @@ contract CollectionsManagerTest is TestSetup { }); Ethscriptions.CreateEthscriptionParams memory editParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: bytes32(uint256(0xED172)), + ethscriptionId: bytes32(uint256(0xED172)), contentUriHash: sha256(bytes("partial-edit")), initialOwner: alice, content: bytes("partial-edit"), @@ -607,7 +607,7 @@ contract CollectionsManagerTest is TestSetup { }); Ethscriptions.CreateEthscriptionParams memory editParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: bytes32(uint256(0xBADED17)), + ethscriptionId: bytes32(uint256(0xBADED17)), contentUriHash: sha256(bytes("bad-edit")), initialOwner: bob, content: bytes("bad-edit"), @@ -645,7 +645,7 @@ contract CollectionsManagerTest is TestSetup { }); Ethscriptions.CreateEthscriptionParams memory editParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: bytes32(uint256(0x901743)), + ethscriptionId: bytes32(uint256(0x901743)), contentUriHash: sha256(bytes("no-item")), initialOwner: alice, content: bytes("no-item"), @@ -694,7 +694,7 @@ contract CollectionsManagerTest is TestSetup { ethscriptionIds[0] = ITEM1_TX_HASH; Ethscriptions.CreateEthscriptionParams memory syncParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: bytes32(uint256(0x5914C)), + ethscriptionId: bytes32(uint256(0x5914C)), contentUriHash: sha256(bytes("sync")), initialOwner: charlie, content: bytes("sync"), @@ -741,7 +741,7 @@ contract CollectionsManagerTest is TestSetup { ethscriptionIds[1] = ITEM2_TX_HASH; Ethscriptions.CreateEthscriptionParams memory syncParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: bytes32(uint256(0x5914CD)), + ethscriptionId: bytes32(uint256(0x5914CD)), contentUriHash: sha256(bytes("sync-multi")), initialOwner: alice, content: bytes("sync-multi"), @@ -771,7 +771,7 @@ contract CollectionsManagerTest is TestSetup { ethscriptionIds[0] = bytes32(uint256(0x999999)); // Non-existent in collection Ethscriptions.CreateEthscriptionParams memory syncParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: bytes32(uint256(0x5914CE)), + ethscriptionId: bytes32(uint256(0x5914CE)), contentUriHash: sha256(bytes("sync-nonexistent")), initialOwner: alice, content: bytes("sync-nonexistent"), @@ -797,7 +797,7 @@ contract CollectionsManagerTest is TestSetup { ethscriptionIds[0] = ITEM1_TX_HASH; Ethscriptions.CreateEthscriptionParams memory syncParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: bytes32(uint256(0x5914CF)), + ethscriptionId: bytes32(uint256(0x5914CF)), contentUriHash: sha256(bytes("sync-fake")), initialOwner: alice, content: bytes("sync-fake"), @@ -825,7 +825,7 @@ contract CollectionsManagerTest is TestSetup { vm.prank(alice); Ethscriptions.CreateEthscriptionParams memory lockParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: bytes32(uint256(0x10CCC)), + ethscriptionId: bytes32(uint256(0x10CCC)), contentUriHash: sha256(bytes("lock")), initialOwner: alice, content: bytes("lock"), @@ -854,7 +854,7 @@ contract CollectionsManagerTest is TestSetup { }); Ethscriptions.CreateEthscriptionParams memory editParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: bytes32(uint256(0x10C3ED)), + ethscriptionId: bytes32(uint256(0x10C3ED)), contentUriHash: sha256(bytes("locked-edit")), initialOwner: alice, content: bytes("locked-edit"), @@ -874,4 +874,4 @@ contract CollectionsManagerTest is TestSetup { CollectionsManager.CollectionItem memory item = collectionsManager.getCollectionItem(COLLECTION_TX_HASH, 0); assertEq(item.name, "Test Item #0"); // Original name preserved } -} \ No newline at end of file +} diff --git a/contracts/test/CollectionsProtocol.t.sol b/contracts/test/CollectionsProtocol.t.sol index ea0d4e4..86419f3 100644 --- a/contracts/test/CollectionsProtocol.t.sol +++ b/contracts/test/CollectionsProtocol.t.sol @@ -39,7 +39,7 @@ contract CollectionsProtocolTest is TestSetup { // Use the getter functions instead of direct mapping access CollectionsManager.CollectionState memory state = collectionsManager.getCollectionState(collectionId); assertNotEq(state.collectionContract, address(0), "Collection contract should be deployed"); - assertEq(state.createTxHash, txHash, "Create tx hash should match"); + assertEq(state.createEthscriptionId, txHash, "Create ethscription ID should match"); assertEq(state.currentSize, 0, "Initial size should be 0"); assertEq(state.locked, false, "Should not be locked"); @@ -77,7 +77,7 @@ contract CollectionsProtocolTest is TestSetup { bytes32 txHash = keccak256(abi.encodePacked("test_collection_tx", block.timestamp)); Ethscriptions.CreateEthscriptionParams memory params = Ethscriptions.CreateEthscriptionParams({ - transactionHash: txHash, + ethscriptionId: txHash, contentUriHash: keccak256(bytes(json)), initialOwner: alice, content: bytes(json), @@ -105,7 +105,7 @@ contract CollectionsProtocolTest is TestSetup { // Verify the collection was created assertTrue(state.collectionContract != address(0), "Collection should exist"); - assertEq(state.createTxHash, txHash); + assertEq(state.createEthscriptionId, txHash); assertEq(state.currentSize, 0); assertEq(state.locked, false); @@ -159,10 +159,10 @@ contract CollectionsProtocolTest is TestSetup { CollectionsManager.CollectionState memory state = abi.decode(result, (CollectionsManager.CollectionState)); assertTrue(state.collectionContract != address(0), "Should have collection contract"); - assertEq(state.createTxHash, txHash); + assertEq(state.createEthscriptionId, txHash); assertEq(state.currentSize, 0); assertEq(state.locked, false); console.log("Successfully read collection state via eth_call!"); } -} \ No newline at end of file +} diff --git a/contracts/test/ERC721Enumerable.t.sol b/contracts/test/ERC721Enumerable.t.sol index 2a48ed7..8ba6647 100644 --- a/contracts/test/ERC721Enumerable.t.sol +++ b/contracts/test/ERC721Enumerable.t.sol @@ -18,7 +18,7 @@ contract ERC721EnumerableTest is TestSetup { vm.prank(creator); return ethscriptions.createEthscription( Ethscriptions.CreateEthscriptionParams({ - transactionHash: txHash, + ethscriptionId: txHash, contentUriHash: keccak256(bytes(content)), initialOwner: owner, content: bytes(content), @@ -144,4 +144,4 @@ contract ERC721EnumerableTest is TestSetup { ethscriptions.tokenOfOwnerByIndex(alice, 1); } } -} \ No newline at end of file +} diff --git a/contracts/test/EthscriptionsFailureHandling.t.sol b/contracts/test/EthscriptionsFailureHandling.t.sol index c693dc9..9d29151 100644 --- a/contracts/test/EthscriptionsFailureHandling.t.sol +++ b/contracts/test/EthscriptionsFailureHandling.t.sol @@ -99,7 +99,7 @@ contract EthscriptionsFailureHandlingTest is TestSetup { string memory dataUri = "data:,Hello World with failing token manager"; Ethscriptions.CreateEthscriptionParams memory params = Ethscriptions.CreateEthscriptionParams({ - transactionHash: txHash, + ethscriptionId: txHash, contentUriHash: sha256(bytes(dataUri)), initialOwner: address(this), content: bytes("Hello World with failing token manager"), @@ -181,7 +181,7 @@ contract EthscriptionsFailureHandlingTest is TestSetup { string memory dataUri = "data:,{\"p\":\"test\",\"op\":\"deploy\",\"tick\":\"FAIL\",\"max\":\"1000\",\"lim\":\"10\"}"; Ethscriptions.CreateEthscriptionParams memory params = Ethscriptions.CreateEthscriptionParams({ - transactionHash: txHash, + ethscriptionId: txHash, contentUriHash: sha256(bytes(dataUri)), initialOwner: address(this), content: bytes("{\"p\":\"test\",\"op\":\"deploy\",\"tick\":\"FAIL\",\"max\":\"1000\",\"lim\":\"10\"}"), @@ -226,4 +226,4 @@ contract EthscriptionsFailureHandlingTest is TestSetup { assertEq(ethscriptions.ownerOf(tokenId), address(this)); assertEq(ethscriptions.totalSupply(), 12); } -} \ No newline at end of file +} diff --git a/contracts/test/EthscriptionsMultiTransfer.t.sol b/contracts/test/EthscriptionsMultiTransfer.t.sol index b472a0a..32845fb 100644 --- a/contracts/test/EthscriptionsMultiTransfer.t.sol +++ b/contracts/test/EthscriptionsMultiTransfer.t.sol @@ -227,7 +227,7 @@ contract EthscriptionsMultiTransferTest is TestSetup { // Check all expected attributes string[10] memory expectedTraits = [ - "Transaction Hash", + "Ethscription ID", "Ethscription Number", "Creator", "Initial Owner", @@ -287,4 +287,4 @@ contract EthscriptionsMultiTransferTest is TestSetup { } return string(result); } -} \ No newline at end of file +} diff --git a/contracts/test/EthscriptionsProver.t.sol b/contracts/test/EthscriptionsProver.t.sol index aa485e4..ca39e50 100644 --- a/contracts/test/EthscriptionsProver.t.sol +++ b/contracts/test/EthscriptionsProver.t.sol @@ -68,7 +68,7 @@ contract EthscriptionsProverTest is TestSetup { (EthscriptionsProver.EthscriptionDataProof) ); - assertEq(decodedProof.ethscriptionTxHash, TEST_TX_HASH); + assertEq(decodedProof.ethscriptionId, TEST_TX_HASH); assertEq(decodedProof.creator, alice); // Creator should be alice due to vm.prank assertEq(decodedProof.currentOwner, bob); assertEq(decodedProof.previousOwner, alice); @@ -102,7 +102,7 @@ contract EthscriptionsProverTest is TestSetup { vm.startPrank(alice); ethscriptions.createEthscription( Ethscriptions.CreateEthscriptionParams({ - transactionHash: txHash1, + ethscriptionId: txHash1, contentUriHash: keccak256("data:,test1"), initialOwner: alice, content: bytes("test1"), @@ -116,7 +116,7 @@ contract EthscriptionsProverTest is TestSetup { vm.startPrank(bob); ethscriptions.createEthscription( Ethscriptions.CreateEthscriptionParams({ - transactionHash: txHash2, + ethscriptionId: txHash2, contentUriHash: keccak256("data:,test2"), initialOwner: bob, content: bytes("test2"), @@ -136,7 +136,7 @@ contract EthscriptionsProverTest is TestSetup { vm.startPrank(charlie); ethscriptions.createEthscription( Ethscriptions.CreateEthscriptionParams({ - transactionHash: txHash3, + ethscriptionId: txHash3, contentUriHash: keccak256("data:,test3"), initialOwner: charlie, content: bytes("test3"), diff --git a/contracts/test/EthscriptionsToken.t.sol b/contracts/test/EthscriptionsToken.t.sol index 0170a23..aa84666 100644 --- a/contracts/test/EthscriptionsToken.t.sol +++ b/contracts/test/EthscriptionsToken.t.sol @@ -46,7 +46,7 @@ contract EthscriptionsTokenTest is TestSetup { } return Ethscriptions.CreateEthscriptionParams({ - transactionHash: transactionHash, + ethscriptionId: transactionHash, contentUriHash: contentUriHash, initialOwner: initialOwner, content: content, diff --git a/contracts/test/EthscriptionsTokenParams.t.sol b/contracts/test/EthscriptionsTokenParams.t.sol index fb54a78..8046f34 100644 --- a/contracts/test/EthscriptionsTokenParams.t.sol +++ b/contracts/test/EthscriptionsTokenParams.t.sol @@ -19,7 +19,7 @@ contract EthscriptionsTokenParamsTest is TestSetup { }); Ethscriptions.CreateEthscriptionParams memory params = Ethscriptions.CreateEthscriptionParams({ - transactionHash: bytes32(uint256(1)), + ethscriptionId: bytes32(uint256(1)), contentUriHash: contentUriHash, initialOwner: address(this), content: bytes(tokenJson), @@ -39,7 +39,7 @@ contract EthscriptionsTokenParamsTest is TestSetup { assertEq(ethscriptions.totalSupply(), 12, "Should have created new ethscription"); // Get the ethscription data - Ethscriptions.Ethscription memory eth = ethscriptions.getEthscription(params.transactionHash); + Ethscriptions.Ethscription memory eth = ethscriptions.getEthscription(params.ethscriptionId); assertEq(eth.creator, address(this), "Creator should match"); assertEq(eth.initialOwner, address(this), "Initial owner should match"); } @@ -57,7 +57,7 @@ contract EthscriptionsTokenParamsTest is TestSetup { }); Ethscriptions.CreateEthscriptionParams memory params = Ethscriptions.CreateEthscriptionParams({ - transactionHash: bytes32(uint256(2)), + ethscriptionId: bytes32(uint256(2)), contentUriHash: contentUriHash, initialOwner: address(this), content: bytes(tokenJson), @@ -84,7 +84,7 @@ contract EthscriptionsTokenParamsTest is TestSetup { bytes32 contentUriHash = sha256(bytes(dataUri)); Ethscriptions.CreateEthscriptionParams memory params = Ethscriptions.CreateEthscriptionParams({ - transactionHash: bytes32(uint256(3)), + ethscriptionId: bytes32(uint256(3)), contentUriHash: contentUriHash, initialOwner: address(this), content: bytes(content), @@ -117,7 +117,7 @@ contract EthscriptionsTokenParamsTest is TestSetup { }); Ethscriptions.CreateEthscriptionParams memory deployParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: keccak256("deploy_tx"), + ethscriptionId: keccak256("deploy_tx"), contentUriHash: sha256(bytes(deployUri)), initialOwner: address(this), content: bytes(deployJson), @@ -143,7 +143,7 @@ contract EthscriptionsTokenParamsTest is TestSetup { }); Ethscriptions.CreateEthscriptionParams memory mintParams = Ethscriptions.CreateEthscriptionParams({ - transactionHash: keccak256("mint_tx"), + ethscriptionId: keccak256("mint_tx"), contentUriHash: sha256(bytes(mintUri)), initialOwner: address(this), content: bytes(mintJson), @@ -161,4 +161,4 @@ contract EthscriptionsTokenParamsTest is TestSetup { // Verify both were created assertEq(ethscriptions.totalSupply(), 13, "Should have 13 total (11 genesis + 2 new)"); } -} \ No newline at end of file +} diff --git a/contracts/test/EthscriptionsWithContent.t.sol b/contracts/test/EthscriptionsWithContent.t.sol index 59de4e4..3d40486 100644 --- a/contracts/test/EthscriptionsWithContent.t.sol +++ b/contracts/test/EthscriptionsWithContent.t.sol @@ -15,7 +15,7 @@ contract EthscriptionsWithContentTest is TestSetup { // Create the ethscription vm.prank(creator); Ethscriptions.CreateEthscriptionParams memory params = Ethscriptions.CreateEthscriptionParams({ - transactionHash: txHash, + ethscriptionId: txHash, contentUriHash: keccak256(bytes("data:text/plain,Hello, World!")), initialOwner: initialOwner, content: bytes(testContent), @@ -91,7 +91,7 @@ contract EthscriptionsWithContentTest is TestSetup { // Create the ethscription vm.prank(creator); Ethscriptions.CreateEthscriptionParams memory params = Ethscriptions.CreateEthscriptionParams({ - transactionHash: txHash, + ethscriptionId: txHash, contentUriHash: keccak256(bytes("data:application/octet-stream,")), initialOwner: initialOwner, content: largeContent, @@ -117,4 +117,4 @@ contract EthscriptionsWithContentTest is TestSetup { assertEq(ethscription.creator, creator); assertEq(ethscription.initialOwner, initialOwner); } -} \ No newline at end of file +} diff --git a/contracts/test/EthscriptionsWithTestFunctions.sol b/contracts/test/EthscriptionsWithTestFunctions.sol index 92c1fa2..f874888 100644 --- a/contracts/test/EthscriptionsWithTestFunctions.sol +++ b/contracts/test/EthscriptionsWithTestFunctions.sol @@ -12,27 +12,27 @@ contract EthscriptionsWithTestFunctions is Ethscriptions { /// @notice Get the number of content pointers for an ethscription /// @dev Test-only function to inspect storage chunks - function getContentPointerCount(bytes32 transactionHash) external view requireExists(transactionHash) returns (uint256) { - Ethscription storage etsc = ethscriptions[transactionHash]; - return contentPointersBySha[etsc.contentSha].length; + function getContentPointerCount(bytes32 ethscriptionId) external view returns (uint256) { + Ethscription storage ethscription = _getEthscriptionOrRevert(ethscriptionId); + return contentPointersBySha[ethscription.contentSha].length; } /// @notice Get all content pointers for an ethscription /// @dev Test-only function to inspect SSTORE2 addresses - function getContentPointers(bytes32 transactionHash) external view requireExists(transactionHash) returns (address[] memory) { - Ethscription storage etsc = ethscriptions[transactionHash]; - return contentPointersBySha[etsc.contentSha]; + function getContentPointers(bytes32 ethscriptionId) external view returns (address[] memory) { + Ethscription storage ethscription = _getEthscriptionOrRevert(ethscriptionId); + return contentPointersBySha[ethscription.contentSha]; } /// @notice Read a specific chunk of content /// @dev Test-only function to read individual SSTORE2 chunks - /// @param transactionHash The ethscription transaction hash + /// @param ethscriptionId The ethscription ID (L1 tx hash) /// @param index The chunk index to read /// @return The chunk data - function readChunk(bytes32 transactionHash, uint256 index) external view requireExists(transactionHash) returns (bytes memory) { - Ethscription storage etsc = ethscriptions[transactionHash]; - address[] storage pointers = contentPointersBySha[etsc.contentSha]; + function readChunk(bytes32 ethscriptionId, uint256 index) external view returns (bytes memory) { + Ethscription storage ethscription = _getEthscriptionOrRevert(ethscriptionId); + address[] storage pointers = contentPointersBySha[ethscription.contentSha]; require(index < pointers.length, "Chunk index out of bounds"); return SSTORE2.read(pointers[index]); } -} \ No newline at end of file +} diff --git a/contracts/test/GasDebug.t.sol b/contracts/test/GasDebug.t.sol index 7f587bd..f97b51f 100644 --- a/contracts/test/GasDebug.t.sol +++ b/contracts/test/GasDebug.t.sol @@ -38,7 +38,7 @@ contract GasDebugTest is TestSetup { console.log("Token ID created:", tokenId); // Verify it was created - Ethscriptions.Ethscription memory etsc = ethscriptions.getEthscription(params.transactionHash); + Ethscriptions.Ethscription memory etsc = ethscriptions.getEthscription(params.ethscriptionId); assertEq(etsc.creator, INITIAL_OWNER); assertEq(etsc.initialOwner, INITIAL_OWNER); assertEq(etsc.mimetype, "image/png"); @@ -89,4 +89,4 @@ contract StoreContentTester is Ethscriptions { ) external returns (bytes32) { return _storeContent(content); } -} \ No newline at end of file +} diff --git a/contracts/test/ProtocolRegistration.t.sol b/contracts/test/ProtocolRegistration.t.sol index f284887..3e34e43 100644 --- a/contracts/test/ProtocolRegistration.t.sol +++ b/contracts/test/ProtocolRegistration.t.sol @@ -112,7 +112,7 @@ contract ProtocolRegistrationTest is TestSetup { bytes32 txHash = bytes32(uint256(0x1234)); Ethscriptions.CreateEthscriptionParams memory params = Ethscriptions.CreateEthscriptionParams({ - transactionHash: txHash, + ethscriptionId: txHash, contentUriHash: sha256(bytes('data:,{"p":"mock-protocol","op":"test"}')), initialOwner: alice, content: bytes('{"p":"mock-protocol","op":"test"}'), @@ -156,7 +156,7 @@ contract ProtocolRegistrationTest is TestSetup { // Create ethscription with unregistered protocol Ethscriptions.CreateEthscriptionParams memory params = Ethscriptions.CreateEthscriptionParams({ - transactionHash: txHash, + ethscriptionId: txHash, contentUriHash: sha256(bytes('data:,{"p":"unregistered","op":"test"}')), initialOwner: alice, content: bytes('{"p":"unregistered","op":"test"}'), @@ -216,4 +216,4 @@ contract MockProtocolHandler is IProtocolHandler { function protocolName() external pure override returns (string memory) { return "mock-protocol"; } -} \ No newline at end of file +} diff --git a/contracts/test/TestImageSVGWrapper.t.sol b/contracts/test/TestImageSVGWrapper.t.sol index da15309..00b2e10 100644 --- a/contracts/test/TestImageSVGWrapper.t.sol +++ b/contracts/test/TestImageSVGWrapper.t.sol @@ -17,7 +17,7 @@ contract TestImageSVGWrapper is TestSetup { vm.prank(alice); ethscriptions.createEthscription(Ethscriptions.CreateEthscriptionParams({ - transactionHash: txHash, + ethscriptionId: txHash, contentUriHash: sha256(bytes(pngDataUri)), initialOwner: alice, content: pngContent, @@ -117,4 +117,4 @@ contract TestImageSVGWrapper is TestSetup { } return string(result); } -} \ No newline at end of file +} diff --git a/contracts/test/TestSetup.sol b/contracts/test/TestSetup.sol index 63749fc..77c62d3 100644 --- a/contracts/test/TestSetup.sol +++ b/contracts/test/TestSetup.sol @@ -131,7 +131,7 @@ abstract contract TestSetup is Test { } return Ethscriptions.CreateEthscriptionParams({ - transactionHash: transactionHash, + ethscriptionId: transactionHash, contentUriHash: contentUriHash, initialOwner: initialOwner, content: content, @@ -144,4 +144,4 @@ abstract contract TestSetup is Test { }) }); } -} \ No newline at end of file +} diff --git a/lib/block_validator.rb b/lib/block_validator.rb index 8c26672..04124c9 100644 --- a/lib/block_validator.rb +++ b/lib/block_validator.rb @@ -109,7 +109,7 @@ def load_genesis_transaction_hashes genesis_file = Rails.root.join('contracts', 'script', 'genesisEthscriptions.json') genesis_data = JSON.parse(File.read(genesis_file)) - # Extract all transaction hashes from the ethscriptions array + # Extract all ethscription IDs (L1 tx hashes) from the ethscriptions array genesis_data['ethscriptions'].map { |e| e['transaction_hash'] } end diff --git a/lib/event_decoder.rb b/lib/event_decoder.rb index 278c939..87af82e 100644 --- a/lib/event_decoder.rb +++ b/lib/event_decoder.rb @@ -111,9 +111,9 @@ def decode_creation(log) def decode_protocol_transfer(log, metadata = {}) return nil unless log['topics']&.size >= 4 - # Event EthscriptionTransferred(bytes32 indexed transactionHash, address indexed from, address indexed to, uint256 ethscriptionNumber) + # Event EthscriptionTransferred(bytes32 indexed ethscriptionId, address indexed from, address indexed to, uint256 ethscriptionNumber) # First 3 parameters are indexed, last one is in data - tx_hash = log['topics'][1]&.downcase # bytes32 transactionHash + tx_hash = log['topics'][1]&.downcase # bytes32 ethscriptionId from = decode_address_from_topic(log['topics'][2]) to = decode_address_from_topic(log['topics'][3]) diff --git a/lib/storage_reader.rb b/lib/storage_reader.rb index 32bac3d..e25cc49 100644 --- a/lib/storage_reader.rb +++ b/lib/storage_reader.rb @@ -27,7 +27,7 @@ class StorageReader 'type' => 'function', 'stateMutability' => 'view', 'inputs' => [ - { 'name' => 'transactionHash', 'type' => 'bytes32' } + { 'name' => 'ethscriptionId', 'type' => 'bytes32' } ], 'outputs' => [ ETHSCRIPTION_STRUCT_ABI @@ -38,7 +38,7 @@ class StorageReader 'type' => 'function', 'stateMutability' => 'view', 'inputs' => [ - { 'name' => 'transactionHash', 'type' => 'bytes32' } + { 'name' => 'ethscriptionId', 'type' => 'bytes32' } ], 'outputs' => [ { 'name' => '', 'type' => 'bytes' } @@ -49,7 +49,7 @@ class StorageReader 'type' => 'function', 'stateMutability' => 'view', 'inputs' => [ - { 'name' => 'transactionHash', 'type' => 'bytes32' } + { 'name' => 'ethscriptionId', 'type' => 'bytes32' } ], 'outputs' => [ { 'name' => '', 'type' => 'address' } @@ -69,7 +69,7 @@ class StorageReader 'type' => 'function', 'stateMutability' => 'view', 'inputs' => [ - { 'name' => 'txHash', 'type' => 'bytes32' } + { 'name' => 'ethscriptionId', 'type' => 'bytes32' } ], 'outputs' => [ { 'name' => 'ethscription', **ETHSCRIPTION_STRUCT_ABI }, @@ -203,29 +203,29 @@ def get_ethscription_content(tx_hash, block_tag: 'latest') nil end - def get_owner(token_id, block_tag: 'latest') + def get_owner(ethscription_id, block_tag: 'latest') # Build function signature function_sig = Eth::Util.keccak256('ownerOf(bytes32)')[0...4] - # Token ID is the transaction hash as uint256 - token_id_bytes32 = format_bytes32(token_id) + # Parameter is the ethscription ID (bytes32) + ethscription_id_bytes32 = format_bytes32(ethscription_id) # Encode the parameter - calldata = function_sig + [token_id_bytes32].pack('H*') + calldata = function_sig + [ethscription_id_bytes32].pack('H*') # Make the eth_call result = eth_call('0x' + calldata.unpack1('H*'), block_tag) # Some nodes return 0x when the call yields no data return nil if result == '0x' # Nil indicates an RPC/network failure - raise StandardError, "RPC call failed for ownerOf #{token_id}" if result.nil? + raise StandardError, "RPC call failed for ownerOf #{ethscription_id}" if result.nil? # Decode the result - ownerOf returns a single address decoded = Eth::Abi.decode(['address'], result) Eth::Address.new(decoded[0]).to_s rescue EthRpcClient::ExecutionRevertedError => e # Contract reverted - token doesn't exist - Rails.logger.debug "Token #{token_id} doesn't exist (contract reverted): #{e.message}" + Rails.logger.debug "Ethscription #{ethscription_id} doesn't exist (contract reverted): #{e.message}" nil end diff --git a/spec/support/ethscriptions_test_helper.rb b/spec/support/ethscriptions_test_helper.rb index ff70ed3..4288803 100644 --- a/spec/support/ethscriptions_test_helper.rb +++ b/spec/support/ethscriptions_test_helper.rb @@ -585,16 +585,14 @@ def import_l1_block_with_geth(l1_transactions) # Return all ethscription transactions (both successful and failed) imported_ethscriptions = ethscription_transactions ethscription_ids = imported_ethscriptions.flat_map do |tx| - case tx.ethscription_operation - when :create + op = tx.ethscription_operation.to_s + case op + when 'create' # For create operations, the ethscription ID is the transaction hash [tx.eth_transaction.tx_hash.to_hex] - when :transfer - if tx.transfer_ids.present? - tx.transfer_ids # Multi-transfer - else - [tx.ethscription_id] # Single transfer - end + when 'transfer', 'transfer_with_previous_owner' + # Always use array form for transfers + Array.wrap(tx.transfer_ids) else [tx.eth_transaction.tx_hash.to_hex] # Fallback end