git.delta.rocks / unique-network / refs/commits / 17ea847df73f

difftreelog

feat add mint_bulk_cross

Grigoriy Simonov2023-09-22parent: #9a3d9a7.patch.diff
in: master

15 files changed

modifiedpallets/common/src/eth.rsdiffbeforeafterboth
--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -631,3 +631,12 @@
 		}
 	}
 }
+
+/// Token minting parameters
+#[derive(AbiCoder, Default, Debug)]
+pub struct MintTokenData {
+	/// Minted token owner
+	pub owner: CrossAddress,
+	/// Minted token properties
+	pub properties: Vec<Property>,
+}
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -981,13 +981,41 @@
 		Ok(true)
 	}
 
+	/// @notice Function to mint a token.
+	/// @param data Array of pairs of token owner and token's properties for minted token
+	#[weight(<SelfWeightOf<T>>::create_multiple_items(data.len() as u32) + <SelfWeightOf<T>>::set_token_properties(data.len() as u32))]
+	fn mint_bulk_cross(&mut self, caller: Caller, data: Vec<eth::MintTokenData>) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		let mut create_nft_data = Vec::with_capacity(data.len());
+		for eth::MintTokenData { owner, properties } in data {
+			let owner = owner.into_sub_cross_account::<T>()?;
+			create_nft_data.push(CreateItemData::<T> {
+				properties: properties
+					.into_iter()
+					.map(|property| property.try_into())
+					.collect::<Result<Vec<_>>>()?
+					.try_into()
+					.map_err(|_| "too many properties")?,
+				owner,
+			});
+		}
+
+		<Pallet<T>>::create_multiple_items(self, &caller, create_nft_data, &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
+
 	/// @notice Function to mint multiple tokens with the given tokenUris.
 	/// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
 	///  numbers and first number should be obtained with `nextTokenId` method
 	/// @param to The new owner
 	/// @param tokens array of pairs of token ID and token URI for minted tokens
 	#[solidity(hide, rename_selector = "mintBulkWithTokenURI")]
-	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32)  + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]
+	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]
 	fn mint_bulk_with_token_uri(
 		&mut self,
 		caller: Caller,
modifiedpallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -800,7 +800,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x307b061a
+/// @dev the ERC-165 identifier for this interface is 0x9b397d16
 contract ERC721UniqueExtensions is Dummy, ERC165 {
 	/// @notice A descriptive name for a collection of NFTs in this contract
 	/// @dev EVM selector for this function is: 0x06fdde03,
@@ -997,6 +997,17 @@
 	// 	return false;
 	// }
 
+	/// @notice Function to mint a token.
+	/// @param data Array of pairs of token owner and token's properties for minted token
+	/// @dev EVM selector for this function is: 0xab427b0c,
+	///  or in textual repr: mintBulkCross(((address,uint256),(string,bytes)[])[])
+	function mintBulkCross(MintTokenData[] memory data) public returns (bool) {
+		require(false, stub_error);
+		data;
+		dummy = 0;
+		return false;
+	}
+
 	// /// @notice Function to mint multiple tokens with the given tokenUris.
 	// /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
 	// ///  numbers and first number should be obtained with `nextTokenId` method
@@ -1044,6 +1055,11 @@
 	string uri;
 }
 
+struct MintTokenData {
+	CrossAddress owner;
+	Property[] properties;
+}
+
 /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 /// @dev See https://eips.ethereum.org/EIPS/eip-721
 /// @dev the ERC-165 identifier for this interface is 0x780e9d63
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -1021,6 +1021,43 @@
 		Ok(true)
 	}
 
+	/// @notice Function to mint a token.
+	/// @param tokenProperties Properties of minted token
+	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_properties.len() as u32) + <SelfWeightOf<T>>::set_token_properties(token_properties.len() as u32))]
+	fn mint_bulk_cross(
+		&mut self,
+		caller: Caller,
+		token_properties: Vec<eth::MintTokenData>,
+	) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		let mut create_rft_data = Vec::with_capacity(token_properties.len());
+		for eth::MintTokenData { owner, properties } in token_properties {
+			let owner = owner.into_sub_cross_account::<T>()?;
+			let users: BoundedBTreeMap<_, _, _> = [(owner, 1)]
+				.into_iter()
+				.collect::<BTreeMap<_, _>>()
+				.try_into()
+				.map_err(|_| "too many users")?;
+			create_rft_data.push(CreateItemData::<T> {
+				properties: properties
+					.into_iter()
+					.map(|property| property.try_into())
+					.collect::<Result<Vec<_>>>()?
+					.try_into()
+					.map_err(|_| "too many properties")?,
+				users,
+			});
+		}
+
+		<Pallet<T>>::create_multiple_items(self, &caller, create_rft_data, &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
+
 	/// @notice Function to mint multiple tokens with the given tokenUris.
 	/// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
 	///  numbers and first number should be obtained with `nextTokenId` method
modifiedpallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth
--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -800,7 +800,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x95c0f66c
+/// @dev the ERC-165 identifier for this interface is 0x3e828d60
 contract ERC721UniqueExtensions is Dummy, ERC165 {
 	/// @notice A descriptive name for a collection of NFTs in this contract
 	/// @dev EVM selector for this function is: 0x06fdde03,
@@ -986,6 +986,17 @@
 	// 	return false;
 	// }
 
+	/// @notice Function to mint a token.
+	/// @param tokenProperties Properties of minted token
+	/// @dev EVM selector for this function is: 0xab427b0c,
+	///  or in textual repr: mintBulkCross(((address,uint256),(string,bytes)[])[])
+	function mintBulkCross(MintTokenData[] memory tokenProperties) public returns (bool) {
+		require(false, stub_error);
+		tokenProperties;
+		dummy = 0;
+		return false;
+	}
+
 	// /// @notice Function to mint multiple tokens with the given tokenUris.
 	// /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
 	// ///  numbers and first number should be obtained with `nextTokenId` method
@@ -1045,6 +1056,11 @@
 	string uri;
 }
 
+struct MintTokenData {
+	CrossAddress owner;
+	Property[] properties;
+}
+
 /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 /// @dev See https://eips.ethereum.org/EIPS/eip-721
 /// @dev the ERC-165 identifier for this interface is 0x780e9d63
modifiedpallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth
--- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol
+++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol
@@ -25,12 +25,12 @@
 }
 
 /// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x4135fff1
+/// @dev the ERC-165 identifier for this interface is 0x94e5af0d
 contract CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
 	/// Create a collection
 	/// @return address Address of the newly created collection
-	/// @dev EVM selector for this function is: 0xa765ee5b,
-	///  or in textual repr: createCollection(((address,uint256),string,string,string,uint8,uint8,(string,bytes)[],(string,(uint8,bool)[])[],(address,uint256)[],(bool,bool,address[]),(uint8,uint256)[],uint8))
+	/// @dev EVM selector for this function is: 0x72b5bea7,
+	///  or in textual repr: createCollection((string,string,string,uint8,uint8,(string,bytes)[],(string,(uint8,bool)[])[],(address,uint256)[],(bool,bool,address[]),(uint8,uint256)[],(address,uint256),uint8))
 	function createCollection(CreateCollectionData memory data) public payable returns (address) {
 		require(false, stub_error);
 		data;
@@ -170,8 +170,6 @@
 
 /// Collection properties
 struct CreateCollectionData {
-	/// Collection sponsor
-	CrossAddress pending_sponsor;
 	/// Collection name
 	string name;
 	/// Collection description
@@ -192,11 +190,12 @@
 	CollectionNestingAndPermission nesting_settings;
 	/// Collection limits
 	CollectionLimitValue[] limits;
+	/// Collection sponsor
+	CrossAddress pending_sponsor;
 	/// Extra collection flags
 	CollectionFlags flags;
 }
 
-/// Cross account struct
 type CollectionFlags is uint8;
 
 library CollectionFlagsLib {
@@ -207,13 +206,19 @@
 	/// External collections can't be managed using `unique` api
 	CollectionFlags constant externalField = CollectionFlags.wrap(1);
 
-	/// Reserved bits
+	/// Reserved flags
 	function reservedField(uint8 value) public pure returns (CollectionFlags) {
 		require(value < 1 << 5, "out of bound value");
 		return CollectionFlags.wrap(value << 1);
 	}
 }
 
+/// Cross account struct
+struct CrossAddress {
+	address eth;
+	uint256 sub;
+}
+
 /// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM.
 struct CollectionLimitValue {
 	CollectionLimitField field;
@@ -250,12 +255,6 @@
 	bool collection_admin;
 	/// If set - only tokens from specified collections can be nested.
 	address[] restricted;
-}
-
-/// Cross account struct
-struct CrossAddress {
-	address eth;
-	uint256 sub;
 }
 
 /// Ethereum representation of Token Property Permissions.
@@ -292,10 +291,10 @@
 
 /// Type of tokens in collection
 enum CollectionMode {
-	/// Fungible
-	Fungible,
 	/// Nonfungible
 	Nonfungible,
+	/// Fungible
+	Fungible,
 	/// Refungible
 	Refungible
 }
modifiedruntime/common/ethereum/sponsoring/refungible.rsdiffbeforeafterboth
--- a/runtime/common/ethereum/sponsoring/refungible.rs
+++ b/runtime/common/ethereum/sponsoring/refungible.rs
@@ -242,6 +242,7 @@
 			BurnFrom { .. }
 			| BurnFromCross { .. }
 			| MintBulk { .. }
+			| MintBulkCross { .. }
 			| MintBulkWithTokenUri { .. } => None,
 
 			MintCross { .. } => withdraw_create_item::<T>(
modifiedtests/src/eth/abi/nonFungible.jsondiffbeforeafterboth
--- a/tests/src/eth/abi/nonFungible.json
+++ b/tests/src/eth/abi/nonFungible.json
@@ -469,6 +469,39 @@
     "inputs": [
       {
         "components": [
+          {
+            "components": [
+              { "internalType": "address", "name": "eth", "type": "address" },
+              { "internalType": "uint256", "name": "sub", "type": "uint256" }
+            ],
+            "internalType": "struct CrossAddress",
+            "name": "owner",
+            "type": "tuple"
+          },
+          {
+            "components": [
+              { "internalType": "string", "name": "key", "type": "string" },
+              { "internalType": "bytes", "name": "value", "type": "bytes" }
+            ],
+            "internalType": "struct Property[]",
+            "name": "properties",
+            "type": "tuple[]"
+          }
+        ],
+        "internalType": "struct MintTokenData[]",
+        "name": "data",
+        "type": "tuple[]"
+      }
+    ],
+    "name": "mintBulkCross",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "components": [
           { "internalType": "address", "name": "eth", "type": "address" },
           { "internalType": "uint256", "name": "sub", "type": "uint256" }
         ],
modifiedtests/src/eth/abi/reFungible.jsondiffbeforeafterboth
after · tests/src/eth/abi/reFungible.json
1[2  {3    "anonymous": false,4    "inputs": [5      {6        "indexed": true,7        "internalType": "address",8        "name": "owner",9        "type": "address"10      },11      {12        "indexed": true,13        "internalType": "address",14        "name": "approved",15        "type": "address"16      },17      {18        "indexed": true,19        "internalType": "uint256",20        "name": "tokenId",21        "type": "uint256"22      }23    ],24    "name": "Approval",25    "type": "event"26  },27  {28    "anonymous": false,29    "inputs": [30      {31        "indexed": true,32        "internalType": "address",33        "name": "owner",34        "type": "address"35      },36      {37        "indexed": true,38        "internalType": "address",39        "name": "operator",40        "type": "address"41      },42      {43        "indexed": false,44        "internalType": "bool",45        "name": "approved",46        "type": "bool"47      }48    ],49    "name": "ApprovalForAll",50    "type": "event"51  },52  {53    "anonymous": false,54    "inputs": [55      {56        "indexed": true,57        "internalType": "uint256",58        "name": "tokenId",59        "type": "uint256"60      }61    ],62    "name": "TokenChanged",63    "type": "event"64  },65  {66    "anonymous": false,67    "inputs": [68      {69        "indexed": true,70        "internalType": "address",71        "name": "from",72        "type": "address"73      },74      {75        "indexed": true,76        "internalType": "address",77        "name": "to",78        "type": "address"79      },80      {81        "indexed": true,82        "internalType": "uint256",83        "name": "tokenId",84        "type": "uint256"85      }86    ],87    "name": "Transfer",88    "type": "event"89  },90  {91    "inputs": [92      {93        "components": [94          { "internalType": "address", "name": "eth", "type": "address" },95          { "internalType": "uint256", "name": "sub", "type": "uint256" }96        ],97        "internalType": "struct CrossAddress",98        "name": "newAdmin",99        "type": "tuple"100      }101    ],102    "name": "addCollectionAdminCross",103    "outputs": [],104    "stateMutability": "nonpayable",105    "type": "function"106  },107  {108    "inputs": [109      {110        "components": [111          { "internalType": "address", "name": "eth", "type": "address" },112          { "internalType": "uint256", "name": "sub", "type": "uint256" }113        ],114        "internalType": "struct CrossAddress",115        "name": "user",116        "type": "tuple"117      }118    ],119    "name": "addToCollectionAllowListCross",120    "outputs": [],121    "stateMutability": "nonpayable",122    "type": "function"123  },124  {125    "inputs": [126      {127        "components": [128          { "internalType": "address", "name": "eth", "type": "address" },129          { "internalType": "uint256", "name": "sub", "type": "uint256" }130        ],131        "internalType": "struct CrossAddress",132        "name": "user",133        "type": "tuple"134      }135    ],136    "name": "allowlistedCross",137    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],138    "stateMutability": "view",139    "type": "function"140  },141  {142    "inputs": [143      { "internalType": "address", "name": "approved", "type": "address" },144      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }145    ],146    "name": "approve",147    "outputs": [],148    "stateMutability": "nonpayable",149    "type": "function"150  },151  {152    "inputs": [153      { "internalType": "address", "name": "owner", "type": "address" }154    ],155    "name": "balanceOf",156    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],157    "stateMutability": "view",158    "type": "function"159  },160  {161    "inputs": [162      {163        "components": [164          { "internalType": "address", "name": "eth", "type": "address" },165          { "internalType": "uint256", "name": "sub", "type": "uint256" }166        ],167        "internalType": "struct CrossAddress",168        "name": "owner",169        "type": "tuple"170      }171    ],172    "name": "balanceOfCross",173    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],174    "stateMutability": "view",175    "type": "function"176  },177  {178    "inputs": [179      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }180    ],181    "name": "burn",182    "outputs": [],183    "stateMutability": "nonpayable",184    "type": "function"185  },186  {187    "inputs": [188      {189        "components": [190          { "internalType": "address", "name": "eth", "type": "address" },191          { "internalType": "uint256", "name": "sub", "type": "uint256" }192        ],193        "internalType": "struct CrossAddress",194        "name": "from",195        "type": "tuple"196      },197      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }198    ],199    "name": "burnFromCross",200    "outputs": [],201    "stateMutability": "nonpayable",202    "type": "function"203  },204  {205    "inputs": [206      {207        "components": [208          { "internalType": "address", "name": "eth", "type": "address" },209          { "internalType": "uint256", "name": "sub", "type": "uint256" }210        ],211        "internalType": "struct CrossAddress",212        "name": "newOwner",213        "type": "tuple"214      }215    ],216    "name": "changeCollectionOwnerCross",217    "outputs": [],218    "stateMutability": "nonpayable",219    "type": "function"220  },221  {222    "inputs": [],223    "name": "collectionAdmins",224    "outputs": [225      {226        "components": [227          { "internalType": "address", "name": "eth", "type": "address" },228          { "internalType": "uint256", "name": "sub", "type": "uint256" }229        ],230        "internalType": "struct CrossAddress[]",231        "name": "",232        "type": "tuple[]"233      }234    ],235    "stateMutability": "view",236    "type": "function"237  },238  {239    "inputs": [],240    "name": "collectionHelperAddress",241    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],242    "stateMutability": "view",243    "type": "function"244  },245  {246    "inputs": [],247    "name": "collectionLimits",248    "outputs": [249      {250        "components": [251          {252            "internalType": "enum CollectionLimitField",253            "name": "field",254            "type": "uint8"255          },256          {257            "components": [258              { "internalType": "bool", "name": "status", "type": "bool" },259              { "internalType": "uint256", "name": "value", "type": "uint256" }260            ],261            "internalType": "struct OptionUint256",262            "name": "value",263            "type": "tuple"264          }265        ],266        "internalType": "struct CollectionLimit[]",267        "name": "",268        "type": "tuple[]"269      }270    ],271    "stateMutability": "view",272    "type": "function"273  },274  {275    "inputs": [],276    "name": "collectionNesting",277    "outputs": [278      {279        "components": [280          { "internalType": "bool", "name": "token_owner", "type": "bool" },281          {282            "internalType": "bool",283            "name": "collection_admin",284            "type": "bool"285          },286          {287            "internalType": "address[]",288            "name": "restricted",289            "type": "address[]"290          }291        ],292        "internalType": "struct CollectionNestingAndPermission",293        "name": "",294        "type": "tuple"295      }296    ],297    "stateMutability": "view",298    "type": "function"299  },300  {301    "inputs": [],302    "name": "collectionOwner",303    "outputs": [304      {305        "components": [306          { "internalType": "address", "name": "eth", "type": "address" },307          { "internalType": "uint256", "name": "sub", "type": "uint256" }308        ],309        "internalType": "struct CrossAddress",310        "name": "",311        "type": "tuple"312      }313    ],314    "stateMutability": "view",315    "type": "function"316  },317  {318    "inputs": [319      { "internalType": "string[]", "name": "keys", "type": "string[]" }320    ],321    "name": "collectionProperties",322    "outputs": [323      {324        "components": [325          { "internalType": "string", "name": "key", "type": "string" },326          { "internalType": "bytes", "name": "value", "type": "bytes" }327        ],328        "internalType": "struct Property[]",329        "name": "",330        "type": "tuple[]"331      }332    ],333    "stateMutability": "view",334    "type": "function"335  },336  {337    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],338    "name": "collectionProperty",339    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],340    "stateMutability": "view",341    "type": "function"342  },343  {344    "inputs": [],345    "name": "collectionSponsor",346    "outputs": [347      {348        "components": [349          { "internalType": "address", "name": "eth", "type": "address" },350          { "internalType": "uint256", "name": "sub", "type": "uint256" }351        ],352        "internalType": "struct CrossAddress",353        "name": "",354        "type": "tuple"355      }356    ],357    "stateMutability": "view",358    "type": "function"359  },360  {361    "inputs": [],362    "name": "confirmCollectionSponsorship",363    "outputs": [],364    "stateMutability": "nonpayable",365    "type": "function"366  },367  {368    "inputs": [],369    "name": "contractAddress",370    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],371    "stateMutability": "view",372    "type": "function"373  },374  {375    "inputs": [376      { "internalType": "string[]", "name": "keys", "type": "string[]" }377    ],378    "name": "deleteCollectionProperties",379    "outputs": [],380    "stateMutability": "nonpayable",381    "type": "function"382  },383  {384    "inputs": [385      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },386      { "internalType": "string[]", "name": "keys", "type": "string[]" }387    ],388    "name": "deleteProperties",389    "outputs": [],390    "stateMutability": "nonpayable",391    "type": "function"392  },393  {394    "inputs": [],395    "name": "description",396    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],397    "stateMutability": "view",398    "type": "function"399  },400  {401    "inputs": [402      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }403    ],404    "name": "getApproved",405    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],406    "stateMutability": "view",407    "type": "function"408  },409  {410    "inputs": [],411    "name": "hasCollectionPendingSponsor",412    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],413    "stateMutability": "view",414    "type": "function"415  },416  {417    "inputs": [418      { "internalType": "address", "name": "owner", "type": "address" },419      { "internalType": "address", "name": "operator", "type": "address" }420    ],421    "name": "isApprovedForAll",422    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],423    "stateMutability": "view",424    "type": "function"425  },426  {427    "inputs": [428      {429        "components": [430          { "internalType": "address", "name": "eth", "type": "address" },431          { "internalType": "uint256", "name": "sub", "type": "uint256" }432        ],433        "internalType": "struct CrossAddress",434        "name": "user",435        "type": "tuple"436      }437    ],438    "name": "isOwnerOrAdminCross",439    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],440    "stateMutability": "view",441    "type": "function"442  },443  {444    "inputs": [{ "internalType": "address", "name": "to", "type": "address" }],445    "name": "mint",446    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],447    "stateMutability": "nonpayable",448    "type": "function"449  },450  {451    "inputs": [452      {453        "components": [454          {455            "components": [456              { "internalType": "address", "name": "eth", "type": "address" },457              { "internalType": "uint256", "name": "sub", "type": "uint256" }458            ],459            "internalType": "struct CrossAddress",460            "name": "owner",461            "type": "tuple"462          },463          {464            "components": [465              { "internalType": "string", "name": "key", "type": "string" },466              { "internalType": "bytes", "name": "value", "type": "bytes" }467            ],468            "internalType": "struct Property[]",469            "name": "properties",470            "type": "tuple[]"471          }472        ],473        "internalType": "struct MintTokenData[]",474        "name": "tokenProperties",475        "type": "tuple[]"476      }477    ],478    "name": "mintBulkCross",479    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],480    "stateMutability": "nonpayable",481    "type": "function"482  },483  {484    "inputs": [485      {486        "components": [487          { "internalType": "address", "name": "eth", "type": "address" },488          { "internalType": "uint256", "name": "sub", "type": "uint256" }489        ],490        "internalType": "struct CrossAddress",491        "name": "to",492        "type": "tuple"493      },494      {495        "components": [496          { "internalType": "string", "name": "key", "type": "string" },497          { "internalType": "bytes", "name": "value", "type": "bytes" }498        ],499        "internalType": "struct Property[]",500        "name": "properties",501        "type": "tuple[]"502      }503    ],504    "name": "mintCross",505    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],506    "stateMutability": "nonpayable",507    "type": "function"508  },509  {510    "inputs": [511      { "internalType": "address", "name": "to", "type": "address" },512      { "internalType": "string", "name": "tokenUri", "type": "string" }513    ],514    "name": "mintWithTokenURI",515    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],516    "stateMutability": "nonpayable",517    "type": "function"518  },519  {520    "inputs": [],521    "name": "name",522    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],523    "stateMutability": "view",524    "type": "function"525  },526  {527    "inputs": [],528    "name": "nextTokenId",529    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],530    "stateMutability": "view",531    "type": "function"532  },533  {534    "inputs": [535      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }536    ],537    "name": "ownerOf",538    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],539    "stateMutability": "view",540    "type": "function"541  },542  {543    "inputs": [544      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }545    ],546    "name": "ownerOfCross",547    "outputs": [548      {549        "components": [550          { "internalType": "address", "name": "eth", "type": "address" },551          { "internalType": "uint256", "name": "sub", "type": "uint256" }552        ],553        "internalType": "struct CrossAddress",554        "name": "",555        "type": "tuple"556      }557    ],558    "stateMutability": "view",559    "type": "function"560  },561  {562    "inputs": [563      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },564      { "internalType": "string[]", "name": "keys", "type": "string[]" }565    ],566    "name": "properties",567    "outputs": [568      {569        "components": [570          { "internalType": "string", "name": "key", "type": "string" },571          { "internalType": "bytes", "name": "value", "type": "bytes" }572        ],573        "internalType": "struct Property[]",574        "name": "",575        "type": "tuple[]"576      }577    ],578    "stateMutability": "view",579    "type": "function"580  },581  {582    "inputs": [583      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },584      { "internalType": "string", "name": "key", "type": "string" }585    ],586    "name": "property",587    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],588    "stateMutability": "view",589    "type": "function"590  },591  {592    "inputs": [593      {594        "components": [595          { "internalType": "address", "name": "eth", "type": "address" },596          { "internalType": "uint256", "name": "sub", "type": "uint256" }597        ],598        "internalType": "struct CrossAddress",599        "name": "admin",600        "type": "tuple"601      }602    ],603    "name": "removeCollectionAdminCross",604    "outputs": [],605    "stateMutability": "nonpayable",606    "type": "function"607  },608  {609    "inputs": [],610    "name": "removeCollectionSponsor",611    "outputs": [],612    "stateMutability": "nonpayable",613    "type": "function"614  },615  {616    "inputs": [617      {618        "components": [619          { "internalType": "address", "name": "eth", "type": "address" },620          { "internalType": "uint256", "name": "sub", "type": "uint256" }621        ],622        "internalType": "struct CrossAddress",623        "name": "user",624        "type": "tuple"625      }626    ],627    "name": "removeFromCollectionAllowListCross",628    "outputs": [],629    "stateMutability": "nonpayable",630    "type": "function"631  },632  {633    "inputs": [634      { "internalType": "address", "name": "from", "type": "address" },635      { "internalType": "address", "name": "to", "type": "address" },636      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }637    ],638    "name": "safeTransferFrom",639    "outputs": [],640    "stateMutability": "nonpayable",641    "type": "function"642  },643  {644    "inputs": [645      { "internalType": "address", "name": "from", "type": "address" },646      { "internalType": "address", "name": "to", "type": "address" },647      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },648      { "internalType": "bytes", "name": "data", "type": "bytes" }649    ],650    "name": "safeTransferFrom",651    "outputs": [],652    "stateMutability": "nonpayable",653    "type": "function"654  },655  {656    "inputs": [657      { "internalType": "address", "name": "operator", "type": "address" },658      { "internalType": "bool", "name": "approved", "type": "bool" }659    ],660    "name": "setApprovalForAll",661    "outputs": [],662    "stateMutability": "nonpayable",663    "type": "function"664  },665  {666    "inputs": [667      { "internalType": "enum AccessMode", "name": "mode", "type": "uint8" }668    ],669    "name": "setCollectionAccess",670    "outputs": [],671    "stateMutability": "nonpayable",672    "type": "function"673  },674  {675    "inputs": [676      {677        "components": [678          {679            "internalType": "enum CollectionLimitField",680            "name": "field",681            "type": "uint8"682          },683          {684            "components": [685              { "internalType": "bool", "name": "status", "type": "bool" },686              { "internalType": "uint256", "name": "value", "type": "uint256" }687            ],688            "internalType": "struct OptionUint256",689            "name": "value",690            "type": "tuple"691          }692        ],693        "internalType": "struct CollectionLimit",694        "name": "limit",695        "type": "tuple"696      }697    ],698    "name": "setCollectionLimit",699    "outputs": [],700    "stateMutability": "nonpayable",701    "type": "function"702  },703  {704    "inputs": [{ "internalType": "bool", "name": "mode", "type": "bool" }],705    "name": "setCollectionMintMode",706    "outputs": [],707    "stateMutability": "nonpayable",708    "type": "function"709  },710  {711    "inputs": [712      {713        "components": [714          { "internalType": "bool", "name": "token_owner", "type": "bool" },715          {716            "internalType": "bool",717            "name": "collection_admin",718            "type": "bool"719          },720          {721            "internalType": "address[]",722            "name": "restricted",723            "type": "address[]"724          }725        ],726        "internalType": "struct CollectionNestingAndPermission",727        "name": "collectionNestingAndPermissions",728        "type": "tuple"729      }730    ],731    "name": "setCollectionNesting",732    "outputs": [],733    "stateMutability": "nonpayable",734    "type": "function"735  },736  {737    "inputs": [738      {739        "components": [740          { "internalType": "string", "name": "key", "type": "string" },741          { "internalType": "bytes", "name": "value", "type": "bytes" }742        ],743        "internalType": "struct Property[]",744        "name": "properties",745        "type": "tuple[]"746      }747    ],748    "name": "setCollectionProperties",749    "outputs": [],750    "stateMutability": "nonpayable",751    "type": "function"752  },753  {754    "inputs": [755      {756        "components": [757          { "internalType": "address", "name": "eth", "type": "address" },758          { "internalType": "uint256", "name": "sub", "type": "uint256" }759        ],760        "internalType": "struct CrossAddress",761        "name": "sponsor",762        "type": "tuple"763      }764    ],765    "name": "setCollectionSponsorCross",766    "outputs": [],767    "stateMutability": "nonpayable",768    "type": "function"769  },770  {771    "inputs": [772      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },773      {774        "components": [775          { "internalType": "string", "name": "key", "type": "string" },776          { "internalType": "bytes", "name": "value", "type": "bytes" }777        ],778        "internalType": "struct Property[]",779        "name": "properties",780        "type": "tuple[]"781      }782    ],783    "name": "setProperties",784    "outputs": [],785    "stateMutability": "nonpayable",786    "type": "function"787  },788  {789    "inputs": [790      {791        "components": [792          { "internalType": "string", "name": "key", "type": "string" },793          {794            "components": [795              {796                "internalType": "enum TokenPermissionField",797                "name": "code",798                "type": "uint8"799              },800              { "internalType": "bool", "name": "value", "type": "bool" }801            ],802            "internalType": "struct PropertyPermission[]",803            "name": "permissions",804            "type": "tuple[]"805          }806        ],807        "internalType": "struct TokenPropertyPermission[]",808        "name": "permissions",809        "type": "tuple[]"810      }811    ],812    "name": "setTokenPropertyPermissions",813    "outputs": [],814    "stateMutability": "nonpayable",815    "type": "function"816  },817  {818    "inputs": [819      { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }820    ],821    "name": "supportsInterface",822    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],823    "stateMutability": "view",824    "type": "function"825  },826  {827    "inputs": [],828    "name": "symbol",829    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],830    "stateMutability": "view",831    "type": "function"832  },833  {834    "inputs": [835      { "internalType": "uint256", "name": "index", "type": "uint256" }836    ],837    "name": "tokenByIndex",838    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],839    "stateMutability": "view",840    "type": "function"841  },842  {843    "inputs": [844      { "internalType": "uint256", "name": "token", "type": "uint256" }845    ],846    "name": "tokenContractAddress",847    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],848    "stateMutability": "view",849    "type": "function"850  },851  {852    "inputs": [853      { "internalType": "address", "name": "owner", "type": "address" },854      { "internalType": "uint256", "name": "index", "type": "uint256" }855    ],856    "name": "tokenOfOwnerByIndex",857    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],858    "stateMutability": "view",859    "type": "function"860  },861  {862    "inputs": [],863    "name": "tokenPropertyPermissions",864    "outputs": [865      {866        "components": [867          { "internalType": "string", "name": "key", "type": "string" },868          {869            "components": [870              {871                "internalType": "enum TokenPermissionField",872                "name": "code",873                "type": "uint8"874              },875              { "internalType": "bool", "name": "value", "type": "bool" }876            ],877            "internalType": "struct PropertyPermission[]",878            "name": "permissions",879            "type": "tuple[]"880          }881        ],882        "internalType": "struct TokenPropertyPermission[]",883        "name": "",884        "type": "tuple[]"885      }886    ],887    "stateMutability": "view",888    "type": "function"889  },890  {891    "inputs": [892      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }893    ],894    "name": "tokenURI",895    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],896    "stateMutability": "view",897    "type": "function"898  },899  {900    "inputs": [],901    "name": "totalSupply",902    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],903    "stateMutability": "view",904    "type": "function"905  },906  {907    "inputs": [908      { "internalType": "address", "name": "to", "type": "address" },909      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }910    ],911    "name": "transfer",912    "outputs": [],913    "stateMutability": "nonpayable",914    "type": "function"915  },916  {917    "inputs": [918      {919        "components": [920          { "internalType": "address", "name": "eth", "type": "address" },921          { "internalType": "uint256", "name": "sub", "type": "uint256" }922        ],923        "internalType": "struct CrossAddress",924        "name": "to",925        "type": "tuple"926      },927      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }928    ],929    "name": "transferCross",930    "outputs": [],931    "stateMutability": "nonpayable",932    "type": "function"933  },934  {935    "inputs": [936      { "internalType": "address", "name": "from", "type": "address" },937      { "internalType": "address", "name": "to", "type": "address" },938      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }939    ],940    "name": "transferFrom",941    "outputs": [],942    "stateMutability": "nonpayable",943    "type": "function"944  },945  {946    "inputs": [947      {948        "components": [949          { "internalType": "address", "name": "eth", "type": "address" },950          { "internalType": "uint256", "name": "sub", "type": "uint256" }951        ],952        "internalType": "struct CrossAddress",953        "name": "from",954        "type": "tuple"955      },956      {957        "components": [958          { "internalType": "address", "name": "eth", "type": "address" },959          { "internalType": "uint256", "name": "sub", "type": "uint256" }960        ],961        "internalType": "struct CrossAddress",962        "name": "to",963        "type": "tuple"964      },965      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }966    ],967    "name": "transferFromCross",968    "outputs": [],969    "stateMutability": "nonpayable",970    "type": "function"971  },972  {973    "inputs": [],974    "name": "uniqueCollectionType",975    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],976    "stateMutability": "view",977    "type": "function"978  }979]
modifiedtests/src/eth/api/CollectionHelpers.soldiffbeforeafterboth
--- a/tests/src/eth/api/CollectionHelpers.sol
+++ b/tests/src/eth/api/CollectionHelpers.sol
@@ -20,12 +20,12 @@
 }
 
 /// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x4135fff1
+/// @dev the ERC-165 identifier for this interface is 0x94e5af0d
 interface CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
 	/// Create a collection
 	/// @return address Address of the newly created collection
-	/// @dev EVM selector for this function is: 0xa765ee5b,
-	///  or in textual repr: createCollection(((address,uint256),string,string,string,uint8,uint8,(string,bytes)[],(string,(uint8,bool)[])[],(address,uint256)[],(bool,bool,address[]),(uint8,uint256)[],uint8))
+	/// @dev EVM selector for this function is: 0x72b5bea7,
+	///  or in textual repr: createCollection((string,string,string,uint8,uint8,(string,bytes)[],(string,(uint8,bool)[])[],(address,uint256)[],(bool,bool,address[]),(uint8,uint256)[],(address,uint256),uint8))
 	function createCollection(CreateCollectionData memory data) external payable returns (address);
 
 	/// Create an NFT collection
@@ -103,8 +103,6 @@
 
 /// Collection properties
 struct CreateCollectionData {
-	/// Collection sponsor
-	CrossAddress pending_sponsor;
 	/// Collection name
 	string name;
 	/// Collection description
@@ -125,11 +123,12 @@
 	CollectionNestingAndPermission nesting_settings;
 	/// Collection limits
 	CollectionLimitValue[] limits;
+	/// Collection sponsor
+	CrossAddress pending_sponsor;
 	/// Extra collection flags
 	CollectionFlags flags;
 }
 
-/// Cross account struct
 type CollectionFlags is uint8;
 
 library CollectionFlagsLib {
@@ -140,13 +139,19 @@
 	/// External collections can't be managed using `unique` api
 	CollectionFlags constant externalField = CollectionFlags.wrap(1);
 
-	/// Reserved bits
+	/// Reserved flags
 	function reservedField(uint8 value) public pure returns (CollectionFlags) {
 		require(value < 1 << 5, "out of bound value");
 		return CollectionFlags.wrap(value << 1);
 	}
 }
 
+/// Cross account struct
+struct CrossAddress {
+	address eth;
+	uint256 sub;
+}
+
 /// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM.
 struct CollectionLimitValue {
 	CollectionLimitField field;
@@ -183,12 +188,6 @@
 	bool collection_admin;
 	/// If set - only tokens from specified collections can be nested.
 	address[] restricted;
-}
-
-/// Cross account struct
-struct CrossAddress {
-	address eth;
-	uint256 sub;
 }
 
 /// Ethereum representation of Token Property Permissions.
@@ -225,10 +224,10 @@
 
 /// Type of tokens in collection
 enum CollectionMode {
-	/// Fungible
-	Fungible,
 	/// Nonfungible
 	Nonfungible,
+	/// Fungible
+	Fungible,
 	/// Refungible
 	Refungible
 }
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -551,7 +551,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x307b061a
+/// @dev the ERC-165 identifier for this interface is 0x9b397d16
 interface ERC721UniqueExtensions is Dummy, ERC165 {
 	/// @notice A descriptive name for a collection of NFTs in this contract
 	/// @dev EVM selector for this function is: 0x06fdde03,
@@ -674,6 +674,12 @@
 	// ///  or in textual repr: mintBulk(address,uint256[])
 	// function mintBulk(address to, uint256[] memory tokenIds) external returns (bool);
 
+	/// @notice Function to mint a token.
+	/// @param data Array of pairs of token owner and token's properties for minted token
+	/// @dev EVM selector for this function is: 0xab427b0c,
+	///  or in textual repr: mintBulkCross(((address,uint256),(string,bytes)[])[])
+	function mintBulkCross(MintTokenData[] memory data) external returns (bool);
+
 	// /// @notice Function to mint multiple tokens with the given tokenUris.
 	// /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
 	// ///  numbers and first number should be obtained with `nextTokenId` method
@@ -705,6 +711,11 @@
 	string uri;
 }
 
+struct MintTokenData {
+	CrossAddress owner;
+	Property[] properties;
+}
+
 /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 /// @dev See https://eips.ethereum.org/EIPS/eip-721
 /// @dev the ERC-165 identifier for this interface is 0x780e9d63
modifiedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -551,7 +551,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x95c0f66c
+/// @dev the ERC-165 identifier for this interface is 0x3e828d60
 interface ERC721UniqueExtensions is Dummy, ERC165 {
 	/// @notice A descriptive name for a collection of NFTs in this contract
 	/// @dev EVM selector for this function is: 0x06fdde03,
@@ -668,6 +668,12 @@
 	// ///  or in textual repr: mintBulk(address,uint256[])
 	// function mintBulk(address to, uint256[] memory tokenIds) external returns (bool);
 
+	/// @notice Function to mint a token.
+	/// @param tokenProperties Properties of minted token
+	/// @dev EVM selector for this function is: 0xab427b0c,
+	///  or in textual repr: mintBulkCross(((address,uint256),(string,bytes)[])[])
+	function mintBulkCross(MintTokenData[] memory tokenProperties) external returns (bool);
+
 	// /// @notice Function to mint multiple tokens with the given tokenUris.
 	// /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
 	// ///  numbers and first number should be obtained with `nextTokenId` method
@@ -706,6 +712,11 @@
 	string uri;
 }
 
+struct MintTokenData {
+	CrossAddress owner;
+	Property[] properties;
+}
+
 /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 /// @dev See https://eips.ethereum.org/EIPS/eip-721
 /// @dev the ERC-165 identifier for this interface is 0x780e9d63