Skip to content

Commit 0f47d8d

Browse files
Merge pull request #125 from ethscriptions-protocol/recator
Refactor
2 parents 2d9a931 + 8f833bd commit 0f47d8d

File tree

8 files changed

+353
-309
lines changed

8 files changed

+353
-309
lines changed

app/models/eth_transaction.rb

Lines changed: 180 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ def self.event_signature(event_name)
2929
def transaction_hash
3030
tx_hash
3131
end
32-
3332

3433
sig { params(block_result: T.untyped, receipt_result: T.untyped).returns(T::Array[EthTransaction]) }
3534
def self.from_rpc_result(block_result, receipt_result)
@@ -66,8 +65,8 @@ def self.ethscription_txs_from_rpc_results(block_results, receipt_results, ethsc
6665
eth_txs.sort_by(&:transaction_index).each do |eth_tx|
6766
next unless eth_tx.is_success?
6867

69-
# Build deposits using the unified builder
70-
deposits = EthscriptionTransactionBuilder.build_deposits(eth_tx, ethscriptions_block)
68+
# Build deposits directly from this EthTransaction instance
69+
deposits = eth_tx.build_ethscription_deposits(ethscriptions_block)
7170
all_deposits.concat(deposits)
7271
end
7372

@@ -83,4 +82,182 @@ def is_success?
8382
def ethscription_source_hash
8483
tx_hash
8584
end
85+
86+
# Build deposit transactions (EthscriptionTransaction objects) from this L1 transaction
87+
sig { params(ethscriptions_block: EthscriptionsBlock).returns(T::Array[EthscriptionTransaction]) }
88+
def build_ethscription_deposits(ethscriptions_block)
89+
@transactions = []
90+
91+
# 1. Process calldata (try as creation, then as transfer)
92+
process_calldata
93+
94+
# 2. Process events (creations and transfers)
95+
process_events
96+
97+
@transactions.compact
98+
end
99+
100+
private
101+
102+
def process_calldata
103+
return unless to_address.present?
104+
105+
try_calldata_creation
106+
try_calldata_transfer
107+
end
108+
109+
def try_calldata_creation
110+
transaction = EthscriptionTransaction.build_create_ethscription(
111+
eth_transaction: self,
112+
creator: normalize_address(from_address),
113+
initial_owner: normalize_address(to_address),
114+
content_uri: utf8_input,
115+
source_type: :input,
116+
source_index: transaction_index
117+
)
118+
119+
@transactions << transaction
120+
end
121+
122+
def try_calldata_transfer
123+
valid_length = if SysConfig.esip5_enabled?(block_number)
124+
input.bytesize > 0 && input.bytesize % 32 == 0
125+
else
126+
input.bytesize == 32
127+
end
128+
129+
return unless valid_length
130+
131+
input_hex = input.to_hex.delete_prefix('0x')
132+
133+
ids = input_hex.scan(/.{64}/).map { |hash_hex| normalize_hash("0x#{hash_hex}") }
134+
135+
# Create transfer transaction
136+
transaction = EthscriptionTransaction.build_transfer(
137+
eth_transaction: self,
138+
from_address: normalize_address(from_address),
139+
to_address: normalize_address(to_address),
140+
ethscription_ids: ids,
141+
source_type: :input,
142+
source_index: transaction_index
143+
)
144+
145+
@transactions << transaction
146+
end
147+
148+
def process_events
149+
ordered_events.each do |log|
150+
begin
151+
case log['topics']&.first
152+
when CreateEthscriptionEventSig
153+
process_create_event(log)
154+
when Esip1EventSig
155+
process_esip1_transfer_event(log)
156+
when Esip2EventSig
157+
process_esip2_transfer_event(log)
158+
end
159+
rescue Eth::Abi::DecodingError, RangeError => e
160+
Rails.logger.error "Failed to decode event: #{e.message}"
161+
next
162+
end
163+
end
164+
end
165+
166+
def process_create_event(log)
167+
return unless SysConfig.esip3_enabled?(block_number)
168+
return unless log['topics'].length == 2
169+
170+
# Decode event data
171+
initial_owner = Eth::Abi.decode(['address'], log['topics'].second).first
172+
content_uri_data = Eth::Abi.decode(['string'], log['data']).first
173+
content_uri = HexDataProcessor.clean_utf8(content_uri_data)
174+
175+
transaction = EthscriptionTransaction.build_create_ethscription(
176+
eth_transaction: self,
177+
creator: normalize_address(log['address']),
178+
initial_owner: normalize_address(initial_owner),
179+
content_uri: content_uri,
180+
source_type: :event,
181+
source_index: log['logIndex'].to_i(16)
182+
)
183+
184+
@transactions << transaction
185+
end
186+
187+
def process_esip1_transfer_event(log)
188+
return unless SysConfig.esip1_enabled?(block_number)
189+
return unless log['topics'].length == 3
190+
191+
# Decode event data
192+
event_to = Eth::Abi.decode(['address'], log['topics'].second).first
193+
tx_hash_hex = Eth::Util.bin_to_prefixed_hex(
194+
Eth::Abi.decode(['bytes32'], log['topics'].third).first
195+
)
196+
197+
ethscription_id = normalize_hash(tx_hash_hex)
198+
199+
transaction = EthscriptionTransaction.build_transfer(
200+
eth_transaction: self,
201+
from_address: normalize_address(log['address']),
202+
to_address: normalize_address(event_to),
203+
ethscription_ids: ethscription_id, # Single ID, will be wrapped in array
204+
source_type: :event,
205+
source_index: log['logIndex'].to_i(16)
206+
)
207+
208+
@transactions << transaction
209+
end
210+
211+
def process_esip2_transfer_event(log)
212+
return unless SysConfig.esip2_enabled?(block_number)
213+
return unless log['topics'].length == 4
214+
215+
event_previous_owner = Eth::Abi.decode(['address'], log['topics'].second).first
216+
event_to = Eth::Abi.decode(['address'], log['topics'].third).first
217+
tx_hash_hex = Eth::Util.bin_to_prefixed_hex(
218+
Eth::Abi.decode(['bytes32'], log['topics'].fourth).first
219+
)
220+
221+
ethscription_id = normalize_hash(tx_hash_hex)
222+
223+
transaction = EthscriptionTransaction.build_transfer(
224+
eth_transaction: self,
225+
from_address: normalize_address(log['address']),
226+
to_address: normalize_address(event_to),
227+
ethscription_ids: ethscription_id, # Single ID, will be wrapped in array
228+
enforced_previous_owner: normalize_address(event_previous_owner),
229+
source_type: :event,
230+
source_index: log['logIndex'].to_i(16)
231+
)
232+
233+
@transactions << transaction
234+
end
235+
236+
def ordered_events
237+
return [] unless logs
238+
239+
logs.reject { |log| log['removed'] }
240+
.sort_by { |log| log['logIndex'].to_i(16) }
241+
end
242+
243+
def utf8_input
244+
HexDataProcessor.hex_to_utf8(
245+
input.to_hex,
246+
support_gzip: SysConfig.esip7_enabled?(block_number)
247+
)
248+
end
249+
250+
def normalize_address(addr)
251+
return nil unless addr
252+
# Handle both Address20 objects and strings
253+
addr_str = addr.respond_to?(:to_hex) ? addr.to_hex : addr.to_s
254+
addr_str.downcase
255+
end
256+
257+
def normalize_hash(hash)
258+
return nil unless hash
259+
# Handle both Hash32 objects and strings
260+
hash_str = hash.respond_to?(:to_hex) ? hash.to_hex : hash.to_s
261+
hash_str.downcase
262+
end
86263
end

app/models/ethscription_transaction.rb

Lines changed: 21 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ class EthscriptionTransaction < T::Struct
1717
prop :content_uri, T.nilable(String)
1818

1919
# Transfer operation fields
20-
prop :ethscription_id, T.nilable(String)
21-
prop :transfer_ids, T.nilable(T::Array[String])
20+
prop :transfer_ids, T.nilable(T::Array[String]) # Always an array, even for single transfers
2221
prop :transfer_from_address, T.nilable(String)
2322
prop :transfer_to_address, T.nilable(String)
2423
prop :enforced_previous_owner, T.nilable(String)
@@ -38,14 +37,16 @@ class EthscriptionTransaction < T::Struct
3837
TO_ADDRESS = SysConfig::ETHSCRIPTIONS_ADDRESS
3938

4039
# Factory method for create operations
41-
def self.create_ethscription(
40+
def self.build_create_ethscription(
4241
eth_transaction:,
4342
creator:,
4443
initial_owner:,
4544
content_uri:,
4645
source_type:,
4746
source_index:
4847
)
48+
return unless DataUri.valid?(content_uri)
49+
4950
new(
5051
from_address: Address20.from_hex(creator.is_a?(String) ? creator : creator.to_hex),
5152
eth_transaction: eth_transaction,
@@ -58,22 +59,26 @@ def self.create_ethscription(
5859
)
5960
end
6061

61-
# Factory method for transfer operations
62-
def self.transfer_ethscription(
62+
# Transfer factory - handles single, multiple, and previous owner cases
63+
def self.build_transfer(
6364
eth_transaction:,
6465
from_address:,
6566
to_address:,
66-
ethscription_id:,
67-
enforced_previous_owner: nil,
6867
source_type:,
69-
source_index:
68+
source_index:,
69+
ethscription_ids:, # Can be a single ID or an array of IDs
70+
enforced_previous_owner: nil
7071
)
72+
# Normalize to array - accept either single ID or array of IDs
73+
ids = Array.wrap(ethscription_ids)
74+
75+
# Determine operation type
7176
operation_type = enforced_previous_owner ? 'transfer_with_previous_owner' : 'transfer'
7277

7378
new(
7479
from_address: Address20.from_hex(from_address.is_a?(String) ? from_address : from_address.to_hex),
7580
eth_transaction: eth_transaction,
76-
ethscription_id: ethscription_id,
81+
transfer_ids: ids, # Always use array
7782
transfer_from_address: from_address,
7883
transfer_to_address: to_address,
7984
enforced_previous_owner: enforced_previous_owner,
@@ -83,35 +88,14 @@ def self.transfer_ethscription(
8388
)
8489
end
8590

86-
# Factory method for transferMultipleEthscriptions (inputs only)
87-
def self.transfer_multiple_ethscriptions(
88-
eth_transaction:,
89-
from_address:,
90-
to_address:,
91-
ethscription_ids:,
92-
source_type:,
93-
source_index:
94-
)
95-
new(
96-
from_address: Address20.from_hex(from_address.is_a?(String) ? from_address : from_address.to_hex),
97-
eth_transaction: eth_transaction,
98-
transfer_ids: ethscription_ids,
99-
transfer_from_address: from_address,
100-
transfer_to_address: to_address,
101-
source_type: source_type&.to_sym,
102-
source_index: source_index,
103-
ethscription_operation: 'transfer'
104-
)
105-
end
106-
10791
# Get function selector for this operation
10892
def function_selector
10993
function_signature = case ethscription_operation
11094
when 'create'
11195
'createEthscription((bytes32,bytes32,address,bytes,string,bool,(string,string,bytes)))'
11296
when 'transfer'
113-
if transfer_ids && transfer_ids.any?
114-
'transferMultipleEthscriptions(bytes32[],address)'
97+
if transfer_ids.length > 1
98+
'transferEthscriptions(address,bytes32[])'
11599
else
116100
'transferEthscription(address,bytes32)'
117101
end
@@ -145,34 +129,6 @@ def source_hash
145129
Hash32.from_bin(bin_val)
146130
end
147131

148-
def valid_create?
149-
content_uri.present? &&
150-
creator.present? &&
151-
initial_owner.present? &&
152-
DataUri.valid?(content_uri)
153-
end
154-
155-
def valid_transfer?
156-
# Basic field validation - if we extracted the data properly, ABI encoding should work
157-
case ethscription_operation
158-
when 'transfer'
159-
if transfer_ids
160-
# Multiple transfer (input-based)
161-
transfer_ids.is_a?(Array) && transfer_ids.any?
162-
else
163-
# Single transfer (event-based)
164-
ethscription_id.present?
165-
end
166-
when 'transfer_with_previous_owner'
167-
# Always single transfer (event-based only)
168-
ethscription_id.present?
169-
else
170-
false
171-
end &&
172-
transfer_from_address.present? &&
173-
transfer_to_address.present?
174-
end
175-
176132
public
177133

178134
# Dynamic input method - builds calldata on demand
@@ -181,7 +137,7 @@ def input
181137
when 'create'
182138
ByteString.from_bin(build_create_calldata)
183139
when 'transfer'
184-
if transfer_ids && transfer_ids.any?
140+
if transfer_ids.length > 1
185141
ByteString.from_bin(build_transfer_multiple_calldata)
186142
else
187143
ByteString.from_bin(build_transfer_calldata)
@@ -267,7 +223,7 @@ def build_transfer_calldata
267223

268224
# Convert to binary for ABI
269225
to_bin = address_to_bin(transfer_to_address)
270-
id_bin = hex_to_bin(ethscription_id)
226+
id_bin = hex_to_bin(transfer_ids.first)
271227

272228
encoded = Eth::Abi.encode(['address', 'bytes32'], [to_bin, id_bin])
273229

@@ -281,7 +237,7 @@ def build_transfer_with_previous_owner_calldata
281237

282238
# Convert to binary for ABI
283239
to_bin = address_to_bin(transfer_to_address)
284-
id_bin = hex_to_bin(ethscription_id)
240+
id_bin = hex_to_bin(transfer_ids.first)
285241
prev_bin = address_to_bin(enforced_previous_owner)
286242

287243
encoded = Eth::Abi.encode(['address', 'bytes32', 'address'], [to_bin, id_bin, prev_bin])
@@ -294,10 +250,10 @@ def build_transfer_multiple_calldata
294250
# Get function selector as binary
295251
function_sig = function_selector.b
296252

297-
ids_bin = (transfer_ids || []).map { |id| hex_to_bin(id) }
298253
to_bin = address_to_bin(transfer_to_address)
254+
ids_bin = transfer_ids.map { |id| hex_to_bin(id) }
299255

300-
encoded = Eth::Abi.encode(['bytes32[]', 'address'], [ids_bin, to_bin])
256+
encoded = Eth::Abi.encode(['address', 'bytes32[]'], [to_bin, ids_bin])
301257

302258
(function_sig + encoded).b
303259
end

0 commit comments

Comments
 (0)