Skip to content

Commit 1386b82

Browse files
committed
Add combined getEthscriptionWithContent method
- Add getEthscriptionWithContent() to Ethscriptions.sol that returns both ethscription struct and content in single call - Update StorageReader to use new combined method, reducing RPC calls from 2 to 1 - Add comprehensive Foundry tests covering basic functionality, large content, and error cases
1 parent 9cd4344 commit 1386b82

2 files changed

Lines changed: 67 additions & 15 deletions

File tree

contracts/src/Ethscriptions.sol

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,18 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable {
536536
return _readFromPointers(contentData.pointers);
537537
}
538538

539+
/// @notice Get ethscription details and content in a single call
540+
/// @param txHash The transaction hash to look up
541+
/// @return ethscription The ethscription struct
542+
/// @return content The content bytes
543+
function getEthscriptionWithContent(bytes32 txHash) external view requireExists(txHash) returns (Ethscription memory ethscription, bytes memory content) {
544+
ethscription = ethscriptions[txHash];
545+
ContentData storage contentData = contentBySha[ethscription.content.contentSha];
546+
require(contentData.contentSha != bytes32(0), "No content stored");
547+
548+
content = _readFromPointers(contentData.pointers);
549+
}
550+
539551
/// @dev Helper function to construct a base64-encoded data URI
540552
function _constructDataURI(string memory mimetype, bytes memory content) internal pure returns (string memory) {
541553
return string.concat(

lib/storage_reader.rb

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,31 +73,71 @@ class StorageReader
7373
'outputs' => [
7474
{ 'name' => '', 'type' => 'uint256' }
7575
]
76+
},
77+
{
78+
'name' => 'getEthscriptionWithContent',
79+
'type' => 'function',
80+
'stateMutability' => 'view',
81+
'inputs' => [
82+
{ 'name' => 'txHash', 'type' => 'bytes32' }
83+
],
84+
'outputs' => [
85+
{ 'name' => 'ethscription', **ETHSCRIPTION_STRUCT_ABI },
86+
{ 'name' => 'content', 'type' => 'bytes' }
87+
]
7688
}
7789
]
7890

7991
class << self
8092
def get_ethscription_with_content(tx_hash, block_tag: 'latest')
81-
# Fetch both ethscription data and content in parallel
82-
threads = []
83-
ethscription_data = nil
84-
content_data = nil
93+
# Single call to get both ethscription and content
94+
tx_hash_bytes32 = format_bytes32(tx_hash)
8595

86-
threads << Thread.new do
87-
ethscription_data = get_ethscription(tx_hash, block_tag: block_tag)
88-
end
96+
# Build function signature and encode parameters
97+
function_sig = Eth::Util.keccak256('getEthscriptionWithContent(bytes32)')[0...4]
8998

90-
threads << Thread.new do
91-
content_data = get_ethscription_content(tx_hash, block_tag: block_tag)
92-
end
99+
# Encode the parameter (bytes32 is already 32 bytes)
100+
calldata = function_sig + [tx_hash_bytes32].pack('H*')
101+
102+
# Make the eth_call
103+
result = eth_call('0x' + calldata.unpack1('H*'), block_tag)
104+
raise StandardError, "Ethscription not found: #{tx_hash}" if result.nil? || result == '0x' || result == '0x0'
105+
106+
# Decode the tuple: (Ethscription, bytes)
107+
types = ['((bytes32,bytes32,string,string,string,bool),address,address,address,uint256,uint256,uint64,uint64,bytes32)', 'bytes']
108+
decoded = Eth::Abi.decode(types, result)
109+
110+
# Extract ethscription struct and content
111+
ethscription_data = decoded[0]
112+
content_data = decoded[1]
113+
content_info = ethscription_data[0] # Nested ContentInfo struct
93114

94-
threads.each(&:join)
115+
{
116+
# ContentInfo fields
117+
content_uri_hash: '0x' + content_info[0].unpack1('H*'),
118+
content_sha: '0x' + content_info[1].unpack1('H*'),
119+
mimetype: content_info[2],
120+
media_type: content_info[3],
121+
mime_subtype: content_info[4],
122+
esip6: content_info[5],
95123

96-
raise StandardError, "Ethscription not found: #{tx_hash}" unless ethscription_data
124+
# Main Ethscription fields
125+
creator: Eth::Address.new(ethscription_data[1]).to_s,
126+
initial_owner: Eth::Address.new(ethscription_data[2]).to_s,
127+
previous_owner: Eth::Address.new(ethscription_data[3]).to_s,
128+
ethscription_number: ethscription_data[4],
129+
created_at: ethscription_data[5],
130+
l1_block_number: ethscription_data[6],
131+
l2_block_number: ethscription_data[7],
132+
l1_block_hash: '0x' + ethscription_data[8].unpack1('H*'),
97133

98-
# Add content to the ethscription data
99-
ethscription_data[:content] = content_data
100-
ethscription_data
134+
# Content
135+
content: content_data
136+
}
137+
rescue => e
138+
Rails.logger.error "Failed to get ethscription with content #{tx_hash}: #{e.message}"
139+
Rails.logger.error e.backtrace.join("\n") if Rails.env.development?
140+
raise e
101141
end
102142

103143
def get_ethscription(tx_hash, block_tag: 'latest')

0 commit comments

Comments
 (0)