@@ -9,12 +9,13 @@ class Erc721EthscriptionsCollectionParser
99 # Operation schemas defining exact structure and ABI encoding
1010 OPERATION_SCHEMAS = {
1111 'create_collection' => {
12- keys : %w[ name symbol total_supply description logo_image_uri banner_image_uri background_color website_link twitter_link discord_link ] ,
13- abi_type : '(string,string,uint256,string,string,string,string,string,string,string)' ,
12+ keys : %w[ name symbol max_supply description logo_image_uri banner_image_uri background_color website_link twitter_link discord_link ] ,
13+ # Contract expects an extra bytes32 merkleRoot at the end. We append zero when omitted.
14+ abi_type : '(string,string,uint256,string,string,string,string,string,string,string,bytes32)' ,
1415 validators : {
1516 'name' => :string ,
1617 'symbol' => :string ,
17- 'total_supply ' => :uint256 ,
18+ 'max_supply ' => :uint256 ,
1819 'description' => :string ,
1920 'logo_image_uri' => :string ,
2021 'banner_image_uri' => :string ,
@@ -24,9 +25,19 @@ class Erc721EthscriptionsCollectionParser
2425 'discord_link' => :string
2526 }
2627 } ,
28+ 'create_and_add_self' => {
29+ keys : %w[ metadata item ] ,
30+ # ((CollectionParams),(ItemData)) with ItemData including bytes32[] merkle_proof
31+ abi_type : '((string,string,uint256,string,string,string,string,string,string,string,bytes32),(uint256,string,bytes32,string,string,(string,string)[],bytes32[]))' ,
32+ validators : {
33+ 'metadata' => :collection_metadata ,
34+ 'item' => :single_item
35+ }
36+ } ,
2737 'add_items_batch' => {
2838 keys : %w[ collection_id items ] ,
29- abi_type : '(bytes32,(uint256,string,bytes32,string,string,(string,string)[])[])' ,
39+ # Includes per-item merkle_proof (bytes32[]) as the final tuple element
40+ abi_type : '(bytes32,(uint256,string,bytes32,string,string,(string,string)[],bytes32[])[])' ,
3041 validators : {
3142 'collection_id' => :bytes32 ,
3243 'items' => :items_array
@@ -42,7 +53,8 @@ class Erc721EthscriptionsCollectionParser
4253 } ,
4354 'edit_collection' => {
4455 keys : %w[ collection_id description logo_image_uri banner_image_uri background_color website_link twitter_link discord_link ] ,
45- abi_type : '(bytes32,string,string,string,string,string,string,string)' ,
56+ # Contract includes a bytes32 merkleRoot; append zero when omitted
57+ abi_type : '(bytes32,string,string,string,string,string,string,string,bytes32)' ,
4658 validators : {
4759 'collection_id' => :bytes32 ,
4860 'description' => :string ,
@@ -84,7 +96,8 @@ class Erc721EthscriptionsCollectionParser
8496 } . freeze
8597
8698 # Item keys for add_items_batch validation
87- ITEM_KEYS = %w[ item_index name ethscription_id background_color description attributes ] . freeze
99+ ITEM_KEYS_MIN = %w[ item_index name ethscription_id background_color description attributes ] . freeze
100+ ITEM_KEYS_WITH_PROOF = %w[ item_index name ethscription_id background_color description attributes merkle_proof ] . freeze
88101
89102 # Attribute keys for NFT metadata
90103 ATTRIBUTE_KEYS = %w[ trait_type value ] . freeze
@@ -153,6 +166,8 @@ def encode_operation(operation, data, schema)
153166 values = case operation
154167 when 'create_collection'
155168 build_create_collection_values ( validated_data )
169+ when 'create_and_add_self'
170+ build_create_and_add_self_values ( validated_data )
156171 when 'add_items_batch'
157172 build_add_items_batch_values ( validated_data )
158173 when 'remove_items'
@@ -243,25 +258,68 @@ def validate_items_array(value, field_name)
243258 end
244259 end
245260
261+ def validate_single_item ( value , field_name )
262+ unless value . is_a? ( Hash )
263+ raise ValidationError , "Expected object for #{ field_name } "
264+ end
265+ validate_item ( value )
266+ end
267+
268+ def validate_collection_metadata ( value , field_name )
269+ unless value . is_a? ( Hash )
270+ raise ValidationError , "Expected object for #{ field_name } "
271+ end
272+ # Expected keys for metadata (merkle_root optional)
273+ expected_min_keys = %w[ name symbol max_supply description logo_image_uri banner_image_uri background_color website_link twitter_link discord_link ]
274+ unless value . keys == expected_min_keys || value . keys == ( expected_min_keys + [ 'merkle_root' ] )
275+ raise ValidationError , "Invalid metadata keys or order"
276+ end
277+
278+ {
279+ name : validate_string ( value [ 'name' ] , 'name' ) ,
280+ symbol : validate_string ( value [ 'symbol' ] , 'symbol' ) ,
281+ maxSupply : validate_uint256 ( value [ 'max_supply' ] , 'max_supply' ) ,
282+ description : validate_string ( value [ 'description' ] , 'description' ) ,
283+ logoImageUri : validate_string ( value [ 'logo_image_uri' ] , 'logo_image_uri' ) ,
284+ bannerImageUri : validate_string ( value [ 'banner_image_uri' ] , 'banner_image_uri' ) ,
285+ backgroundColor : validate_string ( value [ 'background_color' ] , 'background_color' ) ,
286+ websiteLink : validate_string ( value [ 'website_link' ] , 'website_link' ) ,
287+ twitterLink : validate_string ( value [ 'twitter_link' ] , 'twitter_link' ) ,
288+ discordLink : validate_string ( value [ 'discord_link' ] , 'discord_link' ) ,
289+ merkleRoot : value . key? ( 'merkle_root' ) ? validate_bytes32 ( value [ 'merkle_root' ] , 'merkle_root' ) : nil
290+ }
291+ end
292+
246293 def validate_item ( item )
247294 unless item . is_a? ( Hash )
248295 raise ValidationError , "Item must be an object"
249296 end
250297
251- # Check exact key order
252- unless item . keys == ITEM_KEYS
253- raise ValidationError , "Invalid item keys or order. Expected: #{ ITEM_KEYS . join ( ',' ) } , got: #{ item . keys . join ( ',' ) } "
254- end
298+ # Check exact key order; allow optional merkle_proof
299+ has_proof =
300+ if item . keys == ITEM_KEYS_WITH_PROOF
301+ true
302+ elsif item . keys == ITEM_KEYS_MIN
303+ false
304+ else
305+ expected = "[#{ ITEM_KEYS_MIN . join ( ', ' ) } ] or [#{ ITEM_KEYS_WITH_PROOF . join ( ', ' ) } ]"
306+ raise ValidationError , "Invalid item keys or order. Expected: #{ expected } , got: [#{ item . keys . join ( ', ' ) } ]"
307+ end
255308
256309 # Validate each field - return in internal format for encoding
257- {
310+ result = {
258311 itemIndex : validate_uint256 ( item [ 'item_index' ] , 'item_index' ) ,
259312 name : validate_string ( item [ 'name' ] , 'name' ) ,
260313 ethscriptionId : validate_bytes32 ( item [ 'ethscription_id' ] , 'ethscription_id' ) ,
261314 backgroundColor : validate_string ( item [ 'background_color' ] , 'background_color' ) ,
262315 description : validate_string ( item [ 'description' ] , 'description' ) ,
263316 attributes : validate_attributes_array ( item [ 'attributes' ] , 'attributes' )
264317 }
318+
319+ # Optional merkle proof; always include as bytes32[] (empty when omitted)
320+ result [ :merkleProof ] = has_proof ? validate_bytes32_array ( item [ 'merkle_proof' ] , 'merkle_proof' ) : [ ]
321+
322+ result
265323 end
266324
267325 def validate_attributes_array ( value , field_name )
@@ -297,15 +355,50 @@ def build_create_collection_values(data)
297355 [
298356 data [ 'name' ] ,
299357 data [ 'symbol' ] ,
300- data [ 'total_supply ' ] ,
358+ data [ 'max_supply ' ] ,
301359 data [ 'description' ] ,
302360 data [ 'logo_image_uri' ] ,
303361 data [ 'banner_image_uri' ] ,
304362 data [ 'background_color' ] ,
305363 data [ 'website_link' ] ,
306364 data [ 'twitter_link' ] ,
307- data [ 'discord_link' ]
365+ data [ 'discord_link' ] ,
366+ # Append zero merkle root to satisfy contract struct shape
367+ [ "" . ljust ( 64 , '0' ) ] . pack ( 'H*' )
368+ ]
369+ end
370+
371+ def build_create_and_add_self_values ( data )
372+ meta = data [ 'metadata' ]
373+ item = data [ 'item' ]
374+
375+ # Metadata tuple with optional merkleRoot
376+ merkle_root = meta [ :merkleRoot ] || [ "" . ljust ( 64 , '0' ) ] . pack ( 'H*' )
377+ metadata_tuple = [
378+ meta [ :name ] ,
379+ meta [ :symbol ] ,
380+ meta [ :maxSupply ] ,
381+ meta [ :description ] ,
382+ meta [ :logoImageUri ] ,
383+ meta [ :bannerImageUri ] ,
384+ meta [ :backgroundColor ] ,
385+ meta [ :websiteLink ] ,
386+ meta [ :twitterLink ] ,
387+ meta [ :discordLink ] ,
388+ merkle_root
389+ ]
390+
391+ item_tuple = [
392+ item [ :itemIndex ] ,
393+ item [ :name ] ,
394+ item [ :ethscriptionId ] ,
395+ item [ :backgroundColor ] ,
396+ item [ :description ] ,
397+ item [ :attributes ] ,
398+ item [ :merkleProof ]
308399 ]
400+
401+ [ metadata_tuple , item_tuple ]
309402 end
310403
311404 def build_add_items_batch_values ( data )
@@ -317,7 +410,8 @@ def build_add_items_batch_values(data)
317410 item [ :ethscriptionId ] ,
318411 item [ :backgroundColor ] ,
319412 item [ :description ] ,
320- item [ :attributes ]
413+ item [ :attributes ] ,
414+ item [ :merkleProof ]
321415 ]
322416 end
323417
@@ -329,7 +423,7 @@ def build_remove_items_values(data)
329423 end
330424
331425 def build_edit_collection_values ( data )
332- [
426+ values = [
333427 data [ 'collection_id' ] ,
334428 data [ 'description' ] ,
335429 data [ 'logo_image_uri' ] ,
@@ -339,6 +433,10 @@ def build_edit_collection_values(data)
339433 data [ 'twitter_link' ] ,
340434 data [ 'discord_link' ]
341435 ]
436+
437+ # Append zero merkle root if not provided in payload (parser schema omits it)
438+ values << [ "" . ljust ( 64 , '0' ) ] . pack ( 'H*' )
439+ values
342440 end
343441
344442 def build_edit_collection_item_values ( data )
0 commit comments