@@ -12,8 +12,8 @@ class Erc721EthscriptionsCollectionParser
1212 # Operation schemas defining exact structure and ABI encoding
1313 OPERATION_SCHEMAS = {
1414 'create_collection' => {
15- keys : %w[ name symbol max_supply description logo_image_uri banner_image_uri background_color website_link twitter_link discord_link merkle_root ] ,
16- abi_type : '(string,string,uint256,string,string,string,string,string,string,string,bytes32)' ,
15+ keys : %w[ name symbol max_supply description logo_image_uri banner_image_uri background_color website_link twitter_link discord_link merkle_root initial_owner ] ,
16+ abi_type : '(string,string,uint256,string,string,string,string,string,string,string,bytes32,address )' ,
1717 validators : {
1818 'name' => :string ,
1919 'symbol' => :string ,
@@ -25,14 +25,15 @@ class Erc721EthscriptionsCollectionParser
2525 'website_link' => :string ,
2626 'twitter_link' => :string ,
2727 'discord_link' => :string ,
28- 'merkle_root' => :bytes32
28+ 'merkle_root' => :bytes32 ,
29+ 'initial_owner' => :optional_address
2930 }
3031 } ,
3132 # New combined create op name used by the contract; keep legacy alias below
3233 'create_collection_and_add_self' => {
3334 keys : %w[ metadata item ] ,
34- # ((CollectionParams),(ItemData)) - ItemData now includes contentHash as first field
35- abi_type : '((string,string,uint256,string,string,string,string,string,string,string,bytes32),(bytes32,uint256,string,string,string,(string,string)[],bytes32[]))' ,
35+ # ((CollectionParams),(ItemData)) - CollectionParams now includes initialOwner
36+ abi_type : '((string,string,uint256,string,string,string,string,string,string,string,bytes32,address ),(bytes32,uint256,string,string,string,(string,string)[],bytes32[]))' ,
3637 validators : {
3738 'metadata' => :collection_metadata ,
3839 'item' => :single_item
@@ -41,7 +42,7 @@ class Erc721EthscriptionsCollectionParser
4142 # Legacy alias retained for backwards compatibility
4243 'create_and_add_self' => {
4344 keys : %w[ metadata item ] ,
44- abi_type : '((string,string,uint256,string,string,string,string,string,string,string,bytes32),(bytes32,uint256,string,string,string,(string,string)[],bytes32[]))' ,
45+ abi_type : '((string,string,uint256,string,string,string,string,string,string,string,bytes32,address ),(bytes32,uint256,string,string,string,(string,string)[],bytes32[]))' ,
4546 validators : {
4647 'metadata' => :collection_metadata ,
4748 'item' => :single_item
@@ -132,21 +133,22 @@ class ValidationError < StandardError; end
132133
133134 # New API: validate and encode protocol params
134135 # Unified interface - accepts all possible parameters, uses what it needs
135- def self . validate_and_encode ( decoded_content :, operation :, params :, source :, ethscription_id : nil , **_extras )
136+ def self . validate_and_encode ( decoded_content :, operation :, params :, source :, ethscription_id : nil , eth_transaction : nil , **_extras )
136137 new . validate_and_encode (
137138 decoded_content : decoded_content ,
138139 operation : operation ,
139140 params : params ,
140141 source : source ,
141- ethscription_id : ethscription_id
142+ ethscription_id : ethscription_id ,
143+ eth_transaction : eth_transaction
142144 )
143145 end
144146
145- def validate_and_encode ( decoded_content :, operation :, params :, source :, ethscription_id : nil )
147+ def validate_and_encode ( decoded_content :, operation :, params :, source :, ethscription_id : nil , eth_transaction : nil )
146148 # Check import fallback first (if ethscription_id provided)
147149 if ethscription_id
148150 normalized_id = normalize_id ( ethscription_id )
149- if normalized_id && ( preplanned = build_import_encoded_params ( normalized_id , decoded_content ) )
151+ if normalized_id && ( preplanned = build_import_encoded_params ( normalized_id , decoded_content , eth_transaction ) )
150152 return preplanned
151153 end
152154 end
@@ -217,7 +219,7 @@ def normalize_id(value)
217219 # -------------------- Import fallback --------------------
218220
219221 # Returns [protocol, operation, encoded_data] or nil
220- def build_import_encoded_params ( id , decoded_content )
222+ def build_import_encoded_params ( id , decoded_content , eth_transaction = nil )
221223 data = self . class . load_import_data (
222224 items_path : DEFAULT_ITEMS_PATH ,
223225 collections_path : DEFAULT_COLLECTIONS_PATH
@@ -247,7 +249,7 @@ def build_import_encoded_params(id, decoded_content)
247249 operation = 'create_collection_and_add_self'
248250 schema = OPERATION_SCHEMAS [ operation ]
249251 encoding_data = {
250- 'metadata' => build_metadata_object ( metadata ) ,
252+ 'metadata' => build_metadata_object ( metadata , eth_transaction : eth_transaction ) ,
251253 'item' => build_item_object ( item : item , item_index : item_index , content_hash : content_hash )
252254 }
253255 encoded_data = encode_operation ( operation , encoding_data , schema , content_hash : content_hash )
@@ -355,7 +357,7 @@ def load_import_data(items_path:, collections_path:)
355357 end
356358
357359 # Build ordered JSON objects to match strict parser expectations
358- def build_metadata_object ( meta )
360+ def build_metadata_object ( meta , eth_transaction : nil )
359361 name = safe_string ( meta [ 'name' ] )
360362 symbol = safe_string ( meta [ 'symbol' ] || meta [ 'slug' ] || meta [ 'name' ] )
361363 max_supply = safe_uint_string ( meta [ 'max_supply' ] || meta [ 'total_supply' ] || 0 )
@@ -381,6 +383,24 @@ def build_metadata_object(meta)
381383 ]
382384 merkle_root = meta . fetch ( 'merkle_root' )
383385 result [ 'merkle_root' ] = to_bytes32_hex ( merkle_root )
386+
387+ # Handle initial_owner based on should_renounce flag
388+ if meta [ 'should_renounce' ] == true
389+ # address(0) means renounce ownership
390+ result [ 'initial_owner' ] = '0x0000000000000000000000000000000000000000'
391+ elsif meta [ 'initial_owner' ]
392+ # Use explicitly specified initial owner
393+ result [ 'initial_owner' ] = to_address_hex ( meta [ 'initial_owner' ] )
394+ elsif eth_transaction && eth_transaction . respond_to? ( :from_address )
395+ # Use the transaction sender as the actual owner
396+ result [ 'initial_owner' ] = eth_transaction . from_address . to_hex
397+ else
398+ # No transaction context - this shouldn't happen in production
399+ # For import, we always have the transaction
400+ # Return nil to indicate we can't determine the owner
401+ raise ValidationError , "Cannot determine initial owner without transaction context"
402+ end
403+
384404 result
385405 end
386406
@@ -408,6 +428,12 @@ def to_bytes32_hex(val)
408428 h
409429 end
410430
431+ def to_address_hex ( val )
432+ h = safe_string ( val ) . downcase
433+ raise ValidationError , "Invalid address hex: #{ val } " unless h . match? ( /\A 0x[0-9a-f]{40}\z / )
434+ h
435+ end
436+
411437 # Integer coercion helper for import computations
412438 def safe_uint ( val )
413439 case val
@@ -564,6 +590,14 @@ def validate_address(value, field_name)
564590 value . downcase
565591 end
566592
593+ def validate_optional_address ( value , field_name )
594+ unless value . is_a? ( String ) && value . match? ( /\A 0x[0-9a-f]{40}\z /i )
595+ raise ValidationError , "Invalid address for #{ field_name } : #{ value } "
596+ end
597+ # Allow address(0) for renouncement
598+ value . downcase
599+ end
600+
567601 def validate_bytes32_array ( value , field_name )
568602 unless value . is_a? ( Array )
569603 raise ValidationError , "Expected array for #{ field_name } "
@@ -598,8 +632,8 @@ def validate_collection_metadata(value, field_name)
598632 unless value . is_a? ( Hash )
599633 raise ValidationError , "Expected object for #{ field_name } "
600634 end
601- # Expected keys for metadata (merkle_root optional )
602- expected_keys = %w[ name symbol max_supply description logo_image_uri banner_image_uri background_color website_link twitter_link discord_link merkle_root ]
635+ # Expected keys for metadata (now includes initial_owner )
636+ expected_keys = %w[ name symbol max_supply description logo_image_uri banner_image_uri background_color website_link twitter_link discord_link merkle_root initial_owner ]
603637 unless value . keys == expected_keys
604638 raise ValidationError , "Invalid metadata keys or order"
605639 end
@@ -615,7 +649,8 @@ def validate_collection_metadata(value, field_name)
615649 websiteLink : validate_string ( value [ 'website_link' ] , 'website_link' ) ,
616650 twitterLink : validate_string ( value [ 'twitter_link' ] , 'twitter_link' ) ,
617651 discordLink : validate_string ( value [ 'discord_link' ] , 'discord_link' ) ,
618- merkleRoot : validate_bytes32 ( value [ 'merkle_root' ] , 'merkle_root' )
652+ merkleRoot : validate_bytes32 ( value [ 'merkle_root' ] , 'merkle_root' ) ,
653+ initialOwner : validate_optional_address ( value [ 'initial_owner' ] , 'initial_owner' )
619654 }
620655 end
621656
@@ -681,15 +716,16 @@ def build_create_collection_values(data)
681716 data [ 'website_link' ] ,
682717 data [ 'twitter_link' ] ,
683718 data [ 'discord_link' ] ,
684- data [ 'merkle_root' ]
719+ data [ 'merkle_root' ] ,
720+ data [ 'initial_owner' ]
685721 ]
686722 end
687723
688724 def build_create_and_add_self_values ( data , content_hash :)
689725 meta = data [ 'metadata' ]
690726 item = data [ 'item' ]
691727
692- # Metadata tuple with optional merkleRoot
728+ # Metadata tuple with merkleRoot and initialOwner
693729 merkle_root = meta [ :merkleRoot ] || [ "" . ljust ( 64 , '0' ) ] . pack ( 'H*' )
694730 metadata_tuple = [
695731 meta [ :name ] ,
@@ -702,7 +738,8 @@ def build_create_and_add_self_values(data, content_hash:)
702738 meta [ :websiteLink ] ,
703739 meta [ :twitterLink ] ,
704740 meta [ :discordLink ] ,
705- merkle_root
741+ merkle_root ,
742+ meta [ :initialOwner ]
706743 ]
707744
708745 # Item tuple - contentHash comes first (keccak256 of ethscription content)
0 commit comments