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
before · tests/src/eth/abi/nonFungible.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      {154        "components": [155          { "internalType": "address", "name": "eth", "type": "address" },156          { "internalType": "uint256", "name": "sub", "type": "uint256" }157        ],158        "internalType": "struct CrossAddress",159        "name": "approved",160        "type": "tuple"161      },162      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }163    ],164    "name": "approveCross",165    "outputs": [],166    "stateMutability": "nonpayable",167    "type": "function"168  },169  {170    "inputs": [171      { "internalType": "address", "name": "owner", "type": "address" }172    ],173    "name": "balanceOf",174    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],175    "stateMutability": "view",176    "type": "function"177  },178  {179    "inputs": [180      {181        "components": [182          { "internalType": "address", "name": "eth", "type": "address" },183          { "internalType": "uint256", "name": "sub", "type": "uint256" }184        ],185        "internalType": "struct CrossAddress",186        "name": "owner",187        "type": "tuple"188      }189    ],190    "name": "balanceOfCross",191    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],192    "stateMutability": "view",193    "type": "function"194  },195  {196    "inputs": [197      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }198    ],199    "name": "burn",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": "from",213        "type": "tuple"214      },215      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }216    ],217    "name": "burnFromCross",218    "outputs": [],219    "stateMutability": "nonpayable",220    "type": "function"221  },222  {223    "inputs": [224      {225        "components": [226          { "internalType": "address", "name": "eth", "type": "address" },227          { "internalType": "uint256", "name": "sub", "type": "uint256" }228        ],229        "internalType": "struct CrossAddress",230        "name": "newOwner",231        "type": "tuple"232      }233    ],234    "name": "changeCollectionOwnerCross",235    "outputs": [],236    "stateMutability": "nonpayable",237    "type": "function"238  },239  {240    "inputs": [],241    "name": "collectionAdmins",242    "outputs": [243      {244        "components": [245          { "internalType": "address", "name": "eth", "type": "address" },246          { "internalType": "uint256", "name": "sub", "type": "uint256" }247        ],248        "internalType": "struct CrossAddress[]",249        "name": "",250        "type": "tuple[]"251      }252    ],253    "stateMutability": "view",254    "type": "function"255  },256  {257    "inputs": [],258    "name": "collectionHelperAddress",259    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],260    "stateMutability": "view",261    "type": "function"262  },263  {264    "inputs": [],265    "name": "collectionLimits",266    "outputs": [267      {268        "components": [269          {270            "internalType": "enum CollectionLimitField",271            "name": "field",272            "type": "uint8"273          },274          {275            "components": [276              { "internalType": "bool", "name": "status", "type": "bool" },277              { "internalType": "uint256", "name": "value", "type": "uint256" }278            ],279            "internalType": "struct OptionUint256",280            "name": "value",281            "type": "tuple"282          }283        ],284        "internalType": "struct CollectionLimit[]",285        "name": "",286        "type": "tuple[]"287      }288    ],289    "stateMutability": "view",290    "type": "function"291  },292  {293    "inputs": [],294    "name": "collectionNesting",295    "outputs": [296      {297        "components": [298          { "internalType": "bool", "name": "token_owner", "type": "bool" },299          {300            "internalType": "bool",301            "name": "collection_admin",302            "type": "bool"303          },304          {305            "internalType": "address[]",306            "name": "restricted",307            "type": "address[]"308          }309        ],310        "internalType": "struct CollectionNestingAndPermission",311        "name": "",312        "type": "tuple"313      }314    ],315    "stateMutability": "view",316    "type": "function"317  },318  {319    "inputs": [],320    "name": "collectionOwner",321    "outputs": [322      {323        "components": [324          { "internalType": "address", "name": "eth", "type": "address" },325          { "internalType": "uint256", "name": "sub", "type": "uint256" }326        ],327        "internalType": "struct CrossAddress",328        "name": "",329        "type": "tuple"330      }331    ],332    "stateMutability": "view",333    "type": "function"334  },335  {336    "inputs": [337      { "internalType": "string[]", "name": "keys", "type": "string[]" }338    ],339    "name": "collectionProperties",340    "outputs": [341      {342        "components": [343          { "internalType": "string", "name": "key", "type": "string" },344          { "internalType": "bytes", "name": "value", "type": "bytes" }345        ],346        "internalType": "struct Property[]",347        "name": "",348        "type": "tuple[]"349      }350    ],351    "stateMutability": "view",352    "type": "function"353  },354  {355    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],356    "name": "collectionProperty",357    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],358    "stateMutability": "view",359    "type": "function"360  },361  {362    "inputs": [],363    "name": "collectionSponsor",364    "outputs": [365      {366        "components": [367          { "internalType": "address", "name": "eth", "type": "address" },368          { "internalType": "uint256", "name": "sub", "type": "uint256" }369        ],370        "internalType": "struct CrossAddress",371        "name": "",372        "type": "tuple"373      }374    ],375    "stateMutability": "view",376    "type": "function"377  },378  {379    "inputs": [],380    "name": "confirmCollectionSponsorship",381    "outputs": [],382    "stateMutability": "nonpayable",383    "type": "function"384  },385  {386    "inputs": [],387    "name": "contractAddress",388    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],389    "stateMutability": "view",390    "type": "function"391  },392  {393    "inputs": [394      { "internalType": "string[]", "name": "keys", "type": "string[]" }395    ],396    "name": "deleteCollectionProperties",397    "outputs": [],398    "stateMutability": "nonpayable",399    "type": "function"400  },401  {402    "inputs": [403      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },404      { "internalType": "string[]", "name": "keys", "type": "string[]" }405    ],406    "name": "deleteProperties",407    "outputs": [],408    "stateMutability": "nonpayable",409    "type": "function"410  },411  {412    "inputs": [],413    "name": "description",414    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],415    "stateMutability": "view",416    "type": "function"417  },418  {419    "inputs": [420      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }421    ],422    "name": "getApproved",423    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],424    "stateMutability": "view",425    "type": "function"426  },427  {428    "inputs": [],429    "name": "hasCollectionPendingSponsor",430    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],431    "stateMutability": "view",432    "type": "function"433  },434  {435    "inputs": [436      { "internalType": "address", "name": "owner", "type": "address" },437      { "internalType": "address", "name": "operator", "type": "address" }438    ],439    "name": "isApprovedForAll",440    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],441    "stateMutability": "view",442    "type": "function"443  },444  {445    "inputs": [446      {447        "components": [448          { "internalType": "address", "name": "eth", "type": "address" },449          { "internalType": "uint256", "name": "sub", "type": "uint256" }450        ],451        "internalType": "struct CrossAddress",452        "name": "user",453        "type": "tuple"454      }455    ],456    "name": "isOwnerOrAdminCross",457    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],458    "stateMutability": "view",459    "type": "function"460  },461  {462    "inputs": [{ "internalType": "address", "name": "to", "type": "address" }],463    "name": "mint",464    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],465    "stateMutability": "nonpayable",466    "type": "function"467  },468  {469    "inputs": [470      {471        "components": [472          { "internalType": "address", "name": "eth", "type": "address" },473          { "internalType": "uint256", "name": "sub", "type": "uint256" }474        ],475        "internalType": "struct CrossAddress",476        "name": "to",477        "type": "tuple"478      },479      {480        "components": [481          { "internalType": "string", "name": "key", "type": "string" },482          { "internalType": "bytes", "name": "value", "type": "bytes" }483        ],484        "internalType": "struct Property[]",485        "name": "properties",486        "type": "tuple[]"487      }488    ],489    "name": "mintCross",490    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],491    "stateMutability": "nonpayable",492    "type": "function"493  },494  {495    "inputs": [496      { "internalType": "address", "name": "to", "type": "address" },497      { "internalType": "string", "name": "tokenUri", "type": "string" }498    ],499    "name": "mintWithTokenURI",500    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],501    "stateMutability": "nonpayable",502    "type": "function"503  },504  {505    "inputs": [],506    "name": "name",507    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],508    "stateMutability": "view",509    "type": "function"510  },511  {512    "inputs": [],513    "name": "nextTokenId",514    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],515    "stateMutability": "view",516    "type": "function"517  },518  {519    "inputs": [520      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }521    ],522    "name": "ownerOf",523    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],524    "stateMutability": "view",525    "type": "function"526  },527  {528    "inputs": [529      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }530    ],531    "name": "ownerOfCross",532    "outputs": [533      {534        "components": [535          { "internalType": "address", "name": "eth", "type": "address" },536          { "internalType": "uint256", "name": "sub", "type": "uint256" }537        ],538        "internalType": "struct CrossAddress",539        "name": "",540        "type": "tuple"541      }542    ],543    "stateMutability": "view",544    "type": "function"545  },546  {547    "inputs": [548      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },549      { "internalType": "string[]", "name": "keys", "type": "string[]" }550    ],551    "name": "properties",552    "outputs": [553      {554        "components": [555          { "internalType": "string", "name": "key", "type": "string" },556          { "internalType": "bytes", "name": "value", "type": "bytes" }557        ],558        "internalType": "struct Property[]",559        "name": "",560        "type": "tuple[]"561      }562    ],563    "stateMutability": "view",564    "type": "function"565  },566  {567    "inputs": [568      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },569      { "internalType": "string", "name": "key", "type": "string" }570    ],571    "name": "property",572    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],573    "stateMutability": "view",574    "type": "function"575  },576  {577    "inputs": [578      {579        "components": [580          { "internalType": "address", "name": "eth", "type": "address" },581          { "internalType": "uint256", "name": "sub", "type": "uint256" }582        ],583        "internalType": "struct CrossAddress",584        "name": "admin",585        "type": "tuple"586      }587    ],588    "name": "removeCollectionAdminCross",589    "outputs": [],590    "stateMutability": "nonpayable",591    "type": "function"592  },593  {594    "inputs": [],595    "name": "removeCollectionSponsor",596    "outputs": [],597    "stateMutability": "nonpayable",598    "type": "function"599  },600  {601    "inputs": [602      {603        "components": [604          { "internalType": "address", "name": "eth", "type": "address" },605          { "internalType": "uint256", "name": "sub", "type": "uint256" }606        ],607        "internalType": "struct CrossAddress",608        "name": "user",609        "type": "tuple"610      }611    ],612    "name": "removeFromCollectionAllowListCross",613    "outputs": [],614    "stateMutability": "nonpayable",615    "type": "function"616  },617  {618    "inputs": [619      { "internalType": "address", "name": "from", "type": "address" },620      { "internalType": "address", "name": "to", "type": "address" },621      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }622    ],623    "name": "safeTransferFrom",624    "outputs": [],625    "stateMutability": "nonpayable",626    "type": "function"627  },628  {629    "inputs": [630      { "internalType": "address", "name": "from", "type": "address" },631      { "internalType": "address", "name": "to", "type": "address" },632      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },633      { "internalType": "bytes", "name": "data", "type": "bytes" }634    ],635    "name": "safeTransferFrom",636    "outputs": [],637    "stateMutability": "nonpayable",638    "type": "function"639  },640  {641    "inputs": [642      { "internalType": "address", "name": "operator", "type": "address" },643      { "internalType": "bool", "name": "approved", "type": "bool" }644    ],645    "name": "setApprovalForAll",646    "outputs": [],647    "stateMutability": "nonpayable",648    "type": "function"649  },650  {651    "inputs": [652      { "internalType": "enum AccessMode", "name": "mode", "type": "uint8" }653    ],654    "name": "setCollectionAccess",655    "outputs": [],656    "stateMutability": "nonpayable",657    "type": "function"658  },659  {660    "inputs": [661      {662        "components": [663          {664            "internalType": "enum CollectionLimitField",665            "name": "field",666            "type": "uint8"667          },668          {669            "components": [670              { "internalType": "bool", "name": "status", "type": "bool" },671              { "internalType": "uint256", "name": "value", "type": "uint256" }672            ],673            "internalType": "struct OptionUint256",674            "name": "value",675            "type": "tuple"676          }677        ],678        "internalType": "struct CollectionLimit",679        "name": "limit",680        "type": "tuple"681      }682    ],683    "name": "setCollectionLimit",684    "outputs": [],685    "stateMutability": "nonpayable",686    "type": "function"687  },688  {689    "inputs": [{ "internalType": "bool", "name": "mode", "type": "bool" }],690    "name": "setCollectionMintMode",691    "outputs": [],692    "stateMutability": "nonpayable",693    "type": "function"694  },695  {696    "inputs": [697      {698        "components": [699          { "internalType": "bool", "name": "token_owner", "type": "bool" },700          {701            "internalType": "bool",702            "name": "collection_admin",703            "type": "bool"704          },705          {706            "internalType": "address[]",707            "name": "restricted",708            "type": "address[]"709          }710        ],711        "internalType": "struct CollectionNestingAndPermission",712        "name": "collectionNestingAndPermissions",713        "type": "tuple"714      }715    ],716    "name": "setCollectionNesting",717    "outputs": [],718    "stateMutability": "nonpayable",719    "type": "function"720  },721  {722    "inputs": [723      {724        "components": [725          { "internalType": "string", "name": "key", "type": "string" },726          { "internalType": "bytes", "name": "value", "type": "bytes" }727        ],728        "internalType": "struct Property[]",729        "name": "properties",730        "type": "tuple[]"731      }732    ],733    "name": "setCollectionProperties",734    "outputs": [],735    "stateMutability": "nonpayable",736    "type": "function"737  },738  {739    "inputs": [740      {741        "components": [742          { "internalType": "address", "name": "eth", "type": "address" },743          { "internalType": "uint256", "name": "sub", "type": "uint256" }744        ],745        "internalType": "struct CrossAddress",746        "name": "sponsor",747        "type": "tuple"748      }749    ],750    "name": "setCollectionSponsorCross",751    "outputs": [],752    "stateMutability": "nonpayable",753    "type": "function"754  },755  {756    "inputs": [757      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },758      {759        "components": [760          { "internalType": "string", "name": "key", "type": "string" },761          { "internalType": "bytes", "name": "value", "type": "bytes" }762        ],763        "internalType": "struct Property[]",764        "name": "properties",765        "type": "tuple[]"766      }767    ],768    "name": "setProperties",769    "outputs": [],770    "stateMutability": "nonpayable",771    "type": "function"772  },773  {774    "inputs": [775      {776        "components": [777          { "internalType": "string", "name": "key", "type": "string" },778          {779            "components": [780              {781                "internalType": "enum TokenPermissionField",782                "name": "code",783                "type": "uint8"784              },785              { "internalType": "bool", "name": "value", "type": "bool" }786            ],787            "internalType": "struct PropertyPermission[]",788            "name": "permissions",789            "type": "tuple[]"790          }791        ],792        "internalType": "struct TokenPropertyPermission[]",793        "name": "permissions",794        "type": "tuple[]"795      }796    ],797    "name": "setTokenPropertyPermissions",798    "outputs": [],799    "stateMutability": "nonpayable",800    "type": "function"801  },802  {803    "inputs": [804      { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }805    ],806    "name": "supportsInterface",807    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],808    "stateMutability": "view",809    "type": "function"810  },811  {812    "inputs": [],813    "name": "symbol",814    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],815    "stateMutability": "view",816    "type": "function"817  },818  {819    "inputs": [820      { "internalType": "uint256", "name": "index", "type": "uint256" }821    ],822    "name": "tokenByIndex",823    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],824    "stateMutability": "view",825    "type": "function"826  },827  {828    "inputs": [829      { "internalType": "address", "name": "owner", "type": "address" },830      { "internalType": "uint256", "name": "index", "type": "uint256" }831    ],832    "name": "tokenOfOwnerByIndex",833    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],834    "stateMutability": "view",835    "type": "function"836  },837  {838    "inputs": [],839    "name": "tokenPropertyPermissions",840    "outputs": [841      {842        "components": [843          { "internalType": "string", "name": "key", "type": "string" },844          {845            "components": [846              {847                "internalType": "enum TokenPermissionField",848                "name": "code",849                "type": "uint8"850              },851              { "internalType": "bool", "name": "value", "type": "bool" }852            ],853            "internalType": "struct PropertyPermission[]",854            "name": "permissions",855            "type": "tuple[]"856          }857        ],858        "internalType": "struct TokenPropertyPermission[]",859        "name": "",860        "type": "tuple[]"861      }862    ],863    "stateMutability": "view",864    "type": "function"865  },866  {867    "inputs": [868      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }869    ],870    "name": "tokenURI",871    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],872    "stateMutability": "view",873    "type": "function"874  },875  {876    "inputs": [],877    "name": "totalSupply",878    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],879    "stateMutability": "view",880    "type": "function"881  },882  {883    "inputs": [884      { "internalType": "address", "name": "to", "type": "address" },885      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }886    ],887    "name": "transfer",888    "outputs": [],889    "stateMutability": "nonpayable",890    "type": "function"891  },892  {893    "inputs": [894      {895        "components": [896          { "internalType": "address", "name": "eth", "type": "address" },897          { "internalType": "uint256", "name": "sub", "type": "uint256" }898        ],899        "internalType": "struct CrossAddress",900        "name": "to",901        "type": "tuple"902      },903      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }904    ],905    "name": "transferCross",906    "outputs": [],907    "stateMutability": "nonpayable",908    "type": "function"909  },910  {911    "inputs": [912      { "internalType": "address", "name": "from", "type": "address" },913      { "internalType": "address", "name": "to", "type": "address" },914      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }915    ],916    "name": "transferFrom",917    "outputs": [],918    "stateMutability": "nonpayable",919    "type": "function"920  },921  {922    "inputs": [923      {924        "components": [925          { "internalType": "address", "name": "eth", "type": "address" },926          { "internalType": "uint256", "name": "sub", "type": "uint256" }927        ],928        "internalType": "struct CrossAddress",929        "name": "from",930        "type": "tuple"931      },932      {933        "components": [934          { "internalType": "address", "name": "eth", "type": "address" },935          { "internalType": "uint256", "name": "sub", "type": "uint256" }936        ],937        "internalType": "struct CrossAddress",938        "name": "to",939        "type": "tuple"940      },941      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }942    ],943    "name": "transferFromCross",944    "outputs": [],945    "stateMutability": "nonpayable",946    "type": "function"947  },948  {949    "inputs": [],950    "name": "uniqueCollectionType",951    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],952    "stateMutability": "view",953    "type": "function"954  }955]
modifiedtests/src/eth/abi/reFungible.jsondiffbeforeafterboth
--- a/tests/src/eth/abi/reFungible.json
+++ b/tests/src/eth/abi/reFungible.json
@@ -451,6 +451,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": "tokenProperties",
+        "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/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