git.delta.rocks / unique-network / refs/commits / 1062e172486e

difftreelog

Merge pull request #343 from UniqueNetwork/feature/CORE-302

bugrazoid2022-05-27parents: #459d9af #e5c2d12.patch.diff
in: master
Feature/core 302

49 files changed

added.maintain/scripts/generate_abi.shdiffbeforeafterboth
--- /dev/null
+++ b/.maintain/scripts/generate_abi.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+set -eu
+
+dir=$PWD
+
+tmp=$(mktemp -d)
+cd $tmp
+cp $dir/$INPUT input.sol
+solcjs --abi -p input.sol
+
+NAME=input_sol_$(basename $INPUT .sol)
+mv $NAME.abi $NAME.json
+prettier $NAME.json > $dir/$OUTPUT
deleted.maintain/scripts/generate_api.shdiffbeforeafterboth
--- a/.maintain/scripts/generate_api.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/sh
-set -eu
-
-tmp=$(mktemp)
-cargo test --package $PACKAGE -- $NAME --exact --nocapture --ignored | tee $tmp
-raw=$(mktemp --suffix .sol)
-sed -n '/=== SNIP START ===/, /=== SNIP END ===/{ /=== SNIP START ===/! { /=== SNIP END ===/! p } }' $tmp > $raw
-formatted=$(mktemp)
-prettier --use-tabs $raw > $formatted
-
-mv $formatted $OUTPUT
added.maintain/scripts/generate_sol.shdiffbeforeafterboth
--- /dev/null
+++ b/.maintain/scripts/generate_sol.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+set -eu
+
+tmp=$(mktemp)
+cargo test --package $PACKAGE -- $NAME --exact --nocapture --ignored | tee $tmp
+raw=$(mktemp --suffix .sol)
+sed -n '/=== SNIP START ===/, /=== SNIP END ===/{ /=== SNIP START ===/! { /=== SNIP END ===/! p } }' $tmp > $raw
+formatted=$(mktemp)
+prettier --use-tabs $raw > $formatted
+
+mv $formatted $OUTPUT
modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4290,6 +4290,9 @@
 version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+dependencies = [
+ "spin",
+]
 
 [[package]]
 name = "lazycell"
@@ -5467,9 +5470,9 @@
 
 [[package]]
 name = "once_cell"
-version = "1.11.0"
+version = "1.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b10983b38c53aebdf33f542c6275b0f58a238129d00c4ae0e6fb59738d783ca"
+checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
 
 [[package]]
 name = "opal-runtime"
@@ -5917,6 +5920,7 @@
  "frame-benchmarking",
  "frame-support",
  "frame-system",
+ "lazy_static",
  "pallet-evm",
  "pallet-evm-coder-substrate",
  "parity-scale-codec 3.1.2",
@@ -6083,6 +6087,7 @@
  "frame-support",
  "frame-system",
  "log",
+ "pallet-common",
  "pallet-evm",
  "pallet-evm-coder-substrate",
  "parity-scale-codec 3.1.2",
@@ -6090,6 +6095,7 @@
  "sp-core",
  "sp-runtime",
  "sp-std",
+ "up-data-structs",
  "up-sponsorship",
 ]
 
@@ -6814,13 +6820,18 @@
 name = "pallet-unique"
 version = "0.1.0"
 dependencies = [
+ "ethereum",
+ "evm-coder",
  "frame-benchmarking",
  "frame-support",
  "frame-system",
  "pallet-common",
  "pallet-evm",
+ "pallet-evm-coder-substrate",
+ "pallet-nonfungible",
  "parity-scale-codec 3.1.2",
  "scale-info",
+ "serde",
  "sp-core",
  "sp-io",
  "sp-runtime",
@@ -12005,9 +12016,9 @@
 
 [[package]]
 name = "target-lexicon"
-version = "0.12.3"
+version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1"
+checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1"
 
 [[package]]
 name = "tempfile"
modifiedMakefilediffbeforeafterboth
--- a/Makefile
+++ b/Makefile
@@ -1,33 +1,61 @@
 .PHONY: _help
 _help:
 	@echo "regenerate_solidity - generate stubs/interfaces for contracts defined in native (via evm-coder)"
-	@echo "evm_stubs - recompile contract stubs"
+	@echo "evm_stubs - recompile contract stubs and ABI"
 	@echo "bench - run frame-benchmarking"
 	@echo "  bench-evm-migration"
 	@echo "  bench-unique"
 
+FUNGIBLE_EVM_STUBS=./pallets/fungible/src/stubs
+FUNGIBLE_EVM_ABI=./tests/src/eth/fungibleAbi.json
+
+NONFUNGIBLE_EVM_STUBS=./pallets/nonfungible/src/stubs
+NONFUNGIBLE_EVM_ABI=./tests/src/eth/nonFungibleAbi.json
+
+CONTRACT_HELPERS_STUBS=./pallets/evm-contract-helpers/src/stubs/
+CONTRACT_HELPERS_ABI=./tests/src/eth/util/contractHelpersAbi.json
+
+COLLECTION_HELPER_STUBS=./pallets/unique/src/eth/stubs/
+COLLECTION_HELPER_ABI=./tests/src/eth/collectionHelperAbi.json
+
+TESTS_API=./tests/src/eth/api/
+
 .PHONY: regenerate_solidity
-regenerate_solidity:
-	PACKAGE=pallet-fungible NAME=erc::gen_iface OUTPUT=./tests/src/eth/api/UniqueFungible.sol ./.maintain/scripts/generate_api.sh
-	PACKAGE=pallet-nonfungible NAME=erc::gen_iface OUTPUT=./tests/src/eth/api/UniqueNFT.sol ./.maintain/scripts/generate_api.sh
-	PACKAGE=pallet-evm-contract-helpers NAME=eth::contract_helpers_iface OUTPUT=./tests/src/eth/api/ContractHelpers.sol ./.maintain/scripts/generate_api.sh
+regenerate_solidity: UniqueFungible.sol UniqueNFT.sol ContractHelpers.sol CollectionHelper.sol
+
+UniqueFungible.sol:
+	PACKAGE=pallet-fungible NAME=erc::gen_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh
+	PACKAGE=pallet-fungible NAME=erc::gen_impl OUTPUT=$(FUNGIBLE_EVM_STUBS)/$@ ./.maintain/scripts/generate_sol.sh
+
+UniqueNFT.sol:
+	PACKAGE=pallet-nonfungible NAME=erc::gen_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh
+	PACKAGE=pallet-nonfungible NAME=erc::gen_impl OUTPUT=$(NONFUNGIBLE_EVM_STUBS)/$@ ./.maintain/scripts/generate_sol.sh
+
+ContractHelpers.sol:
+	PACKAGE=pallet-evm-contract-helpers NAME=eth::contract_helpers_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh
+	PACKAGE=pallet-evm-contract-helpers NAME=eth::contract_helpers_impl OUTPUT=$(CONTRACT_HELPERS_STUBS)/$@ ./.maintain/scripts/generate_sol.sh
+
+CollectionHelper.sol:
+	PACKAGE=pallet-unique NAME=eth::collection_helper_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh
+	PACKAGE=pallet-unique NAME=eth::collection_helper_impl OUTPUT=$(COLLECTION_HELPER_STUBS)/$@ ./.maintain/scripts/generate_sol.sh
+
+UniqueFungible: UniqueFungible.sol
+	INPUT=$(FUNGIBLE_EVM_STUBS)/$< OUTPUT=$(FUNGIBLE_EVM_STUBS)/UniqueFungible.raw ./.maintain/scripts/compile_stub.sh
+	INPUT=$(FUNGIBLE_EVM_STUBS)/$< OUTPUT=$(FUNGIBLE_EVM_ABI) ./.maintain/scripts/generate_abi.sh
 
-	PACKAGE=pallet-fungible NAME=erc::gen_impl OUTPUT=./pallets/fungible/src/stubs/UniqueFungible.sol ./.maintain/scripts/generate_api.sh
-	PACKAGE=pallet-nonfungible NAME=erc::gen_impl OUTPUT=./pallets/nonfungible/src/stubs/UniqueNFT.sol ./.maintain/scripts/generate_api.sh
-	PACKAGE=pallet-evm-contract-helpers NAME=eth::contract_helpers_impl OUTPUT=./pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol ./.maintain/scripts/generate_api.sh
+UniqueNFT: UniqueNFT.sol
+	INPUT=$(NONFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(NONFUNGIBLE_EVM_STUBS)/UniqueNFT.raw ./.maintain/scripts/compile_stub.sh
+	INPUT=$(NONFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(NONFUNGIBLE_EVM_ABI) ./.maintain/scripts/generate_abi.sh
 
-FUNGIBLE_EVM_STUBS=./pallets/fungible/src/stubs
-NONFUNGIBLE_EVM_STUBS=./pallets/nonfungible/src/stubs
-CONTRACT_HELPERS_STUBS=./pallets/evm-contract-helpers/src/stubs/
+ContractHelpers: ContractHelpers.sol
+	INPUT=$(CONTRACT_HELPERS_STUBS)/$< OUTPUT=$(CONTRACT_HELPERS_STUBS)/ContractHelpers.raw ./.maintain/scripts/compile_stub.sh
+	INPUT=$(CONTRACT_HELPERS_STUBS)/$< OUTPUT=$(CONTRACT_HELPERS_ABI) ./.maintain/scripts/generate_abi.sh
 
-$(FUNGIBLE_EVM_STUBS)/UniqueFungible.raw: $(FUNGIBLE_EVM_STUBS)/UniqueFungible.sol
-	INPUT=$< OUTPUT=$@ ./.maintain/scripts/compile_stub.sh
-$(NONFUNGIBLE_EVM_STUBS)/UniqueNFT.raw: $(NONFUNGIBLE_EVM_STUBS)/UniqueNFT.sol
-	INPUT=$< OUTPUT=$@ ./.maintain/scripts/compile_stub.sh
-$(CONTRACT_HELPERS_STUBS)/ContractHelpers.raw: $(CONTRACT_HELPERS_STUBS)/ContractHelpers.sol
-	INPUT=$< OUTPUT=$@ ./.maintain/scripts/compile_stub.sh
+CollectionHelper: CollectionHelper.sol
+	INPUT=$(COLLECTION_HELPER_STUBS)/$< OUTPUT=$(COLLECTION_HELPER_STUBS)/CollectionHelper.raw ./.maintain/scripts/compile_stub.sh
+	INPUT=$(COLLECTION_HELPER_STUBS)/$< OUTPUT=$(COLLECTION_HELPER_ABI) ./.maintain/scripts/generate_abi.sh
 
-evm_stubs: $(FUNGIBLE_EVM_STUBS)/UniqueFungible.raw $(NONFUNGIBLE_EVM_STUBS)/UniqueNFT.raw $(CONTRACT_HELPERS_STUBS)/ContractHelpers.raw
+evm_stubs: UniqueFungible UniqueNFT ContractHelpers CollectionHelper
 
 .PHONY: _bench
 _bench:
modifiedcrates/evm-coder/src/solidity.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -327,7 +327,7 @@
 	}
 }
 
-#[impl_for_tuples(1, 5)]
+#[impl_for_tuples(1, 12)]
 impl SolidityArguments for Tuple {
 	for_tuples!( where #( Tuple: SolidityArguments ),* );
 
modifiedpallets/common/Cargo.tomldiffbeforeafterboth
--- a/pallets/common/Cargo.toml
+++ b/pallets/common/Cargo.toml
@@ -16,16 +16,18 @@
 sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.21" }
 sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.21" }
 sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.21" }
+frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.21" }
 fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.21-logs" }
 up-data-structs = { default-features = false, path = '../../primitives/data-structs' }
 pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' }
 evm-coder = { default-features = false, path = '../../crates/evm-coder' }
 pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.21-logs" }
+
 serde = { version = "1.0.130", default-features = false }
 scale-info = { version = "2.0.1", default-features = false, features = [
     "derive",
 ] }
-frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.21" }
+lazy_static = { version = "1.4.0", default-features = false, features = ["spin_no_std"] }
 
 [features]
 default = ["std"]
modifiedpallets/common/src/erc.rsdiffbeforeafterboth
--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -14,12 +14,17 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-use evm_coder::{solidity_interface, types::*, execution::Result};
+use evm_coder::{
+	solidity_interface,
+	types::*,
+	execution::{Result, Error},
+};
 pub use pallet_evm::{PrecompileOutput, PrecompileResult, account::CrossAccountId};
 use pallet_evm_coder_substrate::dispatch_to_evm;
 use sp_core::{H160, U256};
 use sp_std::vec::Vec;
-use up_data_structs::Property;
+use up_data_structs::{Property, SponsoringRateLimit};
+use alloc::format;
 
 use crate::{Pallet, CollectionHandle, Config, CollectionProperties};
 
@@ -31,7 +36,7 @@
 	fn call(self, source: &H160, input: &[u8], value: U256) -> Option<PrecompileResult>;
 }
 
-#[solidity_interface(name = "CollectionProperties")]
+#[solidity_interface(name = "Collection")]
 impl<T: Config> CollectionHandle<T> {
 	fn set_collection_property(&mut self, caller: caller, key: string, value: bytes) -> Result<()> {
 		let caller = T::CrossAccountId::from_eth(caller);
@@ -64,4 +69,93 @@
 
 		Ok(prop.to_vec())
 	}
+
+	fn eth_set_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {
+		check_is_owner(caller, self)?;
+
+		let sponsor = T::CrossAccountId::from_eth(sponsor);
+		self.set_sponsor(sponsor.as_sub().clone());
+		save(self);
+		Ok(())
+	}
+
+	fn eth_confirm_sponsorship(&mut self, caller: caller) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		if !self.confirm_sponsorship(caller.as_sub()) {
+			return Err(Error::Revert("Caller is not set as sponsor".into()));
+		}
+		save(self);
+		Ok(())
+	}
+
+	fn set_limit(&mut self, caller: caller, limit: string, value: string) -> Result<void> {
+		check_is_owner(caller, self)?;
+		let mut limits = self.limits.clone();
+
+		match limit.as_str() {
+			"accountTokenOwnershipLimit" => {
+				limits.account_token_ownership_limit = parse_int(value)?;
+			}
+			"sponsoredDataSize" => {
+				limits.sponsored_data_size = parse_int(value)?;
+			}
+			"sponsoredDataRateLimit" => {
+				limits.sponsored_data_rate_limit =
+					Some(SponsoringRateLimit::Blocks(parse_int(value)?.unwrap()));
+			}
+			"tokenLimit" => {
+				limits.token_limit = parse_int(value)?;
+			}
+			"sponsorTransferTimeout" => {
+				limits.sponsor_transfer_timeout = parse_int(value)?;
+			}
+			"sponsorApproveTimeout" => {
+				limits.sponsor_approve_timeout = parse_int(value)?;
+			}
+			"ownerCanTransfer" => {
+				limits.owner_can_transfer = parse_bool(value)?;
+			}
+			"ownerCanDestroy" => {
+				limits.owner_can_destroy = parse_bool(value)?;
+			}
+			"transfersEnabled" => {
+				limits.transfers_enabled = parse_bool(value)?;
+			}
+			_ => return Err(Error::Revert(format!("Unknown limit \"{}\"", limit))),
+		}
+		self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)
+			.map_err(dispatch_to_evm::<T>)?;
+		save(self);
+		Ok(())
+	}
+
+	fn contract_address(&self, _caller: caller) -> Result<address> {
+		Ok(crate::eth::collection_id_to_address(self.id))
+	}
+}
+
+fn check_is_owner<T: Config>(caller: caller, collection: &CollectionHandle<T>) -> Result<()> {
+	let caller = T::CrossAccountId::from_eth(caller);
+	collection
+		.check_is_owner(&caller)
+		.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
+	Ok(())
+}
+
+fn save<T: Config>(collection: &CollectionHandle<T>) {
+	<crate::CollectionById<T>>::insert(collection.id, collection.collection.clone());
+}
+
+fn parse_int(value: string) -> Result<Option<u32>> {
+	value
+		.parse::<u32>()
+		.map_err(|e| Error::Revert(format!("Int value \"{}\" parse error: {}", value, e)))
+		.map(|value| Some(value))
+}
+
+fn parse_bool(value: string) -> Result<Option<bool>> {
+	value
+		.parse::<bool>()
+		.map_err(|e| Error::Revert(format!("Bool value \"{}\" parse error: {}", value, e)))
+		.map(|value| Some(value))
 }
modifiedpallets/common/src/eth.rsdiffbeforeafterboth
--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -17,6 +17,14 @@
 use up_data_structs::CollectionId;
 use sp_core::H160;
 
+lazy_static::lazy_static! {
+	pub static ref KEY_TOKEN_URI: up_data_structs::PropertyKey = {
+		let key: evm_coder::types::string = "tokenURI".into(); //TODO: make static
+		let key: up_data_structs::PropertyKey = key.into_bytes().try_into().expect("Can't create \"tokenURI\" key");
+		key
+	};
+}
+
 // 0x17c4e6453Cc49AAAaEACA894e6D9683e00000001 - collection 1
 // TODO: Unhardcode prefix
 const ETH_COLLECTION_PREFIX: [u8; 16] = [
@@ -37,3 +45,7 @@
 	out[16..20].copy_from_slice(&u32::to_be_bytes(id.0));
 	H160(out)
 }
+
+pub fn is_collection(address: &H160) -> bool {
+	address[0..16] == ETH_COLLECTION_PREFIX
+}
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -114,6 +114,15 @@
 			recorder: SubstrateRecorder::new(gas_limit),
 		})
 	}
+
+	pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {
+		<CollectionById<T>>::get(id).map(|collection| Self {
+			id,
+			collection,
+			recorder,
+		})
+	}
+
 	pub fn new(id: CollectionId) -> Option<Self> {
 		Self::new_with_gas_limit(id, u64::MAX)
 	}
@@ -140,6 +149,19 @@
 		<CollectionById<T>>::insert(self.id, self.collection);
 		Ok(())
 	}
+
+	pub fn set_sponsor(&mut self, sponsor: T::AccountId) {
+		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);
+	}
+
+	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> bool {
+		if self.collection.sponsorship.pending_sponsor() != Some(sender) {
+			return false;
+		};
+
+		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());
+		true
+	}
 }
 impl<T: Config> Deref for CollectionHandle<T> {
 	type Target = Collection<T::AccountId>;
modifiedpallets/evm-contract-helpers/Cargo.tomldiffbeforeafterboth
--- a/pallets/evm-contract-helpers/Cargo.toml
+++ b/pallets/evm-contract-helpers/Cargo.toml
@@ -8,18 +8,26 @@
 scale-info = { version = "2.0.1", default-features = false, features = [
     "derive",
 ] }
-frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.21" }
-frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.21" }
-sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.21" }
-sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.21" }
-sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.21" }
-evm-coder = { default-features = false, path = '../../crates/evm-coder' }
-pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' }
+log = { default-features = false, version = "0.4.14" }
+
+# Substrate
+frame-support = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.21' }
+frame-system = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.21' }
+sp-runtime = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.21' }
+sp-std = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.21' }
+sp-core = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.21' }
+
+# Unique
 pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.21-logs" }
 fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.21-logs" }
 up-sponsorship = { version = "0.1.0", default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = "polkadot-v0.9.21" }
-log = "0.4.14"
 
+# Locals
+evm-coder = { default-features = false, path = '../../crates/evm-coder' }
+pallet-common = { default-features = false, path = '../../pallets/common' }
+pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' }
+up-data-structs = { default-features = false, path = '../../primitives/data-structs', features = ['serde1'] }
+
 [dependencies.codec]
 default-features = false
 features = ['derive']
modifiedpallets/fungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -24,7 +24,7 @@
 use pallet_evm::account::CrossAccountId;
 use pallet_evm_coder_substrate::{call, dispatch_to_evm};
 use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};
-use pallet_common::{CollectionHandle, erc::CollectionPropertiesCall};
+use pallet_common::{CollectionHandle, erc::CollectionCall};
 
 use crate::{
 	Allowance, Balance, Config, FungibleHandle, Pallet, SelfWeightOf, TotalSupply,
@@ -150,7 +150,7 @@
 	is(
 		ERC20,
 		ERC20UniqueExtensions,
-		via("CollectionHandle<T>", common_mut, CollectionProperties)
+		via("CollectionHandle<T>", common_mut, Collection)
 	)
 )]
 impl<T: Config> FungibleHandle<T> {}
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -33,7 +33,7 @@
 use pallet_evm_coder_substrate::WithRecorder;
 use sp_core::H160;
 use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
-use sp_std::collections::btree_map::BTreeMap;
+use sp_std::{collections::btree_map::BTreeMap};
 
 pub use pallet::*;
 
modifiedpallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth
--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -127,8 +127,8 @@
 	}
 }
 
-// Selector: 9b5e29c5
-contract CollectionProperties is Dummy, ERC165 {
+// Selector: f5652829
+contract Collection is Dummy, ERC165 {
 	// Selector: setCollectionProperty(string,bytes) 2f073f66
 	function setCollectionProperty(string memory key, bytes memory value)
 		public
@@ -159,6 +159,34 @@
 		dummy;
 		return hex"";
 	}
+
+	// Selector: ethSetSponsor(address) 8f9af356
+	function ethSetSponsor(address sponsor) public {
+		require(false, stub_error);
+		sponsor;
+		dummy = 0;
+	}
+
+	// Selector: ethConfirmSponsorship() a8580d1a
+	function ethConfirmSponsorship() public {
+		require(false, stub_error);
+		dummy = 0;
+	}
+
+	// Selector: setLimit(string,string) bf4d2014
+	function setLimit(string memory limit, string memory value) public {
+		require(false, stub_error);
+		limit;
+		value;
+		dummy = 0;
+	}
+
+	// Selector: contractAddress() f6b4dfb4
+	function contractAddress() public view returns (address) {
+		require(false, stub_error);
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
 }
 
 contract UniqueFungible is
@@ -166,5 +194,5 @@
 	ERC165,
 	ERC20,
 	ERC20UniqueExtensions,
-	CollectionProperties
+	Collection
 {}
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -22,7 +22,7 @@
 	PropertyKeyPermission, PropertyValue,
 };
 use pallet_common::{
-	CommonCollectionOperations, CommonWeightInfo, with_weight, weights::WeightInfo as _,
+	CommonCollectionOperations, CommonWeightInfo, with_weight, weights::WeightInfo as _
 };
 use sp_runtime::DispatchError;
 use sp_std::vec::Vec;
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -21,13 +21,16 @@
 };
 use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};
 use frame_support::BoundedVec;
-use up_data_structs::{TokenId, SchemaVersion, PropertyPermission, PropertyKeyPermission, Property};
+use up_data_structs::{
+	TokenId, SchemaVersion, PropertyPermission, PropertyKeyPermission, Property, CollectionId,
+	PropertyKey, CollectionPropertiesVec,
+};
 use pallet_evm_coder_substrate::dispatch_to_evm;
 use sp_core::{H160, U256};
 use sp_std::vec::Vec;
 use pallet_common::{
-	erc::{CommonEvmHandler, PrecompileResult, CollectionPropertiesCall},
-	CollectionHandle,
+	erc::{CommonEvmHandler, PrecompileResult, CollectionCall},
+	CollectionHandle, CollectionPropertyPermissions,
 };
 use pallet_evm::account::CrossAccountId;
 use pallet_evm_coder_substrate::call;
@@ -158,12 +161,21 @@
 	/// Returns token's const_metadata
 	#[solidity(rename_selector = "tokenURI")]
 	fn token_uri(&self, token_id: uint256) -> Result<string> {
+		let key = pallet_common::eth::KEY_TOKEN_URI.clone();
+		if !has_token_permission::<T>(self.id, &key) {
+			return Err("No tokenURI permission".into());
+		}
+
 		self.consume_store_reads(1)?;
-		let _token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
-		Ok(string::from_utf8_lossy(
-			todo!()
-		)
-		.into())
+		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
+
+		let properties = <TokenProperties<T>>::try_get((self.id, token_id))
+			.map_err(|_| Error::Revert("Token properties not found".into()))?;
+		if let Some(property) = properties.get(&key) {
+			return Ok(string::from_utf8_lossy(property).into());
+		}
+
+		Err("Property tokenURI not found".into())
 	}
 }
 
@@ -350,6 +362,12 @@
 		token_id: uint256,
 		token_uri: string,
 	) -> Result<bool> {
+		let key = pallet_common::eth::KEY_TOKEN_URI.clone();
+		let permission = get_token_permission::<T>(self.id, &key)?;
+		if !permission.collection_admin {
+			return Err("Operation is not allowed".into());
+		}
+
 		let caller = T::CrossAccountId::from_eth(caller);
 		let to = T::CrossAccountId::from_eth(to);
 		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;
@@ -365,13 +383,22 @@
 			return Err("item id should be next".into());
 		}
 
-		todo!("token uri");
+		let mut properties = CollectionPropertiesVec::default();
+		properties
+			.try_push(Property {
+				key,
+				value: token_uri
+					.into_bytes()
+					.try_into()
+					.map_err(|_| "token uri is too long")?,
+			})
+			.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;
 
 		<Pallet<T>>::create_item(
 			self,
 			&caller,
 			CreateItemData::<T> {
-				properties: BoundedVec::default(),
+				properties,
 				owner: to,
 			},
 			&budget,
@@ -386,6 +413,30 @@
 	}
 }
 
+fn get_token_permission<T: Config>(
+	collection_id: CollectionId,
+	key: &PropertyKey,
+) -> Result<PropertyPermission> {
+	let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)
+		.map_err(|_| Error::Revert("No permissions for collection".into()))?;
+	let a = token_property_permissions
+		.get(key)
+		.map(|p| p.clone())
+		.ok_or_else(|| Error::Revert("No permission".into()))?;
+	Ok(a)
+}
+
+fn has_token_permission<T: Config>(
+	collection_id: CollectionId,
+	key: &PropertyKey,
+) -> bool {
+	if let Ok(token_property_permissions) = CollectionPropertyPermissions::<T>::try_get(collection_id) {
+		return token_property_permissions.contains_key(key);
+	}
+
+	false
+}
+
 #[solidity_interface(name = "ERC721UniqueExtensions")]
 impl<T: Config> NonfungibleHandle<T> {
 	#[weight(<SelfWeightOf<T>>::transfer())]
@@ -491,7 +542,6 @@
 			}
 			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;
 
-			todo!("token uri");
 			data.push(CreateItemData::<T> {
 				properties: BoundedVec::default(),
 				owner: to.clone(),
@@ -513,7 +563,7 @@
 		ERC721UniqueExtensions,
 		ERC721Mintable,
 		ERC721Burnable,
-		via("CollectionHandle<T>", common_mut, CollectionProperties),
+		via("CollectionHandle<T>", common_mut, Collection),
 		TokenProperties,
 	)
 )]
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -33,9 +33,8 @@
 use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
 use sp_core::H160;
 use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};
-use sp_std::{vec::Vec, vec, collections::btree_set::BTreeSet};
+use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap, collections::btree_set::BTreeSet};
 use core::ops::Deref;
-use sp_std::collections::btree_map::BTreeMap;
 use codec::{Encode, Decode, MaxEncodedLen};
 use scale_info::TypeInfo;
 
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
@@ -330,40 +330,6 @@
 	}
 }
 
-// Selector: 9b5e29c5
-contract CollectionProperties is Dummy, ERC165 {
-	// Selector: setCollectionProperty(string,bytes) 2f073f66
-	function setCollectionProperty(string memory key, bytes memory value)
-		public
-	{
-		require(false, stub_error);
-		key;
-		value;
-		dummy = 0;
-	}
-
-	// Selector: deleteCollectionProperty(string) 7b7debce
-	function deleteCollectionProperty(string memory key) public {
-		require(false, stub_error);
-		key;
-		dummy = 0;
-	}
-
-	// Throws error if key not found
-	//
-	// Selector: collectionProperty(string) cf24fd6d
-	function collectionProperty(string memory key)
-		public
-		view
-		returns (bytes memory)
-	{
-		require(false, stub_error);
-		key;
-		dummy;
-		return hex"";
-	}
-}
-
 // Selector: d74d154f
 contract ERC721UniqueExtensions is Dummy, ERC165 {
 	// Selector: transfer(address,uint256) a9059cbb
@@ -414,6 +380,68 @@
 	}
 }
 
+// Selector: f5652829
+contract Collection is Dummy, ERC165 {
+	// Selector: setCollectionProperty(string,bytes) 2f073f66
+	function setCollectionProperty(string memory key, bytes memory value)
+		public
+	{
+		require(false, stub_error);
+		key;
+		value;
+		dummy = 0;
+	}
+
+	// Selector: deleteCollectionProperty(string) 7b7debce
+	function deleteCollectionProperty(string memory key) public {
+		require(false, stub_error);
+		key;
+		dummy = 0;
+	}
+
+	// Throws error if key not found
+	//
+	// Selector: collectionProperty(string) cf24fd6d
+	function collectionProperty(string memory key)
+		public
+		view
+		returns (bytes memory)
+	{
+		require(false, stub_error);
+		key;
+		dummy;
+		return hex"";
+	}
+
+	// Selector: ethSetSponsor(address) 8f9af356
+	function ethSetSponsor(address sponsor) public {
+		require(false, stub_error);
+		sponsor;
+		dummy = 0;
+	}
+
+	// Selector: ethConfirmSponsorship() a8580d1a
+	function ethConfirmSponsorship() public {
+		require(false, stub_error);
+		dummy = 0;
+	}
+
+	// Selector: setLimit(string,string) bf4d2014
+	function setLimit(string memory limit, string memory value) public {
+		require(false, stub_error);
+		limit;
+		value;
+		dummy = 0;
+	}
+
+	// Selector: contractAddress() f6b4dfb4
+	function contractAddress() public view returns (address) {
+		require(false, stub_error);
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
+}
+
 contract UniqueNFT is
 	Dummy,
 	ERC165,
@@ -423,6 +451,6 @@
 	ERC721UniqueExtensions,
 	ERC721Mintable,
 	ERC721Burnable,
-	CollectionProperties,
+	Collection,
 	TokenProperties
 {}
modifiedpallets/unique/Cargo.tomldiffbeforeafterboth
--- a/pallets/unique/Cargo.toml
+++ b/pallets/unique/Cargo.toml
@@ -19,6 +19,7 @@
 runtime-benchmarks = ['frame-benchmarking', 'pallet-common/runtime-benchmarks']
 std = [
     'codec/std',
+    'serde/std',
     'frame-support/std',
     'frame-system/std',
     'pallet-evm/std',
@@ -27,10 +28,25 @@
     'sp-std/std',
     'sp-runtime/std',
     'frame-benchmarking/std',
+    'evm-coder/std',
+    'pallet-evm-coder-substrate/std',
+    'pallet-nonfungible/std',
 ]
 limit-testing = ["up-data-structs/limit-testing"]
 
 ################################################################################
+# Standart Dependencies
+
+[dependencies.serde]
+default-features = false
+features = ['derive']
+version = '1.0.130'
+
+[dependencies.ethereum]
+version = "0.12.0"
+default-features = false
+
+################################################################################
 # Substrate Dependencies
 
 [dependencies.codec]
@@ -74,7 +90,6 @@
 default-features = false
 git = "https://github.com/paritytech/substrate"
 branch = "polkadot-v0.9.21"
-
 
 ################################################################################
 # Local Dependencies
@@ -85,3 +100,6 @@
 ] }
 pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.21-logs" }
 pallet-common = { default-features = false, path = "../common" }
+evm-coder = { default-features = false, path = '../../crates/evm-coder' }
+pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' }
+pallet-nonfungible = { default-features = false, path = '../../pallets/nonfungible' }
addedpallets/unique/src/eth/mod.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/unique/src/eth/mod.rs
@@ -0,0 +1,171 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+use core::marker::PhantomData;
+use evm_coder::{execution::*, generate_stubgen, solidity_interface, types::*, ToLog};
+use ethereum as _;
+use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
+use pallet_evm::{OnMethodCall, PrecompileResult, account::CrossAccountId, Pallet as PalletEvm};
+use up_data_structs::{
+	CreateCollectionData, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,
+	MAX_COLLECTION_NAME_LENGTH,
+};
+use frame_support::traits::Get;
+use sp_core::H160;
+use pallet_common::CollectionById;
+
+use sp_std::vec::Vec;
+use alloc::format;
+
+pub trait Config:
+	frame_system::Config
+	+ pallet_evm_coder_substrate::Config
+	+ pallet_evm::account::Config
+	+ pallet_nonfungible::Config
+{
+	type ContractAddress: Get<H160>;
+}
+
+struct EvmCollectionHelper<T: Config>(SubstrateRecorder<T>);
+impl<T: Config> WithRecorder<T> for EvmCollectionHelper<T> {
+	fn recorder(&self) -> &SubstrateRecorder<T> {
+		&self.0
+	}
+
+	fn into_recorder(self) -> SubstrateRecorder<T> {
+		self.0
+	}
+}
+
+#[solidity_interface(name = "CollectionHelper")]
+impl<T: Config> EvmCollectionHelper<T> {
+	fn create_721_collection(
+		&self,
+		caller: caller,
+		name: string,
+		description: string,
+		token_prefix: string,
+	) -> Result<address> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let name = name
+			.encode_utf16()
+			.collect::<Vec<u16>>()
+			.try_into()
+			.map_err(|_| error_feild_too_long(stringify!(name), MAX_COLLECTION_NAME_LENGTH))?;
+		let description = description
+			.encode_utf16()
+			.collect::<Vec<u16>>()
+			.try_into()
+			.map_err(|_| {
+				error_feild_too_long(stringify!(description), MAX_COLLECTION_DESCRIPTION_LENGTH)
+			})?;
+		let token_prefix = token_prefix
+			.into_bytes()
+			.try_into()
+			.map_err(|_| error_feild_too_long(stringify!(token_prefix), MAX_TOKEN_PREFIX_LENGTH))?;
+
+		let key = pallet_common::eth::KEY_TOKEN_URI.clone();
+		let permission = up_data_structs::PropertyPermission {
+			mutable: true,
+			collection_admin: true,
+			token_owner: false,
+		};
+		let mut token_property_permissions =
+			up_data_structs::CollectionPropertiesPermissionsVec::default();
+		token_property_permissions
+			.try_push(up_data_structs::PropertyKeyPermission { key, permission })
+			.map_err(|e| Error::Revert(format!("{:?}", e)))?;
+
+		let data = CreateCollectionData {
+			name,
+			description,
+			token_prefix,
+			token_property_permissions,
+			..Default::default()
+		};
+
+		let collection_id =
+			<pallet_nonfungible::Pallet<T>>::init_collection(caller.as_sub().clone(), data)
+				.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
+
+		let address = pallet_common::eth::collection_id_to_address(collection_id);
+		<PalletEvm<T>>::deposit_log(
+			EthCollectionEvent::CollectionCreated {
+				owner: *caller.as_eth(),
+				collection_id: address,
+			}
+			.to_log(address),
+		);
+		Ok(address)
+	}
+
+	fn is_collection_exist(&self, _caller: caller, collection_address: address) -> Result<bool> {
+		if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {
+			let collection_id = id;
+			return Ok(<CollectionById<T>>::contains_key(collection_id));
+		}
+
+		Ok(false)
+	}
+}
+
+#[derive(ToLog)]
+pub enum EthCollectionEvent {
+	CollectionCreated {
+		#[indexed]
+		owner: address,
+		#[indexed]
+		collection_id: address,
+	},
+}
+
+pub struct CollectionHelperOnMethodCall<T: Config>(PhantomData<*const T>);
+impl<T: Config> OnMethodCall<T> for CollectionHelperOnMethodCall<T> {
+	fn is_reserved(contract: &sp_core::H160) -> bool {
+		contract == &T::ContractAddress::get()
+	}
+
+	fn is_used(contract: &sp_core::H160) -> bool {
+		contract == &T::ContractAddress::get()
+	}
+
+	fn call(
+		source: &sp_core::H160,
+		target: &sp_core::H160,
+		gas_left: u64,
+		input: &[u8],
+		value: sp_core::U256,
+	) -> Option<PrecompileResult> {
+		if target != &T::ContractAddress::get() {
+			return None;
+		}
+
+		let helpers = EvmCollectionHelper::<T>(SubstrateRecorder::<T>::new(gas_left));
+		pallet_evm_coder_substrate::call(*source, helpers, value, input)
+	}
+
+	fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {
+		(contract == &T::ContractAddress::get())
+			.then(|| include_bytes!("./stubs/CollectionHelper.raw").to_vec())
+	}
+}
+
+generate_stubgen!(collection_helper_impl, CollectionHelperCall<()>, true);
+generate_stubgen!(collection_helper_iface, CollectionHelperCall<()>, false);
+
+fn error_feild_too_long(feild: &str, bound: u32) -> Error {
+	Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))
+}
addedpallets/unique/src/eth/stubs/CollectionHelper.rawdiffbeforeafterboth

binary blob — no preview

addedpallets/unique/src/eth/stubs/CollectionHelper.soldiffbeforeafterboth
--- /dev/null
+++ b/pallets/unique/src/eth/stubs/CollectionHelper.sol
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: OTHER
+// This code is automatically generated
+
+pragma solidity >=0.8.0 <0.9.0;
+
+// Common stubs holder
+contract Dummy {
+	uint8 dummy;
+	string stub_error = "this contract is implemented in native";
+}
+
+contract ERC165 is Dummy {
+	function supportsInterface(bytes4 interfaceID)
+		external
+		view
+		returns (bool)
+	{
+		require(false, stub_error);
+		interfaceID;
+		return true;
+	}
+}
+
+// Selector: 56c215c5
+contract CollectionHelper is Dummy, ERC165 {
+	// Selector: create721Collection(string,string,string) 951c0151
+	function create721Collection(
+		string memory name,
+		string memory description,
+		string memory tokenPrefix
+	) public view returns (address) {
+		require(false, stub_error);
+		name;
+		description;
+		tokenPrefix;
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
+
+	// Selector: isCollectionExist(address) c3de1494
+	function isCollectionExist(address collectionAddress)
+		public
+		view
+		returns (bool)
+	{
+		require(false, stub_error);
+		collectionAddress;
+		dummy;
+		return false;
+	}
+}
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
before · pallets/unique/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20	clippy::too_many_arguments,21	clippy::unnecessary_mut_passed,22	clippy::unused_unit23)]2425use frame_support::{26	decl_module, decl_storage, decl_error, decl_event,27	dispatch::DispatchResult,28	ensure,29	weights::{Weight},30	transactional,31	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},32	BoundedVec,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use up_data_structs::{38	MAX_COLLECTION_NAME_LENGTH,39	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData,40	CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId, SponsorshipState,41	CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,42	PropertyKeyPermission,43};44use pallet_evm::account::CrossAccountId;45use pallet_common::{46	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_call,47	dispatch::CollectionDispatch,48};4950#[cfg(feature = "runtime-benchmarks")]51mod benchmarking;52pub mod weights;53use weights::WeightInfo;5455decl_error! {56	/// Error for non-fungible-token module.57	pub enum Error for Module<T: Config> {58		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.59		CollectionDecimalPointLimitExceeded,60		/// This address is not set as sponsor, use setCollectionSponsor first.61		ConfirmUnsetSponsorFail,62		/// Length of items properties must be greater than 0.63		EmptyArgument,64	}65}6667pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {68	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;6970	/// Weight information for extrinsics in this pallet.71	type WeightInfo: WeightInfo;72	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;73}7475decl_event! {76	pub enum Event<T>77	where78		<T as frame_system::Config>::AccountId,79		<T as pallet_evm::account::Config>::CrossAccountId,80	{81		/// Collection sponsor was removed82		///83		/// # Arguments84		///85		/// * collection_id: Globally unique collection identifier.86		CollectionSponsorRemoved(CollectionId),8788		/// Collection admin was added89		///90		/// # Arguments91		///92		/// * collection_id: Globally unique collection identifier.93		///94		/// * admin:  Admin address.95		CollectionAdminAdded(CollectionId, CrossAccountId),9697		/// Collection owned was change98		///99		/// # Arguments100		///101		/// * collection_id: Globally unique collection identifier.102		///103		/// * owner:  New owner address.104		CollectionOwnedChanged(CollectionId, AccountId),105106		/// Collection sponsor was set107		///108		/// # Arguments109		///110		/// * collection_id: Globally unique collection identifier.111		///112		/// * owner:  New sponsor address.113		CollectionSponsorSet(CollectionId, AccountId),114115		/// const on chain schema was set116		///117		/// # Arguments118		///119		/// * collection_id: Globally unique collection identifier.120		ConstOnChainSchemaSet(CollectionId),121122		/// New sponsor was confirm123		///124		/// # Arguments125		///126		/// * collection_id: Globally unique collection identifier.127		///128		/// * sponsor:  New sponsor address.129		SponsorshipConfirmed(CollectionId, AccountId),130131		/// Collection admin was removed132		///133		/// # Arguments134		///135		/// * collection_id: Globally unique collection identifier.136		///137		/// * admin:  Admin address.138		CollectionAdminRemoved(CollectionId, CrossAccountId),139140		/// Address was remove from allow list141		///142		/// # Arguments143		///144		/// * collection_id: Globally unique collection identifier.145		///146		/// * user:  Address.147		AllowListAddressRemoved(CollectionId, CrossAccountId),148149		/// Address was add to allow list150		///151		/// # Arguments152		///153		/// * collection_id: Globally unique collection identifier.154		///155		/// * user:  Address.156		AllowListAddressAdded(CollectionId, CrossAccountId),157158		/// Collection limits was set159		///160		/// # Arguments161		///162		/// * collection_id: Globally unique collection identifier.163		CollectionLimitSet(CollectionId),164165		CollectionPermissionSet(CollectionId),166167		/// Mint permission	was set168		///169		/// # Arguments170		///171		/// * collection_id: Globally unique collection identifier.172		MintPermissionSet(CollectionId),173174		/// Offchain schema was set175		///176		/// # Arguments177		///178		/// * collection_id: Globally unique collection identifier.179		OffchainSchemaSet(CollectionId),180181		/// Public access mode was set182		///183		/// # Arguments184		///185		/// * collection_id: Globally unique collection identifier.186		///187		/// * mode: New access state.188		PublicAccessModeSet(CollectionId, AccessMode),189190		/// Schema version was set191		///192		/// # Arguments193		///194		/// * collection_id: Globally unique collection identifier.195		SchemaVersionSet(CollectionId),196	}197}198199type SelfWeightOf<T> = <T as Config>::WeightInfo;200201// # Used definitions202//203// ## User control levels204//205// chain-controlled - key is uncontrolled by user206//                    i.e autoincrementing index207//                    can use non-cryptographic hash208// real - key is controlled by user209//        but it is hard to generate enough colliding values, i.e owner of signed txs210//        can use non-cryptographic hash211// controlled - key is completly controlled by users212//              i.e maps with mutable keys213//              should use cryptographic hash214//215// ## User control level downgrade reasons216//217// ?1 - chain-controlled -> controlled218//      collections/tokens can be destroyed, resulting in massive holes219// ?2 - chain-controlled -> controlled220//      same as ?1, but can be only added, resulting in easier exploitation221// ?3 - real -> controlled222//      no confirmation required, so addresses can be easily generated223decl_storage! {224	trait Store for Module<T: Config> as Unique {225226		//#region Private members227		/// Used for migrations228		ChainVersion: u64;229		//#endregion230231		//#region Tokens transfer rate limit baskets232		/// (Collection id (controlled?2), who created (real))233		/// TODO: Off chain worker should remove from this map when collection gets removed234		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;235		/// Collection id (controlled?2), token id (controlled?2)236		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;237		/// Collection id (controlled?2), owning user (real)238		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;239		/// Collection id (controlled?2), token id (controlled?2)240		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;241		//#endregion242243		/// Variable metadata sponsoring244		/// Collection id (controlled?2), token id (controlled?2)245		#[deprecated]246		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;247		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;248249		/// Approval sponsoring250		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;251		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;252		pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;253	}254}255256decl_module! {257	pub struct Module<T: Config> for enum Call258	where259		origin: T::Origin260	{261		type Error = Error<T>;262263		fn deposit_event() = default;264265		fn on_initialize(_now: T::BlockNumber) -> Weight {266			0267		}268269		fn on_runtime_upgrade() -> Weight {270			let limit = None;271272			<VariableMetaDataBasket<T>>::remove_all(limit);273274			0275		}276277		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.278		///279		/// # Permissions280		///281		/// * Anyone.282		///283		/// # Arguments284		///285		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.286		///287		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.288		///289		/// * token_prefix: UTF-8 string with token prefix.290		///291		/// * mode: [CollectionMode] collection type and type dependent data.292		// returns collection ID293		#[weight = <SelfWeightOf<T>>::create_collection()]294		#[transactional]295		#[deprecated]296		pub fn create_collection(origin,297								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,298								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,299								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,300								 mode: CollectionMode) -> DispatchResult  {301			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {302				name: collection_name,303				description: collection_description,304				token_prefix,305				mode,306				..Default::default()307			};308			Self::create_collection_ex(origin, data)309		}310311		/// This method creates a collection312		///313		/// Prefer it to deprecated [`created_collection`] method314		#[weight = <SelfWeightOf<T>>::create_collection()]315		#[transactional]316		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {317			let sender = ensure_signed(origin)?;318319			// =========320321			T::CollectionDispatch::create(sender, data)?;322323			Ok(())324		}325326		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.327		///328		/// # Permissions329		///330		/// * Collection Owner.331		///332		/// # Arguments333		///334		/// * collection_id: collection to destroy.335		#[weight = <SelfWeightOf<T>>::destroy_collection()]336		#[transactional]337		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {338			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);339			let collection = <CollectionHandle<T>>::try_get(collection_id)?;340341			// =========342343			T::CollectionDispatch::destroy(sender, collection)?;344345			<NftTransferBasket<T>>::remove_prefix(collection_id, None);346			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);347			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);348349			<NftApproveBasket<T>>::remove_prefix(collection_id, None);350			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);351			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);352353			Ok(())354		}355356		/// Add an address to allow list.357		///358		/// # Permissions359		///360		/// * Collection Owner361		/// * Collection Admin362		///363		/// # Arguments364		///365		/// * collection_id.366		///367		/// * address.368		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]369		#[transactional]370		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{371372			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);373			let collection = <CollectionHandle<T>>::try_get(collection_id)?;374375			<PalletCommon<T>>::toggle_allowlist(376				&collection,377				&sender,378				&address,379				true,380			)?;381382			Self::deposit_event(Event::<T>::AllowListAddressAdded(383				collection_id,384				address385			));386387			Ok(())388		}389390		/// Remove an address from allow list.391		///392		/// # Permissions393		///394		/// * Collection Owner395		/// * Collection Admin396		///397		/// # Arguments398		///399		/// * collection_id.400		///401		/// * address.402		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]403		#[transactional]404		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{405406			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);407			let collection = <CollectionHandle<T>>::try_get(collection_id)?;408409			<PalletCommon<T>>::toggle_allowlist(410				&collection,411				&sender,412				&address,413				false,414			)?;415416			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(417				collection_id,418				address419			));420421			Ok(())422		}423424		/// Change the owner of the collection.425		///426		/// # Permissions427		///428		/// * Collection Owner.429		///430		/// # Arguments431		///432		/// * collection_id.433		///434		/// * new_owner.435		#[weight = <SelfWeightOf<T>>::change_collection_owner()]436		#[transactional]437		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {438439			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);440441			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;442			target_collection.check_is_owner(&sender)?;443444			target_collection.owner = new_owner.clone();445			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(446				collection_id,447				new_owner448			));449450			target_collection.save()451		}452453		/// Adds an admin of the Collection.454		/// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.455		///456		/// # Permissions457		///458		/// * Collection Owner.459		/// * Collection Admin.460		///461		/// # Arguments462		///463		/// * collection_id: ID of the Collection to add admin for.464		///465		/// * new_admin_id: Address of new admin to add.466		#[weight = <SelfWeightOf<T>>::add_collection_admin()]467		#[transactional]468		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {469			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);470			let collection = <CollectionHandle<T>>::try_get(collection_id)?;471472			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(473				collection_id,474				new_admin_id.clone()475			));476477			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)478		}479480		/// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.481		///482		/// # Permissions483		///484		/// * Collection Owner.485		/// * Collection Admin.486		///487		/// # Arguments488		///489		/// * collection_id: ID of the Collection to remove admin for.490		///491		/// * account_id: Address of admin to remove.492		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]493		#[transactional]494		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {495			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);496			let collection = <CollectionHandle<T>>::try_get(collection_id)?;497498			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(499				collection_id,500				account_id.clone()501			));502503			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)504		}505506		/// # Permissions507		///508		/// * Collection Owner509		///510		/// # Arguments511		///512		/// * collection_id.513		///514		/// * new_sponsor.515		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]516		#[transactional]517		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {518			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);519520			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;521			target_collection.check_is_owner(&sender)?;522523			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());524525			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(526				collection_id,527				new_sponsor528			));529530			target_collection.save()531		}532533		/// # Permissions534		///535		/// * Sponsor.536		///537		/// # Arguments538		///539		/// * collection_id.540		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]541		#[transactional]542		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {543			let sender = ensure_signed(origin)?;544545			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;546			ensure!(547				target_collection.sponsorship.pending_sponsor() == Some(&sender),548				Error::<T>::ConfirmUnsetSponsorFail549			);550551			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());552553			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(554				collection_id,555				sender556			));557558			target_collection.save()559		}560561		/// Switch back to pay-per-own-transaction model.562		///563		/// # Permissions564		///565		/// * Collection owner.566		///567		/// # Arguments568		///569		/// * collection_id.570		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]571		#[transactional]572		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {573			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);574575			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;576			target_collection.check_is_owner(&sender)?;577578			target_collection.sponsorship = SponsorshipState::Disabled;579580			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(581				collection_id582			));583			target_collection.save()584		}585586		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.587		///588		/// # Permissions589		///590		/// * Collection Owner.591		/// * Collection Admin.592		/// * Anyone if593		///     * Allow List is enabled, and594		///     * Address is added to allow list, and595		///     * MintPermission is enabled (see SetMintPermission method)596		///597		/// # Arguments598		///599		/// * collection_id: ID of the collection.600		///601		/// * owner: Address, initial owner of the NFT.602		///603		/// * data: Token data to store on chain.604		#[weight = T::CommonWeightInfo::create_item()]605		#[transactional]606		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {607			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);608			let budget = budget::Value::new(2);609610			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))611		}612613		/// This method creates multiple items in a collection created with CreateCollection method.614		///615		/// # Permissions616		///617		/// * Collection Owner.618		/// * Collection Admin.619		/// * Anyone if620		///     * Allow List is enabled, and621		///     * Address is added to allow list, and622		///     * MintPermission is enabled (see SetMintPermission method)623		///624		/// # Arguments625		///626		/// * collection_id: ID of the collection.627		///628		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].629		///630		/// * owner: Address, initial owner of the NFT.631		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]632		#[transactional]633		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {634			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);635			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);636			let budget = budget::Value::new(2);637638			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))639		}640641		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]642		#[transactional]643		pub fn set_collection_properties(644			origin,645			collection_id: CollectionId,646			properties: Vec<Property>647		) -> DispatchResultWithPostInfo {648			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);649650			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);651652			dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))653		}654655		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]656		#[transactional]657		pub fn delete_collection_properties(658			origin,659			collection_id: CollectionId,660			property_keys: Vec<PropertyKey>,661		) -> DispatchResultWithPostInfo {662			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);663664			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);665666			dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))667		}668669		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]670		#[transactional]671		pub fn set_token_properties(672			origin,673			collection_id: CollectionId,674			token_id: TokenId,675			properties: Vec<Property>676		) -> DispatchResultWithPostInfo {677			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);678679			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);680681			dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))682		}683684		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]685		#[transactional]686		pub fn delete_token_properties(687			origin,688			collection_id: CollectionId,689			token_id: TokenId,690			property_keys: Vec<PropertyKey>691		) -> DispatchResultWithPostInfo {692			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);693694			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);695696			dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))697		}698699		#[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]700		#[transactional]701		pub fn set_property_permissions(702			origin,703			collection_id: CollectionId,704			property_permissions: Vec<PropertyKeyPermission>,705		) -> DispatchResultWithPostInfo {706			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);707708			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);709710			dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))711		}712713		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]714		#[transactional]715		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {716			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);717			let budget = budget::Value::new(2);718719			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))720		}721722		// TODO! transaction weight723724		/// Set transfers_enabled value for particular collection725		///726		/// # Permissions727		///728		/// * Collection Owner.729		///730		/// # Arguments731		///732		/// * collection_id: ID of the collection.733		///734		/// * value: New flag value.735		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]736		#[transactional]737		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {738			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);739			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;740			target_collection.check_is_owner(&sender)?;741742			// =========743744			target_collection.limits.transfers_enabled = Some(value);745			target_collection.save()746		}747748		/// Destroys a concrete instance of NFT.749		///750		/// # Permissions751		///752		/// * Collection Owner.753		/// * Collection Admin.754		/// * Current NFT Owner.755		///756		/// # Arguments757		///758		/// * collection_id: ID of the collection.759		///760		/// * item_id: ID of NFT to burn.761		#[weight = T::CommonWeightInfo::burn_item()]762		#[transactional]763		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {764			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);765766			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;767			if value == 1 {768				<NftTransferBasket<T>>::remove(collection_id, item_id);769				<NftApproveBasket<T>>::remove(collection_id, item_id);770			}771			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?772			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());773			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));774			Ok(post_info)775		}776777		/// Destroys a concrete instance of NFT on behalf of the owner778		/// See also: [`approve`]779		///780		/// # Permissions781		///782		/// * Collection Owner.783		/// * Collection Admin.784		/// * Current NFT Owner.785		///786		/// # Arguments787		///788		/// * collection_id: ID of the collection.789		///790		/// * item_id: ID of NFT to burn.791		///792		/// * from: owner of item793		#[weight = T::CommonWeightInfo::burn_from()]794		#[transactional]795		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {796			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);797			let budget = budget::Value::new(2);798799			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))800		}801802		/// Change ownership of the token.803		///804		/// # Permissions805		///806		/// * Collection Owner807		/// * Collection Admin808		/// * Current NFT owner809		///810		/// # Arguments811		///812		/// * recipient: Address of token recipient.813		///814		/// * collection_id.815		///816		/// * item_id: ID of the item817		///     * Non-Fungible Mode: Required.818		///     * Fungible Mode: Ignored.819		///     * Re-Fungible Mode: Required.820		///821		/// * value: Amount to transfer.822		///     * Non-Fungible Mode: Ignored823		///     * Fungible Mode: Must specify transferred amount824		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)825		#[weight = T::CommonWeightInfo::transfer()]826		#[transactional]827		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {828			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);829			let budget = budget::Value::new(2);830831			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))832		}833834		/// Set, change, or remove approved address to transfer the ownership of the NFT.835		///836		/// # Permissions837		///838		/// * Collection Owner839		/// * Collection Admin840		/// * Current NFT owner841		///842		/// # Arguments843		///844		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).845		///846		/// * collection_id.847		///848		/// * item_id: ID of the item.849		#[weight = T::CommonWeightInfo::approve()]850		#[transactional]851		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {852			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);853854			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))855		}856857		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.858		///859		/// # Permissions860		/// * Collection Owner861		/// * Collection Admin862		/// * Current NFT owner863		/// * Address approved by current NFT owner864		///865		/// # Arguments866		///867		/// * from: Address that owns token.868		///869		/// * recipient: Address of token recipient.870		///871		/// * collection_id.872		///873		/// * item_id: ID of the item.874		///875		/// * value: Amount to transfer.876		#[weight = T::CommonWeightInfo::transfer_from()]877		#[transactional]878		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {879			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);880			let budget = budget::Value::new(2);881882			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))883		}884885		#[weight = <SelfWeightOf<T>>::set_collection_limits()]886		#[transactional]887		pub fn set_collection_limits(888			origin,889			collection_id: CollectionId,890			new_limit: CollectionLimits,891		) -> DispatchResult {892			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);893			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;894			target_collection.check_is_owner(&sender)?;895			let old_limit = &target_collection.limits;896897			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;898899			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(900				collection_id901			));902903			target_collection.save()904		}905906		#[weight = <SelfWeightOf<T>>::set_collection_limits()]907		#[transactional]908		pub fn set_collection_permissions(909			origin,910			collection_id: CollectionId,911			new_limit: CollectionPermissions,912		) -> DispatchResult {913			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);914			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;915			target_collection.check_is_owner(&sender)?;916			let old_limit = &target_collection.permissions;917918			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;919920			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(921				collection_id922			));923924			target_collection.save()925		}926	}927}
after · pallets/unique/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20	clippy::too_many_arguments,21	clippy::unnecessary_mut_passed,22	clippy::unused_unit23)]2425extern crate alloc;2627use frame_support::{28	decl_module, decl_storage, decl_error, decl_event,29	dispatch::DispatchResult,30	ensure,31	weights::{Weight},32	transactional,33	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},34	BoundedVec,35};36use scale_info::TypeInfo;37use frame_system::{self as system, ensure_signed};38use sp_runtime::{sp_std::prelude::Vec};39use up_data_structs::{40	MAX_COLLECTION_NAME_LENGTH,41	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData,42	CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId, SponsorshipState,43	CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,44	PropertyKeyPermission,45};46use pallet_evm::account::CrossAccountId;47use pallet_common::{48	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_call,49	dispatch::CollectionDispatch,50};51pub mod eth;5253#[cfg(feature = "runtime-benchmarks")]54mod benchmarking;55pub mod weights;56use weights::WeightInfo;5758decl_error! {59	/// Error for non-fungible-token module.60	pub enum Error for Module<T: Config> {61		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.62		CollectionDecimalPointLimitExceeded,63		/// This address is not set as sponsor, use setCollectionSponsor first.64		ConfirmUnsetSponsorFail,65		/// Length of items properties must be greater than 0.66		EmptyArgument,67	}68}6970pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {71	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;7273	/// Weight information for extrinsics in this pallet.74	type WeightInfo: WeightInfo;75	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;76}7778decl_event! {79	pub enum Event<T>80	where81		<T as frame_system::Config>::AccountId,82		<T as pallet_evm::account::Config>::CrossAccountId,83	{84		/// Collection sponsor was removed85		///86		/// # Arguments87		///88		/// * collection_id: Globally unique collection identifier.89		CollectionSponsorRemoved(CollectionId),9091		/// Collection admin was added92		///93		/// # Arguments94		///95		/// * collection_id: Globally unique collection identifier.96		///97		/// * admin:  Admin address.98		CollectionAdminAdded(CollectionId, CrossAccountId),99100		/// Collection owned was change101		///102		/// # Arguments103		///104		/// * collection_id: Globally unique collection identifier.105		///106		/// * owner:  New owner address.107		CollectionOwnedChanged(CollectionId, AccountId),108109		/// Collection sponsor was set110		///111		/// # Arguments112		///113		/// * collection_id: Globally unique collection identifier.114		///115		/// * owner:  New sponsor address.116		CollectionSponsorSet(CollectionId, AccountId),117118		/// const on chain schema was set119		///120		/// # Arguments121		///122		/// * collection_id: Globally unique collection identifier.123		ConstOnChainSchemaSet(CollectionId),124125		/// New sponsor was confirm126		///127		/// # Arguments128		///129		/// * collection_id: Globally unique collection identifier.130		///131		/// * sponsor:  New sponsor address.132		SponsorshipConfirmed(CollectionId, AccountId),133134		/// Collection admin was removed135		///136		/// # Arguments137		///138		/// * collection_id: Globally unique collection identifier.139		///140		/// * admin:  Admin address.141		CollectionAdminRemoved(CollectionId, CrossAccountId),142143		/// Address was remove from allow list144		///145		/// # Arguments146		///147		/// * collection_id: Globally unique collection identifier.148		///149		/// * user:  Address.150		AllowListAddressRemoved(CollectionId, CrossAccountId),151152		/// Address was add to allow list153		///154		/// # Arguments155		///156		/// * collection_id: Globally unique collection identifier.157		///158		/// * user:  Address.159		AllowListAddressAdded(CollectionId, CrossAccountId),160161		/// Collection limits was set162		///163		/// # Arguments164		///165		/// * collection_id: Globally unique collection identifier.166		CollectionLimitSet(CollectionId),167168		CollectionPermissionSet(CollectionId),169170		/// Mint permission	was set171		///172		/// # Arguments173		///174		/// * collection_id: Globally unique collection identifier.175		MintPermissionSet(CollectionId),176177		/// Offchain schema was set178		///179		/// # Arguments180		///181		/// * collection_id: Globally unique collection identifier.182		OffchainSchemaSet(CollectionId),183184		/// Public access mode was set185		///186		/// # Arguments187		///188		/// * collection_id: Globally unique collection identifier.189		///190		/// * mode: New access state.191		PublicAccessModeSet(CollectionId, AccessMode),192193		/// Schema version was set194		///195		/// # Arguments196		///197		/// * collection_id: Globally unique collection identifier.198		SchemaVersionSet(CollectionId),199	}200}201202type SelfWeightOf<T> = <T as Config>::WeightInfo;203204// # Used definitions205//206// ## User control levels207//208// chain-controlled - key is uncontrolled by user209//                    i.e autoincrementing index210//                    can use non-cryptographic hash211// real - key is controlled by user212//        but it is hard to generate enough colliding values, i.e owner of signed txs213//        can use non-cryptographic hash214// controlled - key is completly controlled by users215//              i.e maps with mutable keys216//              should use cryptographic hash217//218// ## User control level downgrade reasons219//220// ?1 - chain-controlled -> controlled221//      collections/tokens can be destroyed, resulting in massive holes222// ?2 - chain-controlled -> controlled223//      same as ?1, but can be only added, resulting in easier exploitation224// ?3 - real -> controlled225//      no confirmation required, so addresses can be easily generated226decl_storage! {227	trait Store for Module<T: Config> as Unique {228229		//#region Private members230		/// Used for migrations231		ChainVersion: u64;232		//#endregion233234		//#region Tokens transfer rate limit baskets235		/// (Collection id (controlled?2), who created (real))236		/// TODO: Off chain worker should remove from this map when collection gets removed237		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;238		/// Collection id (controlled?2), token id (controlled?2)239		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;240		/// Collection id (controlled?2), owning user (real)241		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;242		/// Collection id (controlled?2), token id (controlled?2)243		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;244		//#endregion245246		/// Variable metadata sponsoring247		/// Collection id (controlled?2), token id (controlled?2)248		#[deprecated]249		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;250		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;251252		/// Approval sponsoring253		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;254		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;255		pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;256	}257}258259decl_module! {260	pub struct Module<T: Config> for enum Call261	where262		origin: T::Origin263	{264		type Error = Error<T>;265266		fn deposit_event() = default;267268		fn on_initialize(_now: T::BlockNumber) -> Weight {269			0270		}271272		fn on_runtime_upgrade() -> Weight {273			let limit = None;274275			<VariableMetaDataBasket<T>>::remove_all(limit);276277			0278		}279280		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.281		///282		/// # Permissions283		///284		/// * Anyone.285		///286		/// # Arguments287		///288		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.289		///290		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.291		///292		/// * token_prefix: UTF-8 string with token prefix.293		///294		/// * mode: [CollectionMode] collection type and type dependent data.295		// returns collection ID296		#[weight = <SelfWeightOf<T>>::create_collection()]297		#[transactional]298		#[deprecated]299		pub fn create_collection(origin,300								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,301								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,302								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,303								 mode: CollectionMode) -> DispatchResult  {304			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {305				name: collection_name,306				description: collection_description,307				token_prefix,308				mode,309				..Default::default()310			};311			Self::create_collection_ex(origin, data)312		}313314		/// This method creates a collection315		///316		/// Prefer it to deprecated [`created_collection`] method317		#[weight = <SelfWeightOf<T>>::create_collection()]318		#[transactional]319		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {320			let sender = ensure_signed(origin)?;321322			// =========323324			T::CollectionDispatch::create(sender, data)?;325326			Ok(())327		}328329		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.330		///331		/// # Permissions332		///333		/// * Collection Owner.334		///335		/// # Arguments336		///337		/// * collection_id: collection to destroy.338		#[weight = <SelfWeightOf<T>>::destroy_collection()]339		#[transactional]340		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {341			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);342			let collection = <CollectionHandle<T>>::try_get(collection_id)?;343344			// =========345346			T::CollectionDispatch::destroy(sender, collection)?;347348			<NftTransferBasket<T>>::remove_prefix(collection_id, None);349			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);350			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);351352			<NftApproveBasket<T>>::remove_prefix(collection_id, None);353			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);354			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);355356			Ok(())357		}358359		/// Add an address to allow list.360		///361		/// # Permissions362		///363		/// * Collection Owner364		/// * Collection Admin365		///366		/// # Arguments367		///368		/// * collection_id.369		///370		/// * address.371		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]372		#[transactional]373		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{374375			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);376			let collection = <CollectionHandle<T>>::try_get(collection_id)?;377378			<PalletCommon<T>>::toggle_allowlist(379				&collection,380				&sender,381				&address,382				true,383			)?;384385			Self::deposit_event(Event::<T>::AllowListAddressAdded(386				collection_id,387				address388			));389390			Ok(())391		}392393		/// Remove an address from allow list.394		///395		/// # Permissions396		///397		/// * Collection Owner398		/// * Collection Admin399		///400		/// # Arguments401		///402		/// * collection_id.403		///404		/// * address.405		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]406		#[transactional]407		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{408409			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);410			let collection = <CollectionHandle<T>>::try_get(collection_id)?;411412			<PalletCommon<T>>::toggle_allowlist(413				&collection,414				&sender,415				&address,416				false,417			)?;418419			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(420				collection_id,421				address422			));423424			Ok(())425		}426427		/// Change the owner of the collection.428		///429		/// # Permissions430		///431		/// * Collection Owner.432		///433		/// # Arguments434		///435		/// * collection_id.436		///437		/// * new_owner.438		#[weight = <SelfWeightOf<T>>::change_collection_owner()]439		#[transactional]440		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {441442			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);443444			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;445			target_collection.check_is_owner(&sender)?;446447			target_collection.owner = new_owner.clone();448			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(449				collection_id,450				new_owner451			));452453			target_collection.save()454		}455456		/// Adds an admin of the Collection.457		/// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.458		///459		/// # Permissions460		///461		/// * Collection Owner.462		/// * Collection Admin.463		///464		/// # Arguments465		///466		/// * collection_id: ID of the Collection to add admin for.467		///468		/// * new_admin_id: Address of new admin to add.469		#[weight = <SelfWeightOf<T>>::add_collection_admin()]470		#[transactional]471		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {472			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);473			let collection = <CollectionHandle<T>>::try_get(collection_id)?;474475			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(476				collection_id,477				new_admin_id.clone()478			));479480			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)481		}482483		/// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.484		///485		/// # Permissions486		///487		/// * Collection Owner.488		/// * Collection Admin.489		///490		/// # Arguments491		///492		/// * collection_id: ID of the Collection to remove admin for.493		///494		/// * account_id: Address of admin to remove.495		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]496		#[transactional]497		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {498			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);499			let collection = <CollectionHandle<T>>::try_get(collection_id)?;500501			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(502				collection_id,503				account_id.clone()504			));505506			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)507		}508509		/// # Permissions510		///511		/// * Collection Owner512		///513		/// # Arguments514		///515		/// * collection_id.516		///517		/// * new_sponsor.518		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]519		#[transactional]520		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {521			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);522523			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;524			target_collection.check_is_owner(&sender)?;525526			target_collection.set_sponsor(new_sponsor.clone());527528			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(529				collection_id,530				new_sponsor531			));532533			target_collection.save()534		}535536		/// # Permissions537		///538		/// * Sponsor.539		///540		/// # Arguments541		///542		/// * collection_id.543		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]544		#[transactional]545		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {546			let sender = ensure_signed(origin)?;547548			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;549			ensure!(550				target_collection.confirm_sponsorship(&sender),551				Error::<T>::ConfirmUnsetSponsorFail552			);553554			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(555				collection_id,556				sender557			));558559			target_collection.save()560		}561562		/// Switch back to pay-per-own-transaction model.563		///564		/// # Permissions565		///566		/// * Collection owner.567		///568		/// # Arguments569		///570		/// * collection_id.571		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]572		#[transactional]573		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {574			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);575576			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;577			target_collection.check_is_owner(&sender)?;578579			target_collection.sponsorship = SponsorshipState::Disabled;580581			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(582				collection_id583			));584			target_collection.save()585		}586587		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.588		///589		/// # Permissions590		///591		/// * Collection Owner.592		/// * Collection Admin.593		/// * Anyone if594		///     * Allow List is enabled, and595		///     * Address is added to allow list, and596		///     * MintPermission is enabled (see SetMintPermission method)597		///598		/// # Arguments599		///600		/// * collection_id: ID of the collection.601		///602		/// * owner: Address, initial owner of the NFT.603		///604		/// * data: Token data to store on chain.605		#[weight = T::CommonWeightInfo::create_item()]606		#[transactional]607		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {608			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);609			let budget = budget::Value::new(2);610611			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))612		}613614		/// This method creates multiple items in a collection created with CreateCollection method.615		///616		/// # Permissions617		///618		/// * Collection Owner.619		/// * Collection Admin.620		/// * Anyone if621		///     * Allow List is enabled, and622		///     * Address is added to allow list, and623		///     * MintPermission is enabled (see SetMintPermission method)624		///625		/// # Arguments626		///627		/// * collection_id: ID of the collection.628		///629		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].630		///631		/// * owner: Address, initial owner of the NFT.632		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]633		#[transactional]634		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {635			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);636			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);637			let budget = budget::Value::new(2);638639			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))640		}641642		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]643		#[transactional]644		pub fn set_collection_properties(645			origin,646			collection_id: CollectionId,647			properties: Vec<Property>648		) -> DispatchResultWithPostInfo {649			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);650651			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);652653			dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))654		}655656		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]657		#[transactional]658		pub fn delete_collection_properties(659			origin,660			collection_id: CollectionId,661			property_keys: Vec<PropertyKey>,662		) -> DispatchResultWithPostInfo {663			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);664665			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);666667			dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))668		}669670		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]671		#[transactional]672		pub fn set_token_properties(673			origin,674			collection_id: CollectionId,675			token_id: TokenId,676			properties: Vec<Property>677		) -> DispatchResultWithPostInfo {678			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);679680			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);681682			dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))683		}684685		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]686		#[transactional]687		pub fn delete_token_properties(688			origin,689			collection_id: CollectionId,690			token_id: TokenId,691			property_keys: Vec<PropertyKey>692		) -> DispatchResultWithPostInfo {693			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);694695			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);696697			dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))698		}699700		#[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]701		#[transactional]702		pub fn set_property_permissions(703			origin,704			collection_id: CollectionId,705			property_permissions: Vec<PropertyKeyPermission>,706		) -> DispatchResultWithPostInfo {707			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);708709			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);710711			dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))712		}713714		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]715		#[transactional]716		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {717			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);718			let budget = budget::Value::new(2);719720			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))721		}722723		// TODO! transaction weight724725		/// Set transfers_enabled value for particular collection726		///727		/// # Permissions728		///729		/// * Collection Owner.730		///731		/// # Arguments732		///733		/// * collection_id: ID of the collection.734		///735		/// * value: New flag value.736		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]737		#[transactional]738		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {739			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);740			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;741			target_collection.check_is_owner(&sender)?;742743			// =========744745			target_collection.limits.transfers_enabled = Some(value);746			target_collection.save()747		}748749		/// Destroys a concrete instance of NFT.750		///751		/// # Permissions752		///753		/// * Collection Owner.754		/// * Collection Admin.755		/// * Current NFT Owner.756		///757		/// # Arguments758		///759		/// * collection_id: ID of the collection.760		///761		/// * item_id: ID of NFT to burn.762		#[weight = T::CommonWeightInfo::burn_item()]763		#[transactional]764		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {765			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);766767			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;768			if value == 1 {769				<NftTransferBasket<T>>::remove(collection_id, item_id);770				<NftApproveBasket<T>>::remove(collection_id, item_id);771			}772			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?773			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());774			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));775			Ok(post_info)776		}777778		/// Destroys a concrete instance of NFT on behalf of the owner779		/// See also: [`approve`]780		///781		/// # Permissions782		///783		/// * Collection Owner.784		/// * Collection Admin.785		/// * Current NFT Owner.786		///787		/// # Arguments788		///789		/// * collection_id: ID of the collection.790		///791		/// * item_id: ID of NFT to burn.792		///793		/// * from: owner of item794		#[weight = T::CommonWeightInfo::burn_from()]795		#[transactional]796		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {797			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);798			let budget = budget::Value::new(2);799800			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))801		}802803		/// Change ownership of the token.804		///805		/// # Permissions806		///807		/// * Collection Owner808		/// * Collection Admin809		/// * Current NFT owner810		///811		/// # Arguments812		///813		/// * recipient: Address of token recipient.814		///815		/// * collection_id.816		///817		/// * item_id: ID of the item818		///     * Non-Fungible Mode: Required.819		///     * Fungible Mode: Ignored.820		///     * Re-Fungible Mode: Required.821		///822		/// * value: Amount to transfer.823		///     * Non-Fungible Mode: Ignored824		///     * Fungible Mode: Must specify transferred amount825		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)826		#[weight = T::CommonWeightInfo::transfer()]827		#[transactional]828		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {829			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);830			let budget = budget::Value::new(2);831832			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))833		}834835		/// Set, change, or remove approved address to transfer the ownership of the NFT.836		///837		/// # Permissions838		///839		/// * Collection Owner840		/// * Collection Admin841		/// * Current NFT owner842		///843		/// # Arguments844		///845		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).846		///847		/// * collection_id.848		///849		/// * item_id: ID of the item.850		#[weight = T::CommonWeightInfo::approve()]851		#[transactional]852		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {853			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);854855			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))856		}857858		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.859		///860		/// # Permissions861		/// * Collection Owner862		/// * Collection Admin863		/// * Current NFT owner864		/// * Address approved by current NFT owner865		///866		/// # Arguments867		///868		/// * from: Address that owns token.869		///870		/// * recipient: Address of token recipient.871		///872		/// * collection_id.873		///874		/// * item_id: ID of the item.875		///876		/// * value: Amount to transfer.877		#[weight = T::CommonWeightInfo::transfer_from()]878		#[transactional]879		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {880			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);881			let budget = budget::Value::new(2);882883			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))884		}885886		#[weight = <SelfWeightOf<T>>::set_collection_limits()]887		#[transactional]888		pub fn set_collection_limits(889			origin,890			collection_id: CollectionId,891			new_limit: CollectionLimits,892		) -> DispatchResult {893			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);894			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;895			target_collection.check_is_owner(&sender)?;896			let old_limit = &target_collection.limits;897898			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;899900			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(901				collection_id902			));903904			target_collection.save()905		}906907		#[weight = <SelfWeightOf<T>>::set_collection_limits()]908		#[transactional]909		pub fn set_collection_permissions(910			origin,911			collection_id: CollectionId,912			new_limit: CollectionPermissions,913		) -> DispatchResult {914			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);915			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;916			target_collection.check_is_owner(&sender)?;917			let old_limit = &target_collection.permissions;918919			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;920921			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(922				collection_id923			));924925			target_collection.save()926		}927	}928}
modifiedprimitives/data-structs/Cargo.tomldiffbeforeafterboth
--- a/primitives/data-structs/Cargo.toml
+++ b/primitives/data-structs/Cargo.toml
@@ -40,6 +40,6 @@
   "sp-std/std",
   "pallet-evm/std",
 ]
-serde1 = ["serde"]
+serde1 = ["serde/alloc"]
 limit-testing = []
 runtime-benchmarks = []
\ No newline at end of file
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -506,6 +506,7 @@
 }
 
 #[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 pub enum MetaUpdatePermission {
 	ItemOwner,
 	Admin,
@@ -760,6 +761,10 @@
 		self.0.get(key)
 	}
 
+	pub fn contains_key(&self, key: &PropertyKey) -> bool {
+		self.0.contains_key(key)
+	}
+
 	fn check_property_key(key: &PropertyKey) -> Result<(), PropertiesError> {
 		if key.is_empty() {
 			return Err(PropertiesError::EmptyPropertyKey);
modifiedruntime/opal/src/lib.rsdiffbeforeafterboth
--- a/runtime/opal/src/lib.rs
+++ b/runtime/opal/src/lib.rs
@@ -50,6 +50,7 @@
 pub use pallet_balances::Call as BalancesCall;
 pub use pallet_evm::{
 	EnsureAddressTruncated, HashedAddressMapping, Runner, account::CrossAccountId as _,
+	OnMethodCall, Account as EVMAccount, FeeCalculator, GasWeightMapping,
 };
 pub use frame_support::{
 	construct_runtime, match_types,
@@ -79,7 +80,6 @@
 };
 use smallvec::smallvec;
 use codec::{Encode, Decode};
-use pallet_evm::{Account as EVMAccount, FeeCalculator, GasWeightMapping};
 use fp_rpc::TransactionStatus;
 use sp_runtime::{
 	traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf, Saturating},
@@ -306,6 +306,7 @@
 		pallet_evm_migration::OnMethodCall<Self>,
 		pallet_evm_contract_helpers::HelpersOnMethodCall<Self>,
 		CollectionDispatchT<Self>,
+		pallet_unique::eth::CollectionHelperOnMethodCall<Self>,
 	);
 	type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate<Self>;
 	type ChainId = ChainId;
@@ -974,6 +975,11 @@
 	pub const HelpersContractAddress: H160 = H160([
 		0x84, 0x28, 0x99, 0xec, 0xf3, 0x80, 0x55, 0x3e, 0x8a, 0x4d, 0xe7, 0x5b, 0xf5, 0x34, 0xcd, 0xf6, 0xfb, 0xf6, 0x40, 0x49,
 	]);
+
+	// 0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f
+	pub const EvmCollectionHelperAddress: H160 = H160([
+		0x6c, 0x4e, 0x9f, 0xe1, 0xae, 0x37, 0xa4, 0x1e, 0x93, 0xce, 0xe4, 0x29, 0xe8, 0xe1, 0x88, 0x1a, 0xbd, 0xcb, 0xb5, 0x4f,
+	]);
 }
 
 impl pallet_evm_contract_helpers::Config for Runtime {
@@ -981,6 +987,10 @@
 	type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit;
 }
 
+impl pallet_unique::eth::Config for Runtime {
+	type ContractAddress = EvmCollectionHelperAddress;
+}
+
 construct_runtime!(
 	pub enum Runtime where
 		Block = Block,
modifiedruntime/quartz/src/lib.rsdiffbeforeafterboth
--- a/runtime/quartz/src/lib.rs
+++ b/runtime/quartz/src/lib.rs
@@ -66,7 +66,6 @@
 		WeightToFeePolynomial, WeightToFeeCoefficient, WeightToFeeCoefficients, ConstantMultiplier,
 	},
 };
-use unique_runtime_common::dispatch::{CollectionDispatchT, CollectionDispatch};
 use up_data_structs::*;
 // use pallet_contracts::weights::WeightInfo;
 // #[cfg(any(feature = "std", test))]
@@ -116,7 +115,15 @@
 //use xcm_executor::traits::MatchesFungible;
 use sp_runtime::traits::CheckedConversion;
 
-use unique_runtime_common::{impl_common_runtime_apis, types::*, constants::*};
+use unique_runtime_common::{
+	impl_common_runtime_apis,
+	types::*,
+	constants::*,
+	dispatch::{CollectionDispatchT, CollectionDispatch},
+	sponsoring::UniqueSponsorshipHandler,
+	eth_sponsoring::UniqueEthSponsorshipHandler,
+	weights::CommonWeights,
+};
 
 pub const RUNTIME_NAME: &str = "quartz";
 pub const TOKEN_SYMBOL: &str = "QTZ";
@@ -278,6 +285,7 @@
 		pallet_evm_migration::OnMethodCall<Self>,
 		pallet_evm_contract_helpers::HelpersOnMethodCall<Self>,
 		CollectionDispatchT<Self>,
+		pallet_unique::eth::CollectionHelperOnMethodCall<Self>,
 	);
 	type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate<Self>;
 	type ChainId = ChainId;
@@ -891,6 +899,7 @@
 impl pallet_unique::Config for Runtime {
 	type Event = Event;
 	type WeightInfo = pallet_unique::weights::SubstrateWeight<Self>;
+	type CommonWeightInfo = CommonWeights<Self>;
 }
 
 parameter_types! {
@@ -912,11 +921,11 @@
 // }
 
 type EvmSponsorshipHandler = (
-	pallet_unique::UniqueEthSponsorshipHandler<Runtime>,
+	UniqueEthSponsorshipHandler<Runtime>,
 	pallet_evm_contract_helpers::HelpersContractSponsoring<Runtime>,
 );
 type SponsorshipHandler = (
-	pallet_unique::UniqueSponsorshipHandler<Runtime>,
+	UniqueSponsorshipHandler<Runtime>,
 	//pallet_contract_helpers::ContractSponsorshipHandler<Runtime>,
 	pallet_evm_transaction_payment::BridgeSponsorshipHandler<Runtime>,
 );
@@ -951,6 +960,11 @@
 	pub const HelpersContractAddress: H160 = H160([
 		0x84, 0x28, 0x99, 0xec, 0xf3, 0x80, 0x55, 0x3e, 0x8a, 0x4d, 0xe7, 0x5b, 0xf5, 0x34, 0xcd, 0xf6, 0xfb, 0xf6, 0x40, 0x49,
 	]);
+		
+	// 0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f
+	pub const EvmCollectionHelperAddress: H160 = H160([
+		0x6c, 0x4e, 0x9f, 0xe1, 0xae, 0x37, 0xa4, 0x1e, 0x93, 0xce, 0xe4, 0x29, 0xe8, 0xe1, 0x88, 0x1a, 0xbd, 0xcb, 0xb5, 0x4f,
+	]);
 }
 
 impl pallet_evm_contract_helpers::Config for Runtime {
@@ -958,6 +972,10 @@
 	type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit;
 }
 
+impl pallet_unique::eth::Config for Runtime {
+	type ContractAddress = EvmCollectionHelperAddress;
+}
+
 construct_runtime!(
 	pub enum Runtime where
 		Block = Block,
modifiedruntime/unique/src/lib.rsdiffbeforeafterboth
--- a/runtime/unique/src/lib.rs
+++ b/runtime/unique/src/lib.rs
@@ -49,7 +49,8 @@
 // A few exports that help ease life for downstream crates.
 pub use pallet_balances::Call as BalancesCall;
 pub use pallet_evm::{
-	EnsureAddressTruncated, HashedAddressMapping, Runner, account::CrossAccountId as _,
+	EnsureAddressTruncated, HashedAddressMapping, Runner, account::CrossAccountId as _, OnMethodCall,
+	Account as EVMAccount, FeeCalculator, GasWeightMapping,
 };
 pub use frame_support::{
 	construct_runtime, match_types,
@@ -84,7 +85,6 @@
 };
 use smallvec::smallvec;
 use codec::{Encode, Decode};
-use pallet_evm::{Account as EVMAccount, FeeCalculator, GasWeightMapping};
 use fp_rpc::TransactionStatus;
 use sp_runtime::{
 	traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf, Saturating},
@@ -120,7 +120,15 @@
 //use xcm_executor::traits::MatchesFungible;
 use sp_runtime::traits::CheckedConversion;
 
-use unique_runtime_common::{impl_common_runtime_apis, types::*, constants::*};
+use unique_runtime_common::{
+	impl_common_runtime_apis,
+	types::*,
+	constants::*,
+	dispatch::{CollectionDispatchT, CollectionDispatch},
+	sponsoring::UniqueSponsorshipHandler,
+	eth_sponsoring::UniqueEthSponsorshipHandler,
+	weights::CommonWeights,
+};
 
 pub const RUNTIME_NAME: &str = "unique";
 pub const TOKEN_SYMBOL: &str = "UNQ";
@@ -282,6 +290,7 @@
 		pallet_evm_migration::OnMethodCall<Self>,
 		pallet_evm_contract_helpers::HelpersOnMethodCall<Self>,
 		CollectionDispatchT<Self>,
+		pallet_unique::eth::CollectionHelperOnMethodCall<Self>,
 	);
 	type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate<Self>;
 	type ChainId = ChainId;
@@ -956,6 +965,11 @@
 	pub const HelpersContractAddress: H160 = H160([
 		0x84, 0x28, 0x99, 0xec, 0xf3, 0x80, 0x55, 0x3e, 0x8a, 0x4d, 0xe7, 0x5b, 0xf5, 0x34, 0xcd, 0xf6, 0xfb, 0xf6, 0x40, 0x49,
 	]);
+		
+	// 0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f
+	pub const EvmCollectionHelperAddress: H160 = H160([
+		0x6c, 0x4e, 0x9f, 0xe1, 0xae, 0x37, 0xa4, 0x1e, 0x93, 0xce, 0xe4, 0x29, 0xe8, 0xe1, 0x88, 0x1a, 0xbd, 0xcb, 0xb5, 0x4f,
+	]);
 }
 
 impl pallet_evm_contract_helpers::Config for Runtime {
@@ -963,6 +977,10 @@
 	type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit;
 }
 
+impl pallet_unique::eth::Config for Runtime {
+	type ContractAddress = EvmCollectionHelperAddress;
+}
+
 construct_runtime!(
 	pub enum Runtime where
 		Block = Block,
modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -73,6 +73,7 @@
     "testBlockProduction": "mocha --timeout 9999999 -r ts-node/register ./**/block-production.test.ts",
     "testEnableDisableTransfers": "mocha --timeout 9999999 -r ts-node/register ./**/enableDisableTransfer.test.ts",
     "testLimits": "mocha --timeout 9999999 -r ts-node/register ./**/limits.test.ts",
+    "testEthCreateCollection": "mocha --timeout 9999999 -r ts-node/register ./**/eth/createCollection.test.ts",
     "polkadot-types-fetch-metadata": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > src/interfaces/metadata.json",
     "polkadot-types-from-defs": "ts-node ./node_modules/.bin/polkadot-types-from-defs --endpoint src/interfaces/metadata.json --input src/interfaces/ --package .",
     "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint src/interfaces/metadata.json --output src/interfaces/ --package .",
@@ -88,7 +89,7 @@
     "bignumber.js": "^9.0.2",
     "chai-as-promised": "^7.1.1",
     "find-process": "^1.4.7",
-    "solc": "^0.8.13",
+    "solc": "0.8.13",
     "web3": "^1.7.3"
   },
   "standard": {
addedtests/src/eth/api/CollectionHelper.soldiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/api/CollectionHelper.sol
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: OTHER
+// This code is automatically generated
+
+pragma solidity >=0.8.0 <0.9.0;
+
+// Common stubs holder
+interface Dummy {
+
+}
+
+interface ERC165 is Dummy {
+	function supportsInterface(bytes4 interfaceID) external view returns (bool);
+}
+
+// Selector: 56c215c5
+interface CollectionHelper is Dummy, ERC165 {
+	// Selector: create721Collection(string,string,string) 951c0151
+	function create721Collection(
+		string memory name,
+		string memory description,
+		string memory tokenPrefix
+	) external view returns (address);
+
+	// Selector: isCollectionExist(address) c3de1494
+	function isCollectionExist(address collectionAddress)
+		external
+		view
+		returns (bool);
+}
modifiedtests/src/eth/api/UniqueFungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -65,8 +65,8 @@
 		returns (uint256);
 }
 
-// Selector: 9b5e29c5
-interface CollectionProperties is Dummy, ERC165 {
+// Selector: f5652829
+interface Collection is Dummy, ERC165 {
 	// Selector: setCollectionProperty(string,bytes) 2f073f66
 	function setCollectionProperty(string memory key, bytes memory value)
 		external;
@@ -81,6 +81,18 @@
 		external
 		view
 		returns (bytes memory);
+
+	// Selector: ethSetSponsor(address) 8f9af356
+	function ethSetSponsor(address sponsor) external;
+
+	// Selector: ethConfirmSponsorship() a8580d1a
+	function ethConfirmSponsorship() external;
+
+	// Selector: setLimit(string,string) bf4d2014
+	function setLimit(string memory limit, string memory value) external;
+
+	// Selector: contractAddress() f6b4dfb4
+	function contractAddress() external view returns (address);
 }
 
 interface UniqueFungible is
@@ -88,5 +100,5 @@
 	ERC165,
 	ERC20,
 	ERC20UniqueExtensions,
-	CollectionProperties
+	Collection
 {}
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -191,24 +191,6 @@
 	function totalSupply() external view returns (uint256);
 }
 
-// Selector: 9b5e29c5
-interface CollectionProperties is Dummy, ERC165 {
-	// Selector: setCollectionProperty(string,bytes) 2f073f66
-	function setCollectionProperty(string memory key, bytes memory value)
-		external;
-
-	// Selector: deleteCollectionProperty(string) 7b7debce
-	function deleteCollectionProperty(string memory key) external;
-
-	// Throws error if key not found
-	//
-	// Selector: collectionProperty(string) cf24fd6d
-	function collectionProperty(string memory key)
-		external
-		view
-		returns (bytes memory);
-}
-
 // Selector: d74d154f
 interface ERC721UniqueExtensions is Dummy, ERC165 {
 	// Selector: transfer(address,uint256) a9059cbb
@@ -231,6 +213,36 @@
 		returns (bool);
 }
 
+// Selector: f5652829
+interface Collection is Dummy, ERC165 {
+	// Selector: setCollectionProperty(string,bytes) 2f073f66
+	function setCollectionProperty(string memory key, bytes memory value)
+		external;
+
+	// Selector: deleteCollectionProperty(string) 7b7debce
+	function deleteCollectionProperty(string memory key) external;
+
+	// Throws error if key not found
+	//
+	// Selector: collectionProperty(string) cf24fd6d
+	function collectionProperty(string memory key)
+		external
+		view
+		returns (bytes memory);
+
+	// Selector: ethSetSponsor(address) 8f9af356
+	function ethSetSponsor(address sponsor) external;
+
+	// Selector: ethConfirmSponsorship() a8580d1a
+	function ethConfirmSponsorship() external;
+
+	// Selector: setLimit(string,string) bf4d2014
+	function setLimit(string memory limit, string memory value) external;
+
+	// Selector: contractAddress() f6b4dfb4
+	function contractAddress() external view returns (address);
+}
+
 interface UniqueNFT is
 	Dummy,
 	ERC165,
@@ -240,6 +252,6 @@
 	ERC721UniqueExtensions,
 	ERC721Mintable,
 	ERC721Burnable,
-	CollectionProperties,
+	Collection,
 	TokenProperties
 {}
modifiedtests/src/eth/base.test.tsdiffbeforeafterboth
--- a/tests/src/eth/base.test.ts
+++ b/tests/src/eth/base.test.ts
@@ -14,7 +14,17 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, deployFlipper, ethBalanceViaSub, GAS_ARGS, itWeb3, recordEthFee, usingWeb3} from './util/helpers';
+import {
+  collectionIdToAddress, 
+  createEthAccount, 
+  createEthAccountWithBalance, 
+  deployFlipper, 
+  ethBalanceViaSub, 
+  GAS_ARGS, 
+  itWeb3, 
+  recordEthFee, 
+  usingWeb3,
+} from './util/helpers';
 import {expect} from 'chai';
 import {createCollectionExpectSuccess, createItemExpectSuccess, UNIQUE} from '../util/helpers';
 import nonFungibleAbi from './nonFungibleAbi.json';
addedtests/src/eth/collectionHelperAbi.jsondiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/collectionHelperAbi.json
@@ -0,0 +1,35 @@
+[
+  {
+    "inputs": [
+      { "internalType": "string", "name": "name", "type": "string" },
+      { "internalType": "string", "name": "description", "type": "string" },
+      { "internalType": "string", "name": "tokenPrefix", "type": "string" }
+    ],
+    "name": "create721Collection",
+    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "collectionAddress",
+        "type": "address"
+      }
+    ],
+    "name": "isCollectionExist",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }
+    ],
+    "name": "supportsInterface",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
+  }
+]
modifiedtests/src/eth/contractSponsoring.test.tsdiffbeforeafterboth
--- a/tests/src/eth/contractSponsoring.test.ts
+++ b/tests/src/eth/contractSponsoring.test.ts
@@ -29,19 +29,19 @@
   normalizeEvents,
   subToEth,
   executeEthTxOnSub,
+  evmCollectionHelper,
+  getCollectionAddressFromResult,
+  evmCollection,
 } from './util/helpers';
 import {
   addCollectionAdminExpectSuccess,
   createCollectionExpectSuccess,
-  getCreateCollectionResult,
+  getDetailedCollectionInfo,
   transferBalanceTo,
 } from '../util/helpers';
 import nonFungibleAbi from './nonFungibleAbi.json';
-import {
-  submitTransactionAsync,
-} from '../substrate/substrate-api';
 import getBalance from '../substrate/get-balance';
-import {alicesPublicKey} from '../accounts';
+import {evmToAddress} from '@polkadot/util-crypto';
 
 describe('Sponsoring EVM contracts', () => {
   itWeb3('Sponsoring can be set by the address that has deployed the contract', async ({api, web3}) => {
@@ -221,89 +221,87 @@
     expect(await helpers.methods.getSponsoringRateLimit(flipper.options.address).call()).to.be.equals('7200');
   });
 
-  itWeb3('Sponsoring evm address from substrate collection', async ({api, web3}) => {
-    const owner = privateKey('//Alice');
-    const userEth = createEthAccount(web3);
-    const collectionId = await createCollectionExpectSuccess();
+  //TODO: CORE-302 add eth methods
+  itWeb3.skip('Sponsoring evm address from substrate collection', async ({api, web3}) => {
+    const owner = await createEthAccountWithBalance(api, web3);
+    const collectionHelper = evmCollectionHelper(web3, owner);
+    let result = await collectionHelper.methods.create721Collection('Sponsor collection', '1', '1').send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+    const sponsor = await createEthAccountWithBalance(api, web3);
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    result = await collectionEvm.methods.ethSetSponsor(sponsor).send();
+    let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+    expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
+    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));
+    await expect(collectionEvm.methods.ethConfirmSponsorship().call()).to.be.rejectedWith('Caller is not set as sponsor');
 
-    {
-      const tx = api.tx.unique.setCollectionSponsor(collectionId, owner.address);
-      const events = await submitTransactionAsync(owner, tx);
-      const result = getCreateCollectionResult(events);
-      expect(result.success).to.be.true;
-    }
-    {
-      const tx = api.tx.unique.confirmSponsorship(collectionId);
-      const events = await submitTransactionAsync(owner, tx);
-      const result = getCreateCollectionResult(events);
-      expect(result.success).to.be.true;
-    }
+    const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);
+    await sponsorCollection.methods.ethConfirmSponsorship().send();
+    collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+    expect(collectionSub.sponsorship.isConfirmed).to.be.true;
+    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));
 
-    const address = collectionIdToAddress(collectionId);
-    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: userEth, ...GAS_ARGS});
+    const user = createEthAccount(web3);
+    const userContract = evmCollection(web3, user, collectionIdAddress);
+    const nextTokenId = await userContract.methods.nextTokenId().call();
 
-    { // This part should fail, because user not in access list and user have no money
-      const nextTokenId = await contract.methods.nextTokenId().call();
-      expect(nextTokenId).to.be.equal('1');
-      await expect(contract.methods.mintWithTokenURI(
-        userEth,
-        nextTokenId,
-        'Test URI',
-      ).call({from: userEth})).to.be.rejectedWith(/PublicMintingNotAllowed/);
-    }
-
-    {
-      const tx = api.tx.unique.setPublicAccessMode(collectionId, 'AllowList');
-      const events = await submitTransactionAsync(owner, tx);
-      const result = getCreateCollectionResult(events);
-      expect(result.success).to.be.true;
-    }
-    {
-      const tx = api.tx.unique.addToAllowList(collectionId, {Ethereum: userEth});
-      const events = await submitTransactionAsync(owner, tx);
-      const result = getCreateCollectionResult(events);
-      expect(result.success).to.be.true;
-    }
-    {
-      const tx = api.tx.unique.setMintPermission(collectionId, true);
-      const events = await submitTransactionAsync(owner, tx);
-      const result = getCreateCollectionResult(events);
-      expect(result.success).to.be.true;
-    }
+    expect(nextTokenId).to.be.equal('1');
+    await expect(userContract.methods.mintWithTokenURI(
+      user,
+      nextTokenId,
+      'Test URI',
+    ).call()).to.be.rejectedWith('PublicMintingNotAllowed');
+    
+    // TODO: add this methods to eth
+    // {
+    //   const tx = api.tx.unique.setPublicAccessMode(collectionId, 'AllowList');
+    //   const events = await submitTransactionAsync(owner, tx);
+    //   const result = getCreateCollectionResult(events);
+    //   expect(result.success).to.be.true;
+    // }
+    // {
+    //   const tx = api.tx.unique.addToAllowList(collectionId, {Ethereum: userEth});
+    //   const events = await submitTransactionAsync(owner, tx);
+    //   const result = getCreateCollectionResult(events);
+    //   expect(result.success).to.be.true;
+    // }
+    // {
+    //   const tx = api.tx.unique.setMintPermission(collectionId, true);
+    //   const events = await submitTransactionAsync(owner, tx);
+    //   const result = getCreateCollectionResult(events);
+    //   expect(result.success).to.be.true;
+    // }
 
-    const [alicesBalanceBefore] = await getBalance(api, [alicesPublicKey]);
+    // const [alicesBalanceBefore] = await getBalance(api, [alicesPublicKey]);
 
     {
-      const nextTokenId = await contract.methods.nextTokenId().call();
+      const nextTokenId = await userContract.methods.nextTokenId().call();
       expect(nextTokenId).to.be.equal('1');
-      const result = await contract.methods.mintWithTokenURI(
-        userEth,
+      const result = await userContract.methods.mintWithTokenURI(
+        user,
         nextTokenId,
         'Test URI',
-      ).send({from: userEth});
+      ).send();
       const events = normalizeEvents(result.events);
 
       expect(events).to.be.deep.equal([
         {
-          address,
+          collectionIdAddress,
           event: 'Transfer',
           args: {
             from: '0x0000000000000000000000000000000000000000',
-            to: userEth,
+            to: user,
             tokenId: nextTokenId,
           },
         },
       ]);
 
-      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+      expect(await userContract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
     }
-
-    const [alicesBalanceAfter] = await getBalance(api, [alicesPublicKey]);
-    expect(alicesBalanceAfter < alicesBalanceBefore).to.be.true;
   });
-
 
-  itWeb3('Check that transaction via EVM spend money from substrate address', async ({api, web3}) => {
+  //TODO: CORE-302 add eth methods
+  itWeb3.skip('Check that transaction via EVM spend money from substrate address', async ({api, web3}) => {
     const owner = privateKey('//Alice');
     const user = privateKey(`//User/${Date.now()}`);
     const userEth = subToEth(user.address);
addedtests/src/eth/createCollection.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/createCollection.test.ts
@@ -0,0 +1,236 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {evmToAddress} from '@polkadot/util-crypto';
+import {expect} from 'chai';
+import {getCreatedCollectionCount, getDetailedCollectionInfo} from '../util/helpers';
+import {
+  evmCollectionHelper,
+  collectionIdToAddress,
+  createEthAccount,
+  createEthAccountWithBalance,
+  evmCollection,
+  itWeb3,
+  getCollectionAddressFromResult,
+} from './util/helpers';
+
+describe('Create collection from EVM', () => {
+  itWeb3('Create collection', async ({api, web3}) => {
+    const owner = await createEthAccountWithBalance(api, web3);
+    const helper = evmCollectionHelper(web3, owner);
+    const collectionName = 'CollectionEVM';
+    const description = 'Some description';
+    const tokenPrefix = 'token prefix';
+  
+    const collectionCountBefore = await getCreatedCollectionCount(api);
+    const result = await helper.methods
+      .create721Collection(collectionName, description, tokenPrefix)
+      .send();
+    const collectionCountAfter = await getCreatedCollectionCount(api);
+  
+    const {collectionId, collection} = await getCollectionAddressFromResult(api, result);
+    expect(collectionCountAfter - collectionCountBefore).to.be.eq(1);
+    expect(collectionId).to.be.eq(collectionCountAfter);
+    expect(collection.name.map(v => String.fromCharCode(v.toNumber())).join('')).to.be.eq(collectionName);
+    expect(collection.description.map(v => String.fromCharCode(v.toNumber())).join('')).to.be.eq(description);
+    expect(collection.tokenPrefix.toHuman()).to.be.eq(tokenPrefix);
+    expect(collection.schemaVersion.type).to.be.eq('ImageURL');
+  });
+
+  itWeb3('Check collection address exist', async ({api, web3}) => {
+    const owner = await createEthAccountWithBalance(api, web3);
+    const collectionHelper = evmCollectionHelper(web3, owner);
+  
+    const expectedCollectionId = await getCreatedCollectionCount(api) + 1;
+    const expectedCollectionAddress = collectionIdToAddress(expectedCollectionId);
+    expect(await collectionHelper.methods
+      .isCollectionExist(expectedCollectionAddress)
+      .call()).to.be.false;
+
+    await collectionHelper.methods
+      .create721Collection('A', 'A', 'A')
+      .send();
+    
+    expect(await collectionHelper.methods
+      .isCollectionExist(expectedCollectionAddress)
+      .call()).to.be.true;
+  });
+  
+  itWeb3('Set sponsorship', async ({api, web3}) => {
+    const owner = await createEthAccountWithBalance(api, web3);
+    const collectionHelper = evmCollectionHelper(web3, owner);
+    let result = await collectionHelper.methods.create721Collection('Sponsor collection', '1', '1').send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+    const sponsor = await createEthAccountWithBalance(api, web3);
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    result = await collectionEvm.methods.ethSetSponsor(sponsor).send();
+    let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+    expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
+    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));
+    await expect(collectionEvm.methods.ethConfirmSponsorship().call()).to.be.rejectedWith('Caller is not set as sponsor');
+    const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);
+    await sponsorCollection.methods.ethConfirmSponsorship().send();
+    collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+    expect(collectionSub.sponsorship.isConfirmed).to.be.true;
+    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));
+  });
+
+  itWeb3('Set limits', async ({api, web3}) => {
+    const owner = await createEthAccountWithBalance(api, web3);
+    const collectionHelper = evmCollectionHelper(web3, owner);
+    const result = await collectionHelper.methods.create721Collection('Const collection', '5', '5').send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+    const limits = {
+      accountTokenOwnershipLimit: 1000,
+      sponsoredDataSize: 1024,
+      sponsoredDataRateLimit: 30,
+      tokenLimit: 1000000,
+      sponsorTransferTimeout: 6,
+      sponsorApproveTimeout: 6,
+      ownerCanTransfer: false,
+      ownerCanDestroy: false,
+      transfersEnabled: false,
+    };
+
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    await collectionEvm.methods.setLimit('accountTokenOwnershipLimit', limits.accountTokenOwnershipLimit.toString()).send();
+    await collectionEvm.methods.setLimit('sponsoredDataSize', limits.sponsoredDataSize.toString()).send();
+    await collectionEvm.methods.setLimit('sponsoredDataRateLimit', limits.sponsoredDataRateLimit.toString()).send();
+    await collectionEvm.methods.setLimit('tokenLimit', limits.tokenLimit.toString()).send();
+    await collectionEvm.methods.setLimit('sponsorTransferTimeout', limits.sponsorTransferTimeout.toString()).send();
+    await collectionEvm.methods.setLimit('sponsorApproveTimeout', limits.sponsorApproveTimeout.toString()).send();
+    await collectionEvm.methods.setLimit('ownerCanTransfer', limits.ownerCanTransfer.toString()).send();
+    await collectionEvm.methods.setLimit('ownerCanDestroy', limits.ownerCanDestroy.toString()).send();
+    await collectionEvm.methods.setLimit('transfersEnabled', limits.transfersEnabled.toString()).send();
+    
+    const collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+    expect(collectionSub.limits.accountTokenOwnershipLimit.unwrap().toNumber()).to.be.eq(limits.accountTokenOwnershipLimit);
+    expect(collectionSub.limits.sponsoredDataSize.unwrap().toNumber()).to.be.eq(limits.sponsoredDataSize);
+    expect(collectionSub.limits.sponsoredDataRateLimit.unwrap().asBlocks.toNumber()).to.be.eq(limits.sponsoredDataRateLimit);
+    expect(collectionSub.limits.tokenLimit.unwrap().toNumber()).to.be.eq(limits.tokenLimit);
+    expect(collectionSub.limits.sponsorTransferTimeout.unwrap().toNumber()).to.be.eq(limits.sponsorTransferTimeout);
+    expect(collectionSub.limits.sponsorApproveTimeout.unwrap().toNumber()).to.be.eq(limits.sponsorApproveTimeout);
+    expect(collectionSub.limits.ownerCanTransfer.toHuman()).to.be.eq(limits.ownerCanTransfer);
+    expect(collectionSub.limits.ownerCanDestroy.toHuman()).to.be.eq(limits.ownerCanDestroy);
+    expect(collectionSub.limits.transfersEnabled.toHuman()).to.be.eq(limits.transfersEnabled);
+  });
+
+  itWeb3('Collection address exist', async ({api, web3}) => {
+    const owner = await createEthAccountWithBalance(api, web3);
+    const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';
+    const collectionHelper = evmCollectionHelper(web3, owner);
+    expect(await collectionHelper.methods
+      .isCollectionExist(collectionAddressForNonexistentCollection).call())
+      .to.be.false;
+    
+    const result = await collectionHelper.methods.create721Collection('Collection address exist', '7', '7').send();
+    const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
+    expect(await collectionHelper.methods
+      .isCollectionExist(collectionIdAddress).call())
+      .to.be.true;
+  });
+});
+
+describe('(!negative tests!) Create collection from EVM', () => {
+  itWeb3('(!negative test!) Create collection (bad lengths)', async ({api, web3}) => {
+    const owner = await createEthAccountWithBalance(api, web3);
+    const helper = evmCollectionHelper(web3, owner);
+    {
+      const MAX_NAME_LENGHT = 64;
+      const collectionName = 'A'.repeat(MAX_NAME_LENGHT + 1);
+      const description = 'A';
+      const tokenPrefix = 'A';
+    
+      await expect(helper.methods
+        .create721Collection(collectionName, description, tokenPrefix)
+        .call()).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGHT);
+      
+    }
+    {  
+      const MAX_DESCRIPTION_LENGHT = 256;
+      const collectionName = 'A';
+      const description = 'A'.repeat(MAX_DESCRIPTION_LENGHT + 1);
+      const tokenPrefix = 'A';
+      await expect(helper.methods
+        .create721Collection(collectionName, description, tokenPrefix)
+        .call()).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGHT);
+    }
+    {  
+      const MAX_TOKEN_PREFIX_LENGHT = 16;
+      const collectionName = 'A';
+      const description = 'A';
+      const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGHT + 1);
+      await expect(helper.methods
+        .create721Collection(collectionName, description, tokenPrefix)
+        .call()).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGHT);
+    }
+  });
+  
+  itWeb3('(!negative test!) Create collection (no funds)', async ({web3}) => {
+    const owner = await createEthAccount(web3);
+    const helper = evmCollectionHelper(web3, owner);
+    const collectionName = 'A';
+    const description = 'A';
+    const tokenPrefix = 'A';
+    
+    await expect(helper.methods
+      .create721Collection(collectionName, description, tokenPrefix)
+      .call()).to.be.rejectedWith('NotSufficientFounds');
+  });
+
+  itWeb3('(!negative test!) Check owner', async ({api, web3}) => {
+    const owner = await createEthAccountWithBalance(api, web3);
+    const notOwner = await createEthAccount(web3);
+    const collectionHelper = evmCollectionHelper(web3, owner);
+    const result = await collectionHelper.methods.create721Collection('A', 'A', 'A').send();
+    const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
+    const contractEvmFromNotOwner = evmCollection(web3, notOwner, collectionIdAddress);
+    const EXPECTED_ERROR = 'NoPermission';
+    {
+      const sponsor = await createEthAccountWithBalance(api, web3);
+      await expect(contractEvmFromNotOwner.methods
+        .ethSetSponsor(sponsor)
+        .call()).to.be.rejectedWith(EXPECTED_ERROR);
+      
+      const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);
+      await expect(sponsorCollection.methods
+        .ethConfirmSponsorship()
+        .call()).to.be.rejectedWith('Caller is not set as sponsor');
+    }
+    {
+      await expect(contractEvmFromNotOwner.methods
+        .setLimit('account_token_ownership_limit', '1000')
+        .call()).to.be.rejectedWith(EXPECTED_ERROR);
+    }
+  });
+
+  itWeb3('(!negative test!) Set limits', async ({api, web3}) => {
+    const owner = await createEthAccountWithBalance(api, web3);
+    const collectionHelper = evmCollectionHelper(web3, owner);
+    const result = await collectionHelper.methods.create721Collection('Schema collection', 'A', 'A').send();
+    const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    await expect(collectionEvm.methods
+      .setLimit('badLimit', 'true')
+      .call()).to.be.rejectedWith('Unknown limit "badLimit"');
+    await expect(collectionEvm.methods
+      .setLimit('sponsoredDataSize', 'badValue')
+      .call()).to.be.rejectedWith('Int value "badValue" parse error:');
+    await expect(collectionEvm.methods
+      .setLimit('ownerCanTransfer', 'badValue')
+      .call()).to.be.rejectedWith('Bool value "badValue" parse error:');
+  });
+});
\ No newline at end of file
modifiedtests/src/eth/fungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/fungibleAbi.json
+++ b/tests/src/eth/fungibleAbi.json
@@ -1,175 +1,206 @@
 [
-    {
-        "constant": false,
-        "inputs": [
-            {
-                "name": "_spender",
-                "type": "address"
-            },
-            {
-                "name": "_value",
-                "type": "uint256"
-            }
-        ],
-        "name": "approve",
-        "outputs": [
-            {
-                "name": "",
-                "type": "bool"
-            }
-        ],
-        "payable": false,
-        "stateMutability": "nonpayable",
-        "type": "function"
-    },
-    {
-        "constant": true,
-        "inputs": [],
-        "name": "totalSupply",
-        "outputs": [
-            {
-                "name": "",
-                "type": "uint256"
-            }
-        ],
-        "payable": false,
-        "stateMutability": "view",
-        "type": "function"
-    },
-    {
-        "constant": false,
-        "inputs": [
-            {
-                "name": "_from",
-                "type": "address"
-            },
-            {
-                "name": "_to",
-                "type": "address"
-            },
-            {
-                "name": "_value",
-                "type": "uint256"
-            }
-        ],
-        "name": "transferFrom",
-        "outputs": [
-            {
-                "name": "",
-                "type": "bool"
-            }
-        ],
-        "payable": false,
-        "stateMutability": "nonpayable",
-        "type": "function"
-    },
-    {
-        "constant": true,
-        "inputs": [
-            {
-                "name": "_owner",
-                "type": "address"
-            }
-        ],
-        "name": "balanceOf",
-        "outputs": [
-            {
-                "name": "balance",
-                "type": "uint256"
-            }
-        ],
-        "payable": false,
-        "stateMutability": "view",
-        "type": "function"
-    },
-    {
-        "constant": false,
-        "inputs": [
-            {
-                "name": "_to",
-                "type": "address"
-            },
-            {
-                "name": "_value",
-                "type": "uint256"
-            }
-        ],
-        "name": "transfer",
-        "outputs": [
-            {
-                "name": "",
-                "type": "bool"
-            }
-        ],
-        "payable": false,
-        "stateMutability": "nonpayable",
-        "type": "function"
-    },
-    {
-        "constant": true,
-        "inputs": [
-            {
-                "name": "_owner",
-                "type": "address"
-            },
-            {
-                "name": "_spender",
-                "type": "address"
-            }
-        ],
-        "name": "allowance",
-        "outputs": [
-            {
-                "name": "",
-                "type": "uint256"
-            }
-        ],
-        "payable": false,
-        "stateMutability": "view",
-        "type": "function"
-    },
-    {
-        "anonymous": false,
-        "inputs": [
-            {
-                "indexed": true,
-                "name": "owner",
-                "type": "address"
-            },
-            {
-                "indexed": true,
-                "name": "spender",
-                "type": "address"
-            },
-            {
-                "indexed": false,
-                "name": "value",
-                "type": "uint256"
-            }
-        ],
-        "name": "Approval",
-        "type": "event"
-    },
-    {
-        "anonymous": false,
-        "inputs": [
-            {
-                "indexed": true,
-                "name": "from",
-                "type": "address"
-            },
-            {
-                "indexed": true,
-                "name": "to",
-                "type": "address"
-            },
-            {
-                "indexed": false,
-                "name": "value",
-                "type": "uint256"
-            }
-        ],
-        "name": "Transfer",
-        "type": "event"
-    }
-]
\ No newline at end of file
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "value",
+        "type": "uint256"
+      }
+    ],
+    "name": "Approval",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "from",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "value",
+        "type": "uint256"
+      }
+    ],
+    "name": "Transfer",
+    "type": "event"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "owner", "type": "address" },
+      { "internalType": "address", "name": "spender", "type": "address" }
+    ],
+    "name": "allowance",
+    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "spender", "type": "address" },
+      { "internalType": "uint256", "name": "amount", "type": "uint256" }
+    ],
+    "name": "approve",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "owner", "type": "address" }
+    ],
+    "name": "balanceOf",
+    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "from", "type": "address" },
+      { "internalType": "uint256", "name": "amount", "type": "uint256" }
+    ],
+    "name": "burnFrom",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],
+    "name": "collectionProperty",
+    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "contractAddress",
+    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "decimals",
+    "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],
+    "name": "deleteCollectionProperty",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "ethConfirmSponsorship",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "sponsor", "type": "address" }
+    ],
+    "name": "ethSetSponsor",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "name",
+    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "string", "name": "key", "type": "string" },
+      { "internalType": "bytes", "name": "value", "type": "bytes" }
+    ],
+    "name": "setCollectionProperty",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "string", "name": "limit", "type": "string" },
+      { "internalType": "string", "name": "value", "type": "string" }
+    ],
+    "name": "setLimit",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }
+    ],
+    "name": "supportsInterface",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "symbol",
+    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "totalSupply",
+    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "to", "type": "address" },
+      { "internalType": "uint256", "name": "amount", "type": "uint256" }
+    ],
+    "name": "transfer",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "from", "type": "address" },
+      { "internalType": "address", "name": "to", "type": "address" },
+      { "internalType": "uint256", "name": "amount", "type": "uint256" }
+    ],
+    "name": "transferFrom",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  }
+]
deletedtests/src/eth/fungibleMetadataAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/fungibleMetadataAbi.json
+++ /dev/null
@@ -1,41 +0,0 @@
-[
-    {
-        "inputs": [],
-        "name": "name",
-        "outputs": [
-            {
-                "internalType": "string",
-                "name": "",
-                "type": "string"
-            }
-        ],
-        "stateMutability": "view",
-        "type": "function"
-    },
-    {
-        "inputs": [],
-        "name": "symbol",
-        "outputs": [
-            {
-                "internalType": "string",
-                "name": "",
-                "type": "string"
-            }
-        ],
-        "stateMutability": "view",
-        "type": "function"
-    },
-    {
-        "inputs": [],
-        "name": "decimals",
-        "outputs": [
-            {
-                "internalType": "uint8",
-                "name": "",
-                "type": "uint8"
-            }
-        ],
-        "stateMutability": "view",
-        "type": "function"
-    }
-]
\ No newline at end of file
deletedtests/src/eth/metadata.test.tsdiffbeforeafterboth
--- a/tests/src/eth/metadata.test.ts
+++ /dev/null
@@ -1,210 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-import {expect} from 'chai';
-import {createCollectionExpectSuccess} from '../util/helpers';
-import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents} from './util/helpers';
-import fungibleMetadataAbi from './fungibleMetadataAbi.json';
-import privateKey from '../substrate/privateKey';
-import {submitTransactionAsync} from '../substrate/substrate-api';
-import nonFungibleAbi from './nonFungibleAbi.json';
-
-describe('Common metadata', () => {
-  itWeb3('Returns collection name', async ({api, web3}) => {
-    const collection = await createCollectionExpectSuccess({
-      name: 'token name',
-      mode: {type: 'NFT'},
-    });
-    const caller = await createEthAccountWithBalance(api, web3);
-
-    const address = collectionIdToAddress(collection);
-    const contract = new web3.eth.Contract(fungibleMetadataAbi as any, address, {from: caller, ...GAS_ARGS});
-    const name = await contract.methods.name().call();
-
-    expect(name).to.equal('token name');
-  });
-
-  itWeb3('Returns symbol name', async ({api, web3}) => {
-    const collection = await createCollectionExpectSuccess({
-      tokenPrefix: 'TOK',
-      mode: {type: 'NFT'},
-    });
-    const caller = await createEthAccountWithBalance(api, web3);
-
-    const address = collectionIdToAddress(collection);
-    const contract = new web3.eth.Contract(fungibleMetadataAbi as any, address, {from: caller, ...GAS_ARGS});
-    const symbol = await contract.methods.symbol().call();
-
-    expect(symbol).to.equal('TOK');
-  });
-});
-
-describe('Fungible metadata', () => {
-  itWeb3('Returns fungible decimals', async ({api, web3}) => {
-    const collection = await createCollectionExpectSuccess({
-      mode: {type: 'Fungible', decimalPoints: 6},
-    });
-    const caller = await createEthAccountWithBalance(api, web3);
-
-    const address = collectionIdToAddress(collection);
-    const contract = new web3.eth.Contract(fungibleMetadataAbi as any, address, {from: caller, ...GAS_ARGS});
-    const decimals = await contract.methods.decimals().call();
-
-    expect(+decimals).to.equal(6);
-  });
-});
-
-describe('Support ERC721Metadata', () => {
-  itWeb3('Check unsupport ERC721Metadata SchemaVersion::Unique', async ({web3, api}) => {
-    const collectionId = await createCollectionExpectSuccess({
-      mode: {type: 'NFT'},
-      schemaVersion: 'Unique',
-      name: 'some_name',
-      tokenPrefix: 'some_prefix',
-    });
-    const collection = await api.rpc.unique.collectionById(collectionId);
-    expect(collection.isSome).to.be.true;
-    expect(collection.unwrap().schemaVersion.toHuman()).to.be.eq('Unique');
-
-    const alice = privateKey('//Alice');
-
-    const caller = await createEthAccountWithBalance(api, web3);
-    const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, {Ethereum: caller});
-    await submitTransactionAsync(alice, changeAdminTx);
-
-    const address = collectionIdToAddress(collectionId);
-    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
-
-    expect(await contract.methods.name().call()).to.be.eq('some_name');
-    expect(await contract.methods.symbol().call()).to.be.eq('some_prefix');
-
-    const receiver = createEthAccount(web3);
-    const nextTokenId = await contract.methods.nextTokenId().call();
-    expect(nextTokenId).to.be.equal('1');
-    await expect(contract.methods.mintWithTokenURI(
-      receiver,
-      nextTokenId,
-      'Test URI',
-    ).call({from: caller})).to.be.rejectedWith('Unsupported schema version! Support only ImageURL');
-
-    await expect(contract.methods.mintBulkWithTokenURI(
-      receiver,
-      [
-        [nextTokenId, 'Test URI 0'],
-        [+nextTokenId + 1, 'Test URI 1'],
-        [+nextTokenId + 2, 'Test URI 2'],
-      ],
-    ).call({from: caller})).to.be.rejectedWith('Unsupported schema version! Support only ImageURL');
-  });
-
-  itWeb3('Check support ERC721Metadata for SchemaVersion::ImageURL', async ({web3, api}) => {
-    const collectionId = await createCollectionExpectSuccess({
-      mode: {type: 'NFT'},
-      name: 'some_name',
-      tokenPrefix: 'some_prefix',
-    });
-    const collection = await api.rpc.unique.collectionById(collectionId);
-    expect(collection.isSome).to.be.true;
-    expect(collection.unwrap().schemaVersion.toHuman()).to.be.eq('ImageURL');
-
-    const alice = privateKey('//Alice');
-
-    const caller = await createEthAccountWithBalance(api, web3);
-    const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, {Ethereum: caller});
-    await submitTransactionAsync(alice, changeAdminTx);
-
-    const address = collectionIdToAddress(collectionId);
-    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
-    
-    expect(await contract.methods.name().call()).to.be.eq('some_name');
-    expect(await contract.methods.symbol().call()).to.be.eq('some_prefix');
-
-    const receiver = createEthAccount(web3);
-    { // mintWithTokenURI
-      const nextTokenId = await contract.methods.nextTokenId().call();
-      expect(nextTokenId).to.be.equal('1');
-      const result = await contract.methods.mintWithTokenURI(
-        receiver,
-        nextTokenId,
-        'Test URI',
-      ).send({from: caller});
-      const events = normalizeEvents(result.events);
-  
-      expect(events).to.be.deep.equal([
-        {
-          address,
-          event: 'Transfer',
-          args: {
-            from: '0x0000000000000000000000000000000000000000',
-            to: receiver,
-            tokenId: nextTokenId,
-          },
-        },
-      ]);
-  
-      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
-    }
-
-    { // mintBulkWithTokenURI
-      const nextTokenId = await contract.methods.nextTokenId().call();
-      expect(nextTokenId).to.be.equal('2');
-      const result = await contract.methods.mintBulkWithTokenURI(
-        receiver,
-        [
-          [nextTokenId, 'Test URI 0'],
-          [+nextTokenId + 1, 'Test URI 1'],
-          [+nextTokenId + 2, 'Test URI 2'],
-        ],
-      ).send({from: caller});
-      const events = normalizeEvents(result.events);
-
-      expect(events).to.be.deep.equal([
-        {
-          address,
-          event: 'Transfer',
-          args: {
-            from: '0x0000000000000000000000000000000000000000',
-            to: receiver,
-            tokenId: nextTokenId,
-          },
-        },
-        {
-          address,
-          event: 'Transfer',
-          args: {
-            from: '0x0000000000000000000000000000000000000000',
-            to: receiver,
-            tokenId: String(+nextTokenId + 1),
-          },
-        },
-        {
-          address,
-          event: 'Transfer',
-          args: {
-            from: '0x0000000000000000000000000000000000000000',
-            to: receiver,
-            tokenId: String(+nextTokenId + 2),
-          },
-        },
-      ]);
-
-      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI 0');
-      expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1');
-      expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2');
-    }
-  });
-});
-
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -16,7 +16,7 @@
 
 import privateKey from '../substrate/privateKey';
 import {approveExpectSuccess, burnItemExpectSuccess, createCollectionExpectSuccess, createItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE} from '../util/helpers';
-import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth} from './util/helpers';
+import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelper, GAS_ARGS, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth} from './util/helpers';
 import nonFungibleAbi from './nonFungibleAbi.json';
 import {expect} from 'chai';
 import {submitTransactionAsync} from '../substrate/substrate-api';
@@ -75,45 +75,46 @@
 
 describe('NFT: Plain calls', () => {
   itWeb3('Can perform mint()', async ({web3, api}) => {
-    const collection = await createCollectionExpectSuccess({
-      mode: {type: 'NFT'},
-    });
-    const alice = privateKey('//Alice');
-
-    const caller = await createEthAccountWithBalance(api, web3);
-    const changeAdminTx = api.tx.unique.addCollectionAdmin(collection, {Ethereum: caller});
-    await submitTransactionAsync(alice, changeAdminTx);
+    const owner = await createEthAccountWithBalance(api, web3);
+    const helper = evmCollectionHelper(web3, owner);
+    let result = await helper.methods.create721Collection('Mint collection', '6', '6').send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
     const receiver = createEthAccount(web3);
+    const contract = evmCollection(web3, owner, collectionIdAddress);
+    const nextTokenId = await contract.methods.nextTokenId().call();
 
-    const address = collectionIdToAddress(collection);
-    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
+    expect(nextTokenId).to.be.equal('1');
+    result = await contract.methods.mintWithTokenURI(
+      receiver,
+      nextTokenId,
+      'Test URI',
+    ).send();
 
-    {
-      const nextTokenId = await contract.methods.nextTokenId().call();
-      expect(nextTokenId).to.be.equal('1');
-      const result = await contract.methods.mintWithTokenURI(
-        receiver,
-        nextTokenId,
-        'Test URI',
-      ).send({from: caller});
-      const events = normalizeEvents(result.events);
+    const events = normalizeEvents(result.events);
+    const address = collectionIdToAddress(collectionId);
 
-      expect(events).to.be.deep.equal([
-        {
-          address,
-          event: 'Transfer',
-          args: {
-            from: '0x0000000000000000000000000000000000000000',
-            to: receiver,
-            tokenId: nextTokenId,
-          },
+    expect(events).to.be.deep.equal([
+      {
+        address,
+        event: 'Transfer',
+        args: {
+          from: '0x0000000000000000000000000000000000000000',
+          to: receiver,
+          tokenId: nextTokenId,
         },
-      ]);
+      },
+    ]);
+
+    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
 
-      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
-    }
+    // TODO: this wont work right now, need release 919000 first
+    // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send();
+    // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();
+    // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);
   });
-  itWeb3('Can perform mintBulk()', async ({web3, api}) => {
+
+  //TODO: CORE-302 add eth methods
+  itWeb3.skip('Can perform mintBulk()', async ({web3, api}) => {
     const collection = await createCollectionExpectSuccess({
       mode: {type: 'NFT'},
     });
@@ -539,3 +540,33 @@
     ]);
   });
 });
+
+describe('Common metadata', () => {
+  itWeb3('Returns collection name', async ({api, web3}) => {
+    const collection = await createCollectionExpectSuccess({
+      name: 'token name',
+      mode: {type: 'NFT'},
+    });
+    const caller = await createEthAccountWithBalance(api, web3);
+
+    const address = collectionIdToAddress(collection);
+    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
+    const name = await contract.methods.name().call();
+
+    expect(name).to.equal('token name');
+  });
+
+  itWeb3('Returns symbol name', async ({api, web3}) => {
+    const collection = await createCollectionExpectSuccess({
+      tokenPrefix: 'TOK',
+      mode: {type: 'NFT'},
+    });
+    const caller = await createEthAccountWithBalance(api, web3);
+
+    const address = collectionIdToAddress(collection);
+    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
+    const symbol = await contract.methods.symbol().call();
+
+    expect(symbol).to.equal('TOK');
+  });
+});
\ No newline at end of file
modifiedtests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -1,738 +1,431 @@
 [
-	{
-		"anonymous": false,
-		"inputs": [
-			{
-				"indexed": true,
-				"internalType": "address",
-				"name": "owner",
-				"type": "address"
-			},
-			{
-				"indexed": true,
-				"internalType": "address",
-				"name": "approved",
-				"type": "address"
-			},
-			{
-				"indexed": true,
-				"internalType": "uint256",
-				"name": "tokenId",
-				"type": "uint256"
-			}
-		],
-		"name": "Approval",
-		"type": "event"
-	},
-	{
-		"anonymous": false,
-		"inputs": [
-			{
-				"indexed": true,
-				"internalType": "address",
-				"name": "owner",
-				"type": "address"
-			},
-			{
-				"indexed": true,
-				"internalType": "address",
-				"name": "operator",
-				"type": "address"
-			},
-			{
-				"indexed": false,
-				"internalType": "bool",
-				"name": "approved",
-				"type": "bool"
-			}
-		],
-		"name": "ApprovalForAll",
-		"type": "event"
-	},
-	{
-		"anonymous": false,
-		"inputs": [],
-		"name": "MintingFinished",
-		"type": "event"
-	},
-	{
-		"anonymous": false,
-		"inputs": [
-			{
-				"indexed": true,
-				"internalType": "address",
-				"name": "from",
-				"type": "address"
-			},
-			{
-				"indexed": true,
-				"internalType": "address",
-				"name": "to",
-				"type": "address"
-			},
-			{
-				"indexed": true,
-				"internalType": "uint256",
-				"name": "tokenId",
-				"type": "uint256"
-			}
-		],
-		"name": "Transfer",
-		"type": "event"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "address",
-				"name": "approved",
-				"type": "address"
-			},
-			{
-				"internalType": "uint256",
-				"name": "tokenId",
-				"type": "uint256"
-			}
-		],
-		"name": "approve",
-		"outputs": [],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "address",
-				"name": "owner",
-				"type": "address"
-			}
-		],
-		"name": "balanceOf",
-		"outputs": [
-			{
-				"internalType": "uint256",
-				"name": "",
-				"type": "uint256"
-			}
-		],
-		"stateMutability": "view",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "uint256",
-				"name": "tokenId",
-				"type": "uint256"
-			}
-		],
-		"name": "burn",
-		"outputs": [],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "address",
-				"name": "from",
-				"type": "address"
-			},
-			{
-				"internalType": "uint256",
-				"name": "tokenId",
-				"type": "uint256"
-			}
-		],
-		"name": "burnFrom",
-		"outputs": [],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "string",
-				"name": "key",
-				"type": "string"
-			}
-		],
-		"name": "collectionProperty",
-		"outputs": [
-			{
-				"internalType": "bytes",
-				"name": "",
-				"type": "bytes"
-			}
-		],
-		"stateMutability": "view",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "string",
-				"name": "key",
-				"type": "string"
-			}
-		],
-		"name": "deleteCollectionProperty",
-		"outputs": [],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "uint256",
-				"name": "tokenId",
-				"type": "uint256"
-			},
-			{
-				"internalType": "string",
-				"name": "key",
-				"type": "string"
-			}
-		],
-		"name": "deleteProperty",
-		"outputs": [],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	},
-	{
-		"inputs": [],
-		"name": "finishMinting",
-		"outputs": [
-			{
-				"internalType": "bool",
-				"name": "",
-				"type": "bool"
-			}
-		],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "uint256",
-				"name": "tokenId",
-				"type": "uint256"
-			}
-		],
-		"name": "getApproved",
-		"outputs": [
-			{
-				"internalType": "address",
-				"name": "",
-				"type": "address"
-			}
-		],
-		"stateMutability": "view",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "address",
-				"name": "owner",
-				"type": "address"
-			},
-			{
-				"internalType": "address",
-				"name": "operator",
-				"type": "address"
-			}
-		],
-		"name": "isApprovedForAll",
-		"outputs": [
-			{
-				"internalType": "address",
-				"name": "",
-				"type": "address"
-			}
-		],
-		"stateMutability": "view",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "address",
-				"name": "to",
-				"type": "address"
-			},
-			{
-				"internalType": "uint256",
-				"name": "tokenId",
-				"type": "uint256"
-			}
-		],
-		"name": "mint",
-		"outputs": [
-			{
-				"internalType": "bool",
-				"name": "",
-				"type": "bool"
-			}
-		],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "address",
-				"name": "to",
-				"type": "address"
-			},
-			{
-				"internalType": "uint256[]",
-				"name": "tokenIds",
-				"type": "uint256[]"
-			}
-		],
-		"name": "mintBulk",
-		"outputs": [
-			{
-				"internalType": "bool",
-				"name": "",
-				"type": "bool"
-			}
-		],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "address",
-				"name": "to",
-				"type": "address"
-			},
-			{
-				"components": [
-					{
-						"internalType": "uint256",
-						"name": "field_0",
-						"type": "uint256"
-					},
-					{
-						"internalType": "string",
-						"name": "field_1",
-						"type": "string"
-					}
-				],
-				"internalType": "struct Tuple0[]",
-				"name": "tokens",
-				"type": "tuple[]"
-			}
-		],
-		"name": "mintBulkWithTokenURI",
-		"outputs": [
-			{
-				"internalType": "bool",
-				"name": "",
-				"type": "bool"
-			}
-		],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "address",
-				"name": "to",
-				"type": "address"
-			},
-			{
-				"internalType": "uint256",
-				"name": "tokenId",
-				"type": "uint256"
-			},
-			{
-				"internalType": "string",
-				"name": "tokenUri",
-				"type": "string"
-			}
-		],
-		"name": "mintWithTokenURI",
-		"outputs": [
-			{
-				"internalType": "bool",
-				"name": "",
-				"type": "bool"
-			}
-		],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	},
-	{
-		"inputs": [],
-		"name": "mintingFinished",
-		"outputs": [
-			{
-				"internalType": "bool",
-				"name": "",
-				"type": "bool"
-			}
-		],
-		"stateMutability": "view",
-		"type": "function"
-	},
-	{
-		"inputs": [],
-		"name": "name",
-		"outputs": [
-			{
-				"internalType": "string",
-				"name": "",
-				"type": "string"
-			}
-		],
-		"stateMutability": "view",
-		"type": "function"
-	},
-	{
-		"inputs": [],
-		"name": "nextTokenId",
-		"outputs": [
-			{
-				"internalType": "uint256",
-				"name": "",
-				"type": "uint256"
-			}
-		],
-		"stateMutability": "view",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "uint256",
-				"name": "tokenId",
-				"type": "uint256"
-			}
-		],
-		"name": "ownerOf",
-		"outputs": [
-			{
-				"internalType": "address",
-				"name": "",
-				"type": "address"
-			}
-		],
-		"stateMutability": "view",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "uint256",
-				"name": "tokenId",
-				"type": "uint256"
-			},
-			{
-				"internalType": "string",
-				"name": "key",
-				"type": "string"
-			}
-		],
-		"name": "property",
-		"outputs": [
-			{
-				"internalType": "bytes",
-				"name": "",
-				"type": "bytes"
-			}
-		],
-		"stateMutability": "view",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "address",
-				"name": "from",
-				"type": "address"
-			},
-			{
-				"internalType": "address",
-				"name": "to",
-				"type": "address"
-			},
-			{
-				"internalType": "uint256",
-				"name": "tokenId",
-				"type": "uint256"
-			}
-		],
-		"name": "safeTransferFrom",
-		"outputs": [],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "address",
-				"name": "from",
-				"type": "address"
-			},
-			{
-				"internalType": "address",
-				"name": "to",
-				"type": "address"
-			},
-			{
-				"internalType": "uint256",
-				"name": "tokenId",
-				"type": "uint256"
-			},
-			{
-				"internalType": "bytes",
-				"name": "data",
-				"type": "bytes"
-			}
-		],
-		"name": "safeTransferFromWithData",
-		"outputs": [],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "address",
-				"name": "operator",
-				"type": "address"
-			},
-			{
-				"internalType": "bool",
-				"name": "approved",
-				"type": "bool"
-			}
-		],
-		"name": "setApprovalForAll",
-		"outputs": [],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "string",
-				"name": "key",
-				"type": "string"
-			},
-			{
-				"internalType": "bytes",
-				"name": "value",
-				"type": "bytes"
-			}
-		],
-		"name": "setCollectionProperty",
-		"outputs": [],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "uint256",
-				"name": "tokenId",
-				"type": "uint256"
-			},
-			{
-				"internalType": "string",
-				"name": "key",
-				"type": "string"
-			},
-			{
-				"internalType": "bytes",
-				"name": "value",
-				"type": "bytes"
-			}
-		],
-		"name": "setProperty",
-		"outputs": [],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "string",
-				"name": "key",
-				"type": "string"
-			},
-			{
-				"internalType": "bool",
-				"name": "isMutable",
-				"type": "bool"
-			},
-			{
-				"internalType": "bool",
-				"name": "collectionAdmin",
-				"type": "bool"
-			},
-			{
-				"internalType": "bool",
-				"name": "tokenOwner",
-				"type": "bool"
-			}
-		],
-		"name": "setTokenPropertyPermission",
-		"outputs": [],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "bytes4",
-				"name": "interfaceID",
-				"type": "bytes4"
-			}
-		],
-		"name": "supportsInterface",
-		"outputs": [
-			{
-				"internalType": "bool",
-				"name": "",
-				"type": "bool"
-			}
-		],
-		"stateMutability": "view",
-		"type": "function"
-	},
-	{
-		"inputs": [],
-		"name": "symbol",
-		"outputs": [
-			{
-				"internalType": "string",
-				"name": "",
-				"type": "string"
-			}
-		],
-		"stateMutability": "view",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "uint256",
-				"name": "index",
-				"type": "uint256"
-			}
-		],
-		"name": "tokenByIndex",
-		"outputs": [
-			{
-				"internalType": "uint256",
-				"name": "",
-				"type": "uint256"
-			}
-		],
-		"stateMutability": "view",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "address",
-				"name": "owner",
-				"type": "address"
-			},
-			{
-				"internalType": "uint256",
-				"name": "index",
-				"type": "uint256"
-			}
-		],
-		"name": "tokenOfOwnerByIndex",
-		"outputs": [
-			{
-				"internalType": "uint256",
-				"name": "",
-				"type": "uint256"
-			}
-		],
-		"stateMutability": "view",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "uint256",
-				"name": "tokenId",
-				"type": "uint256"
-			}
-		],
-		"name": "tokenURI",
-		"outputs": [
-			{
-				"internalType": "string",
-				"name": "",
-				"type": "string"
-			}
-		],
-		"stateMutability": "view",
-		"type": "function"
-	},
-	{
-		"inputs": [],
-		"name": "totalSupply",
-		"outputs": [
-			{
-				"internalType": "uint256",
-				"name": "",
-				"type": "uint256"
-			}
-		],
-		"stateMutability": "view",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "address",
-				"name": "to",
-				"type": "address"
-			},
-			{
-				"internalType": "uint256",
-				"name": "tokenId",
-				"type": "uint256"
-			}
-		],
-		"name": "transfer",
-		"outputs": [],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "address",
-				"name": "from",
-				"type": "address"
-			},
-			{
-				"internalType": "address",
-				"name": "to",
-				"type": "address"
-			},
-			{
-				"internalType": "uint256",
-				"name": "tokenId",
-				"type": "uint256"
-			}
-		],
-		"name": "transferFrom",
-		"outputs": [],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	}
-]
\ No newline at end of file
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "approved",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "tokenId",
+        "type": "uint256"
+      }
+    ],
+    "name": "Approval",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "operator",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "bool",
+        "name": "approved",
+        "type": "bool"
+      }
+    ],
+    "name": "ApprovalForAll",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [],
+    "name": "MintingFinished",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "from",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "tokenId",
+        "type": "uint256"
+      }
+    ],
+    "name": "Transfer",
+    "type": "event"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "approved", "type": "address" },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "approve",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "owner", "type": "address" }
+    ],
+    "name": "balanceOf",
+    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "burn",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "from", "type": "address" },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "burnFrom",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],
+    "name": "collectionProperty",
+    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "contractAddress",
+    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],
+    "name": "deleteCollectionProperty",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },
+      { "internalType": "string", "name": "key", "type": "string" }
+    ],
+    "name": "deleteProperty",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "ethConfirmSponsorship",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "sponsor", "type": "address" }
+    ],
+    "name": "ethSetSponsor",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "finishMinting",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "getApproved",
+    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "owner", "type": "address" },
+      { "internalType": "address", "name": "operator", "type": "address" }
+    ],
+    "name": "isApprovedForAll",
+    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "to", "type": "address" },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "mint",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "to", "type": "address" },
+      { "internalType": "uint256[]", "name": "tokenIds", "type": "uint256[]" }
+    ],
+    "name": "mintBulk",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "to", "type": "address" },
+      {
+        "components": [
+          { "internalType": "uint256", "name": "field_0", "type": "uint256" },
+          { "internalType": "string", "name": "field_1", "type": "string" }
+        ],
+        "internalType": "struct Tuple0[]",
+        "name": "tokens",
+        "type": "tuple[]"
+      }
+    ],
+    "name": "mintBulkWithTokenURI",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "to", "type": "address" },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },
+      { "internalType": "string", "name": "tokenUri", "type": "string" }
+    ],
+    "name": "mintWithTokenURI",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "mintingFinished",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "name",
+    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "nextTokenId",
+    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "ownerOf",
+    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },
+      { "internalType": "string", "name": "key", "type": "string" }
+    ],
+    "name": "property",
+    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "from", "type": "address" },
+      { "internalType": "address", "name": "to", "type": "address" },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "safeTransferFrom",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "from", "type": "address" },
+      { "internalType": "address", "name": "to", "type": "address" },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },
+      { "internalType": "bytes", "name": "data", "type": "bytes" }
+    ],
+    "name": "safeTransferFromWithData",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "operator", "type": "address" },
+      { "internalType": "bool", "name": "approved", "type": "bool" }
+    ],
+    "name": "setApprovalForAll",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "string", "name": "key", "type": "string" },
+      { "internalType": "bytes", "name": "value", "type": "bytes" }
+    ],
+    "name": "setCollectionProperty",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "string", "name": "limit", "type": "string" },
+      { "internalType": "string", "name": "value", "type": "string" }
+    ],
+    "name": "setLimit",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },
+      { "internalType": "string", "name": "key", "type": "string" },
+      { "internalType": "bytes", "name": "value", "type": "bytes" }
+    ],
+    "name": "setProperty",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "string", "name": "key", "type": "string" },
+      { "internalType": "bool", "name": "isMutable", "type": "bool" },
+      { "internalType": "bool", "name": "collectionAdmin", "type": "bool" },
+      { "internalType": "bool", "name": "tokenOwner", "type": "bool" }
+    ],
+    "name": "setTokenPropertyPermission",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }
+    ],
+    "name": "supportsInterface",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "symbol",
+    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "uint256", "name": "index", "type": "uint256" }
+    ],
+    "name": "tokenByIndex",
+    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "owner", "type": "address" },
+      { "internalType": "uint256", "name": "index", "type": "uint256" }
+    ],
+    "name": "tokenOfOwnerByIndex",
+    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "tokenURI",
+    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "totalSupply",
+    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "to", "type": "address" },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "transfer",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "from", "type": "address" },
+      { "internalType": "address", "name": "to", "type": "address" },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "transferFrom",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  }
+]
modifiedtests/src/eth/proxy/nonFungibleProxy.test.tsdiffbeforeafterboth
--- a/tests/src/eth/proxy/nonFungibleProxy.test.ts
+++ b/tests/src/eth/proxy/nonFungibleProxy.test.ts
@@ -88,7 +88,8 @@
 });
 
 describe('NFT (Via EVM proxy): Plain calls', () => {
-  itWeb3('Can perform mint()', async ({web3, api}) => {
+  //TODO: CORE-302 add eth methods
+  itWeb3.skip('Can perform mint()', async ({web3, api}) => {
     const collection = await createCollectionExpectSuccess({
       mode: {type: 'NFT'},
     });
@@ -127,7 +128,9 @@
       expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
     }
   });
-  itWeb3('Can perform mintBulk()', async ({web3, api}) => {
+  
+  //TODO: CORE-302 add eth methods
+  itWeb3.skip('Can perform mintBulk()', async ({web3, api}) => {
     const collection = await createCollectionExpectSuccess({
       mode: {type: 'NFT'},
     });
modifiedtests/src/eth/util/contractHelpersAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/util/contractHelpersAbi.json
+++ b/tests/src/eth/util/contractHelpersAbi.json
@@ -1,216 +1,161 @@
 [
-    {
-        "inputs": [
-            {
-                "internalType": "address",
-                "name": "contract",
-                "type": "address"
-            }
-        ],
-        "name": "allowlistEnabled",
-        "outputs": [
-            {
-                "internalType": "bool",
-                "name": "",
-                "type": "bool"
-            }
-        ],
-        "stateMutability": "view",
-        "type": "function"
-    },
-    {
-        "inputs": [
-            {
-                "internalType": "address",
-                "name": "target",
-                "type": "address"
-            },
-            {
-                "internalType": "address",
-                "name": "caller",
-                "type": "address"
-            }
-        ],
-        "name": "allowed",
-        "outputs": [
-            {
-                "internalType": "bool",
-                "name": "",
-                "type": "bool"
-            }
-        ],
-        "stateMutability": "view",
-        "type": "function"
-    },
-    {
-        "inputs": [
-            {
-                "internalType": "address",
-                "name": "target",
-                "type": "address"
-            }
-        ],
-        "name": "contractOwner",
-        "outputs": [
-            {
-                "internalType": "address",
-                "name": "",
-                "type": "address"
-            }
-        ],
-        "stateMutability": "view",
-        "type": "function"
-    },
-    {
-        "inputs": [
-            {
-                "internalType": "address",
-                "name": "target",
-                "type": "address"
-            }
-        ],
-        "name": "sponsoringEnabled",
-        "outputs": [
-            {
-                "internalType": "bool",
-                "name": "",
-                "type": "bool"
-            }
-        ],
-        "stateMutability": "view",
-        "type": "function"
-    },
-    {
-        "inputs": [
-            {
-                "internalType": "address",
-                "name": "target",
-                "type": "address"
-            },
-            {
-                "internalType": "address",
-                "name": "user",
-                "type": "address"
-            },
-            {
-                "internalType": "bool",
-                "name": "isAllowed",
-                "type": "bool"
-            }
-        ],
-        "name": "toggleAllowed",
-        "outputs": [],
-        "stateMutability": "nonpayable",
-        "type": "function"
-    },
-    {
-        "inputs": [
-            {
-                "internalType": "address",
-                "name": "target",
-                "type": "address"
-            },
-            {
-                "internalType": "bool",
-                "name": "enabled",
-                "type": "bool"
-            }
-        ],
-        "name": "toggleAllowlist",
-        "outputs": [],
-        "stateMutability": "nonpayable",
-        "type": "function"
-    },
-    {
-        "inputs": [
-            {
-                "internalType": "address",
-                "name": "target",
-                "type": "address"
-            },
-            {
-                "internalType": "bool",
-                "name": "enabled",
-                "type": "bool"
-            }
-        ],
-        "name": "toggleSponsoring",
-        "outputs": [],
-        "stateMutability": "nonpayable",
-        "type": "function"
-    },
-    {
-        "inputs": [
-            {
-                "internalType": "address",
-                "name": "target",
-                "type": "address"
-            },
-            {
-                "internalType": "uint8",
-                "name": "mode",
-                "type": "uint8"
-            }
-        ],
-        "name": "setSponsoringMode",
-        "outputs": [],
-        "stateMutability": "nonpayable",
-        "type": "function"
-    },
-    {
-        "inputs": [
-            {
-                "internalType": "address",
-                "name": "target",
-                "type": "address"
-            }
-        ],
-        "name": "sponsoringMode",
-        "outputs": [
-            {
-                "internalType": "uint8",
-                "name": "",
-                "type": "uint8"
-            }
-        ],
-        "stateMutability": "nonpayable",
-        "type": "function"
-    },
-    {
-        "inputs": [
-            {
-                "internalType": "address",
-                "name": "target",
-                "type": "address"
-            },
-            {
-                "internalType": "uint32",
-                "name": "limit",
-                "type": "uint32"
-            }
-        ],
-        "name": "setSponsoringRateLimit",
-        "outputs": [],
-        "stateMutability": "nonpayable",
-        "type": "function"
-    },
-    {
-        "inputs": [
-            {
-                "internalType": "address",
-                "name": "contractAddress",
-                "type": "address"
-            }
-        ],
-        "name": "getSponsoringRateLimit",
-        "outputs": [
-            {
-                "internalType": "uint32",
-                "name": "",
-                "type": "uint32"
-            }
-        ],
-        "stateMutability": "view",
-        "type": "function"
-    }
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      },
+      { "internalType": "address", "name": "user", "type": "address" }
+    ],
+    "name": "allowed",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      }
+    ],
+    "name": "allowlistEnabled",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      }
+    ],
+    "name": "contractOwner",
+    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      }
+    ],
+    "name": "getSponsoringRateLimit",
+    "outputs": [{ "internalType": "uint32", "name": "", "type": "uint32" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      },
+      { "internalType": "uint8", "name": "mode", "type": "uint8" }
+    ],
+    "name": "setSponsoringMode",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      },
+      { "internalType": "uint32", "name": "rateLimit", "type": "uint32" }
+    ],
+    "name": "setSponsoringRateLimit",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      }
+    ],
+    "name": "sponsoringEnabled",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      }
+    ],
+    "name": "sponsoringMode",
+    "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }
+    ],
+    "name": "supportsInterface",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      },
+      { "internalType": "address", "name": "user", "type": "address" },
+      { "internalType": "bool", "name": "allowed", "type": "bool" }
+    ],
+    "name": "toggleAllowed",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      },
+      { "internalType": "bool", "name": "enabled", "type": "bool" }
+    ],
+    "name": "toggleAllowlist",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      },
+      { "internalType": "bool", "name": "enabled", "type": "bool" }
+    ],
+    "name": "toggleSponsoring",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  }
 ]
modifiedtests/src/eth/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/eth/util/helpers.ts
+++ b/tests/src/eth/util/helpers.ts
@@ -23,11 +23,13 @@
 import usingApi, {submitTransactionAsync} from '../../substrate/substrate-api';
 import {IKeyringPair} from '@polkadot/types/types';
 import {expect} from 'chai';
-import {CrossAccountId, getGenericResult, UNIQUE} from '../../util/helpers';
+import {CrossAccountId, getDetailedCollectionInfo, getGenericResult, UNIQUE} from '../../util/helpers';
 import * as solc from 'solc';
 import config from '../../config';
 import privateKey from '../../substrate/privateKey';
 import contractHelpersAbi from './contractHelpersAbi.json';
+import nonFungibleAbi from '../nonFungibleAbi.json';
+import collectionHelperAbi from '../collectionHelperAbi.json';
 import getBalance from '../../substrate/get-balance';
 import waitNewBlocks from '../../substrate/wait-new-blocks';
 
@@ -66,12 +68,30 @@
   ];
 }
 
+export async function getCollectionAddressFromResult(api: ApiPromise, result: any) {
+  const collectionIdAddress = normalizeAddress(result.events[0].raw.topics[2]);
+  const collectionId = collectionIdFromAddress(collectionIdAddress);  
+  const collection = (await getDetailedCollectionInfo(api, collectionId))!;
+  return {collectionIdAddress, collectionId, collection};
+}
+
 export function collectionIdToAddress(collection: number): string {
   const buf = Buffer.from([0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e,
     ...encodeIntBE(collection),
   ]);
   return Web3.utils.toChecksumAddress('0x' + buf.toString('hex'));
 }
+export function collectionIdFromAddress(address: string): number {
+  if (!address.startsWith('0x'))
+    throw 'address not starts with "0x"';
+  if (address.length > 42)
+    throw 'address length is more than 20 bytes';
+  return Number('0x' + address.substring(address.length - 8));
+}
+  
+export function normalizeAddress(address: string): string {
+  return '0x' + address.substring(address.length - 40);
+}
 
 export function tokenIdToAddress(collection: number, token: number): string {
   const buf = Buffer.from([0xf8, 0x23, 0x8c, 0xcf, 0xff, 0x8e, 0xd8, 0x87, 0x46, 0x3f, 0xd5, 0xe0,
@@ -271,6 +291,26 @@
   return new web3.eth.Contract(contractHelpersAbi as any, '0x842899ECF380553E8a4de75bF534cdf6fBF64049', {from: caller, ...GAS_ARGS});
 }
 
+/** 
+ * evm collection helper
+ * @param web3 
+ * @param caller - eth address
+ * @returns 
+ */
+export function evmCollectionHelper(web3: Web3, caller: string) {
+  return new web3.eth.Contract(collectionHelperAbi as any, '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f', {from: caller, ...GAS_ARGS});
+}
+
+/** 
+ * evm collection
+ * @param web3 
+ * @param caller - eth address
+ * @returns 
+ */
+export function evmCollection(web3: Web3, caller: string, collection: string) {
+  return new web3.eth.Contract(nonFungibleAbi as any, collection, {from: caller, ...GAS_ARGS});
+}
+
 /**
  * Execute ethereum method call using substrate account
  * @param to target contract
modifiedtests/src/interfaces/augment-types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-types.ts
+++ b/tests/src/interfaces/augment-types.ts
@@ -1,7 +1,6 @@
 // Auto-generated via `yarn polkadot-types-from-defs`, do not edit
 /* eslint-disable */
 
-import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportPalletId, FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchClass, FrameSupportWeightsDispatchInfo, FrameSupportWeightsPays, FrameSupportWeightsPerDispatchClassU32, FrameSupportWeightsPerDispatchClassU64, FrameSupportWeightsPerDispatchClassWeightsPerClass, FrameSupportWeightsRuntimeDbWeight, FrameSupportWeightsWeightToFeeCoefficient, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OpalRuntimeRuntime, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReleases, PalletBalancesReserveData, PalletCommonError, PalletCommonEvent, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEthereumFakeTransactionFinalizer, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletFungibleError, PalletInflationCall, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRefungibleItemData, PalletRmrkCoreCall, PalletRmrkCoreError, PalletRmrkCoreEvent, PalletRmrkEquipCall, PalletRmrkEquipError, PalletRmrkEquipEvent, PalletStructureCall, PalletStructureError, PalletStructureEvent, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTimestampCall, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletUniqueRawEvent, PalletXcmCall, PalletXcmError, PalletXcmEvent, PhantomTypeUpDataStructsBaseInfo, PhantomTypeUpDataStructsCollectionInfo, PhantomTypeUpDataStructsNftChild, PhantomTypeUpDataStructsNftInfo, PhantomTypeUpDataStructsPartType, PhantomTypeUpDataStructsPropertyInfo, PhantomTypeUpDataStructsResourceInfo, PhantomTypeUpDataStructsRpcCollection, PhantomTypeUpDataStructsTheme, PhantomTypeUpDataStructsTokenData, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2AbridgedHrmpChannel, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpRuntimeTransactionalError, SpTrieStorageProof, SpVersionRuntimeVersion, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionField, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsCreateNftData, UpDataStructsCreateNftExData, UpDataStructsCreateReFungibleData, UpDataStructsCreateRefungibleExData, UpDataStructsNestingRule, UpDataStructsProperties, UpDataStructsPropertiesMapBoundedVec, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsProperty, UpDataStructsPropertyKeyPermission, UpDataStructsPropertyPermission, UpDataStructsRmrkAccountIdOrCollectionNftTuple, UpDataStructsRmrkBaseInfo, UpDataStructsRmrkBasicResource, UpDataStructsRmrkCollectionInfo, UpDataStructsRmrkComposableResource, UpDataStructsRmrkEquippableList, UpDataStructsRmrkFixedPart, UpDataStructsRmrkNftChild, UpDataStructsRmrkNftInfo, UpDataStructsRmrkPartType, UpDataStructsRmrkPropertyInfo, UpDataStructsRmrkResourceInfo, UpDataStructsRmrkResourceTypes, UpDataStructsRmrkRoyaltyInfo, UpDataStructsRmrkSlotPart, UpDataStructsRmrkSlotResource, UpDataStructsRmrkTheme, UpDataStructsRmrkThemeProperty, UpDataStructsRpcCollection, UpDataStructsSchemaVersion, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipState, UpDataStructsTokenData, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from './unique';
 import type { Data, StorageKey } from '@polkadot/types';
 import type { BitVec, Bool, Bytes, I128, I16, I256, I32, I64, I8, Json, Null, Raw, Text, Type, U128, U16, U256, U32, U64, U8, USize, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types-codec';
 import type { AssetApproval, AssetApprovalKey, AssetBalance, AssetDestroyWitness, AssetDetails, AssetMetadata, TAssetBalance, TAssetDepositBalance } from '@polkadot/types/interfaces/assets';
@@ -291,27 +290,6 @@
     CoreOccupied: CoreOccupied;
     CrateVersion: CrateVersion;
     CreatedBlock: CreatedBlock;
-    CumulusPalletDmpQueueCall: CumulusPalletDmpQueueCall;
-    CumulusPalletDmpQueueConfigData: CumulusPalletDmpQueueConfigData;
-    CumulusPalletDmpQueueError: CumulusPalletDmpQueueError;
-    CumulusPalletDmpQueueEvent: CumulusPalletDmpQueueEvent;
-    CumulusPalletDmpQueuePageIndexData: CumulusPalletDmpQueuePageIndexData;
-    CumulusPalletParachainSystemCall: CumulusPalletParachainSystemCall;
-    CumulusPalletParachainSystemError: CumulusPalletParachainSystemError;
-    CumulusPalletParachainSystemEvent: CumulusPalletParachainSystemEvent;
-    CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot: CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot;
-    CumulusPalletXcmCall: CumulusPalletXcmCall;
-    CumulusPalletXcmError: CumulusPalletXcmError;
-    CumulusPalletXcmEvent: CumulusPalletXcmEvent;
-    CumulusPalletXcmpQueueCall: CumulusPalletXcmpQueueCall;
-    CumulusPalletXcmpQueueError: CumulusPalletXcmpQueueError;
-    CumulusPalletXcmpQueueEvent: CumulusPalletXcmpQueueEvent;
-    CumulusPalletXcmpQueueInboundChannelDetails: CumulusPalletXcmpQueueInboundChannelDetails;
-    CumulusPalletXcmpQueueInboundState: CumulusPalletXcmpQueueInboundState;
-    CumulusPalletXcmpQueueOutboundChannelDetails: CumulusPalletXcmpQueueOutboundChannelDetails;
-    CumulusPalletXcmpQueueOutboundState: CumulusPalletXcmpQueueOutboundState;
-    CumulusPalletXcmpQueueQueueConfigData: CumulusPalletXcmpQueueQueueConfigData;
-    CumulusPrimitivesParachainInherentParachainInherentData: CumulusPrimitivesParachainInherentParachainInherentData;
     Data: Data;
     DeferredOffenceOf: DeferredOffenceOf;
     DefunctVoter: DefunctVoter;
@@ -378,25 +356,11 @@
     EthAddress: EthAddress;
     EthBlock: EthBlock;
     EthBloom: EthBloom;
-    EthbloomBloom: EthbloomBloom;
     EthCallRequest: EthCallRequest;
     EthereumAccountId: EthereumAccountId;
     EthereumAddress: EthereumAddress;
-    EthereumBlock: EthereumBlock;
-    EthereumHeader: EthereumHeader;
-    EthereumLog: EthereumLog;
     EthereumLookupSource: EthereumLookupSource;
-    EthereumReceiptEip658ReceiptData: EthereumReceiptEip658ReceiptData;
-    EthereumReceiptReceiptV3: EthereumReceiptReceiptV3;
     EthereumSignature: EthereumSignature;
-    EthereumTransactionAccessListItem: EthereumTransactionAccessListItem;
-    EthereumTransactionEip1559Transaction: EthereumTransactionEip1559Transaction;
-    EthereumTransactionEip2930Transaction: EthereumTransactionEip2930Transaction;
-    EthereumTransactionLegacyTransaction: EthereumTransactionLegacyTransaction;
-    EthereumTransactionTransactionAction: EthereumTransactionTransactionAction;
-    EthereumTransactionTransactionSignature: EthereumTransactionTransactionSignature;
-    EthereumTransactionTransactionV2: EthereumTransactionTransactionV2;
-    EthereumTypesHashH64: EthereumTypesHashH64;
     EthFilter: EthFilter;
     EthFilterAddress: EthFilterAddress;
     EthFilterChanges: EthFilterChanges;
@@ -433,11 +397,6 @@
     EventMetadataV9: EventMetadataV9;
     EventRecord: EventRecord;
     EvmAccount: EvmAccount;
-    EvmCoreErrorExitError: EvmCoreErrorExitError;
-    EvmCoreErrorExitFatal: EvmCoreErrorExitFatal;
-    EvmCoreErrorExitReason: EvmCoreErrorExitReason;
-    EvmCoreErrorExitRevert: EvmCoreErrorExitRevert;
-    EvmCoreErrorExitSucceed: EvmCoreErrorExitSucceed;
     EvmLog: EvmLog;
     EvmVicinity: EvmVicinity;
     ExecReturnValue: ExecReturnValue;
@@ -476,31 +435,6 @@
     Forcing: Forcing;
     ForkTreePendingChange: ForkTreePendingChange;
     ForkTreePendingChangeNode: ForkTreePendingChangeNode;
-    FpRpcTransactionStatus: FpRpcTransactionStatus;
-    FrameSupportPalletId: FrameSupportPalletId;
-    FrameSupportTokensMiscBalanceStatus: FrameSupportTokensMiscBalanceStatus;
-    FrameSupportWeightsDispatchClass: FrameSupportWeightsDispatchClass;
-    FrameSupportWeightsDispatchInfo: FrameSupportWeightsDispatchInfo;
-    FrameSupportWeightsPays: FrameSupportWeightsPays;
-    FrameSupportWeightsPerDispatchClassU32: FrameSupportWeightsPerDispatchClassU32;
-    FrameSupportWeightsPerDispatchClassU64: FrameSupportWeightsPerDispatchClassU64;
-    FrameSupportWeightsPerDispatchClassWeightsPerClass: FrameSupportWeightsPerDispatchClassWeightsPerClass;
-    FrameSupportWeightsRuntimeDbWeight: FrameSupportWeightsRuntimeDbWeight;
-    FrameSupportWeightsWeightToFeeCoefficient: FrameSupportWeightsWeightToFeeCoefficient;
-    FrameSystemAccountInfo: FrameSystemAccountInfo;
-    FrameSystemCall: FrameSystemCall;
-    FrameSystemError: FrameSystemError;
-    FrameSystemEvent: FrameSystemEvent;
-    FrameSystemEventRecord: FrameSystemEventRecord;
-    FrameSystemExtensionsCheckGenesis: FrameSystemExtensionsCheckGenesis;
-    FrameSystemExtensionsCheckNonce: FrameSystemExtensionsCheckNonce;
-    FrameSystemExtensionsCheckSpecVersion: FrameSystemExtensionsCheckSpecVersion;
-    FrameSystemExtensionsCheckWeight: FrameSystemExtensionsCheckWeight;
-    FrameSystemLastRuntimeUpgradeInfo: FrameSystemLastRuntimeUpgradeInfo;
-    FrameSystemLimitsBlockLength: FrameSystemLimitsBlockLength;
-    FrameSystemLimitsBlockWeights: FrameSystemLimitsBlockWeights;
-    FrameSystemLimitsWeightsPerClass: FrameSystemLimitsWeightsPerClass;
-    FrameSystemPhase: FrameSystemPhase;
     FullIdentification: FullIdentification;
     FunctionArgumentMetadataLatest: FunctionArgumentMetadataLatest;
     FunctionArgumentMetadataV10: FunctionArgumentMetadataV10;
@@ -716,7 +650,6 @@
     OffchainAccuracyCompact: OffchainAccuracyCompact;
     OffenceDetails: OffenceDetails;
     Offender: Offender;
-    OpalRuntimeRuntime: OpalRuntimeRuntime;
     OpaqueCall: OpaqueCall;
     OpaqueMultiaddr: OpaqueMultiaddr;
     OpaqueNetworkState: OpaqueNetworkState;
@@ -732,10 +665,6 @@
     OriginKindV0: OriginKindV0;
     OriginKindV1: OriginKindV1;
     OriginKindV2: OriginKindV2;
-    OrmlVestingModuleCall: OrmlVestingModuleCall;
-    OrmlVestingModuleError: OrmlVestingModuleError;
-    OrmlVestingModuleEvent: OrmlVestingModuleEvent;
-    OrmlVestingVestingSchedule: OrmlVestingVestingSchedule;
     OutboundHrmpMessage: OutboundHrmpMessage;
     OutboundLaneData: OutboundLaneData;
     OutboundMessageFee: OutboundMessageFee;
@@ -746,76 +675,21 @@
     Owner: Owner;
     PageCounter: PageCounter;
     PageIndexData: PageIndexData;
-    PalletBalancesAccountData: PalletBalancesAccountData;
-    PalletBalancesBalanceLock: PalletBalancesBalanceLock;
-    PalletBalancesCall: PalletBalancesCall;
-    PalletBalancesError: PalletBalancesError;
-    PalletBalancesEvent: PalletBalancesEvent;
-    PalletBalancesReasons: PalletBalancesReasons;
-    PalletBalancesReleases: PalletBalancesReleases;
-    PalletBalancesReserveData: PalletBalancesReserveData;
     PalletCallMetadataLatest: PalletCallMetadataLatest;
     PalletCallMetadataV14: PalletCallMetadataV14;
-    PalletCommonError: PalletCommonError;
-    PalletCommonEvent: PalletCommonEvent;
     PalletConstantMetadataLatest: PalletConstantMetadataLatest;
     PalletConstantMetadataV14: PalletConstantMetadataV14;
     PalletErrorMetadataLatest: PalletErrorMetadataLatest;
     PalletErrorMetadataV14: PalletErrorMetadataV14;
-    PalletEthereumCall: PalletEthereumCall;
-    PalletEthereumError: PalletEthereumError;
-    PalletEthereumEvent: PalletEthereumEvent;
-    PalletEthereumFakeTransactionFinalizer: PalletEthereumFakeTransactionFinalizer;
     PalletEventMetadataLatest: PalletEventMetadataLatest;
     PalletEventMetadataV14: PalletEventMetadataV14;
-    PalletEvmAccountBasicCrossAccountIdRepr: PalletEvmAccountBasicCrossAccountIdRepr;
-    PalletEvmCall: PalletEvmCall;
-    PalletEvmCoderSubstrateError: PalletEvmCoderSubstrateError;
-    PalletEvmContractHelpersError: PalletEvmContractHelpersError;
-    PalletEvmContractHelpersSponsoringModeT: PalletEvmContractHelpersSponsoringModeT;
-    PalletEvmError: PalletEvmError;
-    PalletEvmEvent: PalletEvmEvent;
-    PalletEvmMigrationCall: PalletEvmMigrationCall;
-    PalletEvmMigrationError: PalletEvmMigrationError;
-    PalletFungibleError: PalletFungibleError;
     PalletId: PalletId;
-    PalletInflationCall: PalletInflationCall;
     PalletMetadataLatest: PalletMetadataLatest;
     PalletMetadataV14: PalletMetadataV14;
-    PalletNonfungibleError: PalletNonfungibleError;
-    PalletNonfungibleItemData: PalletNonfungibleItemData;
-    PalletRefungibleError: PalletRefungibleError;
-    PalletRefungibleItemData: PalletRefungibleItemData;
-    PalletRmrkCoreCall: PalletRmrkCoreCall;
-    PalletRmrkCoreError: PalletRmrkCoreError;
-    PalletRmrkCoreEvent: PalletRmrkCoreEvent;
-    PalletRmrkEquipCall: PalletRmrkEquipCall;
-    PalletRmrkEquipError: PalletRmrkEquipError;
-    PalletRmrkEquipEvent: PalletRmrkEquipEvent;
     PalletsOrigin: PalletsOrigin;
     PalletStorageMetadataLatest: PalletStorageMetadataLatest;
     PalletStorageMetadataV14: PalletStorageMetadataV14;
-    PalletStructureCall: PalletStructureCall;
-    PalletStructureError: PalletStructureError;
-    PalletStructureEvent: PalletStructureEvent;
-    PalletSudoCall: PalletSudoCall;
-    PalletSudoError: PalletSudoError;
-    PalletSudoEvent: PalletSudoEvent;
-    PalletTemplateTransactionPaymentCall: PalletTemplateTransactionPaymentCall;
-    PalletTemplateTransactionPaymentChargeTransactionPayment: PalletTemplateTransactionPaymentChargeTransactionPayment;
-    PalletTimestampCall: PalletTimestampCall;
-    PalletTransactionPaymentReleases: PalletTransactionPaymentReleases;
-    PalletTreasuryCall: PalletTreasuryCall;
-    PalletTreasuryError: PalletTreasuryError;
-    PalletTreasuryEvent: PalletTreasuryEvent;
-    PalletTreasuryProposal: PalletTreasuryProposal;
-    PalletUniqueCall: PalletUniqueCall;
-    PalletUniqueError: PalletUniqueError;
-    PalletUniqueRawEvent: PalletUniqueRawEvent;
     PalletVersion: PalletVersion;
-    PalletXcmCall: PalletXcmCall;
-    PalletXcmError: PalletXcmError;
-    PalletXcmEvent: PalletXcmEvent;
     ParachainDispatchOrigin: ParachainDispatchOrigin;
     ParachainInherentData: ParachainInherentData;
     ParachainProposal: ParachainProposal;
@@ -855,27 +729,9 @@
     PerU16: PerU16;
     Phantom: Phantom;
     PhantomData: PhantomData;
-    PhantomTypeUpDataStructsBaseInfo: PhantomTypeUpDataStructsBaseInfo;
-    PhantomTypeUpDataStructsCollectionInfo: PhantomTypeUpDataStructsCollectionInfo;
-    PhantomTypeUpDataStructsNftChild: PhantomTypeUpDataStructsNftChild;
-    PhantomTypeUpDataStructsNftInfo: PhantomTypeUpDataStructsNftInfo;
-    PhantomTypeUpDataStructsPartType: PhantomTypeUpDataStructsPartType;
-    PhantomTypeUpDataStructsPropertyInfo: PhantomTypeUpDataStructsPropertyInfo;
-    PhantomTypeUpDataStructsResourceInfo: PhantomTypeUpDataStructsResourceInfo;
-    PhantomTypeUpDataStructsRpcCollection: PhantomTypeUpDataStructsRpcCollection;
-    PhantomTypeUpDataStructsTheme: PhantomTypeUpDataStructsTheme;
-    PhantomTypeUpDataStructsTokenData: PhantomTypeUpDataStructsTokenData;
     Phase: Phase;
     PhragmenScore: PhragmenScore;
     Points: Points;
-    PolkadotCorePrimitivesInboundDownwardMessage: PolkadotCorePrimitivesInboundDownwardMessage;
-    PolkadotCorePrimitivesInboundHrmpMessage: PolkadotCorePrimitivesInboundHrmpMessage;
-    PolkadotCorePrimitivesOutboundHrmpMessage: PolkadotCorePrimitivesOutboundHrmpMessage;
-    PolkadotParachainPrimitivesXcmpMessageFormat: PolkadotParachainPrimitivesXcmpMessageFormat;
-    PolkadotPrimitivesV2AbridgedHostConfiguration: PolkadotPrimitivesV2AbridgedHostConfiguration;
-    PolkadotPrimitivesV2AbridgedHrmpChannel: PolkadotPrimitivesV2AbridgedHrmpChannel;
-    PolkadotPrimitivesV2PersistedValidationData: PolkadotPrimitivesV2PersistedValidationData;
-    PolkadotPrimitivesV2UpgradeRestriction: PolkadotPrimitivesV2UpgradeRestriction;
     PortableType: PortableType;
     PortableTypeV14: PortableTypeV14;
     Precommits: Precommits;
@@ -1071,20 +927,7 @@
     SolutionSupports: SolutionSupports;
     SpanIndex: SpanIndex;
     SpanRecord: SpanRecord;
-    SpCoreEcdsaSignature: SpCoreEcdsaSignature;
-    SpCoreEd25519Signature: SpCoreEd25519Signature;
-    SpCoreSr25519Signature: SpCoreSr25519Signature;
     SpecVersion: SpecVersion;
-    SpRuntimeArithmeticError: SpRuntimeArithmeticError;
-    SpRuntimeDigest: SpRuntimeDigest;
-    SpRuntimeDigestDigestItem: SpRuntimeDigestDigestItem;
-    SpRuntimeDispatchError: SpRuntimeDispatchError;
-    SpRuntimeModuleError: SpRuntimeModuleError;
-    SpRuntimeMultiSignature: SpRuntimeMultiSignature;
-    SpRuntimeTokenError: SpRuntimeTokenError;
-    SpRuntimeTransactionalError: SpRuntimeTransactionalError;
-    SpTrieStorageProof: SpTrieStorageProof;
-    SpVersionRuntimeVersion: SpVersionRuntimeVersion;
     Sr25519Signature: Sr25519Signature;
     StakingLedger: StakingLedger;
     StakingLedgerTo223: StakingLedgerTo223;
@@ -1181,50 +1024,6 @@
     UnlockChunk: UnlockChunk;
     UnrewardedRelayer: UnrewardedRelayer;
     UnrewardedRelayersState: UnrewardedRelayersState;
-    UpDataStructsAccessMode: UpDataStructsAccessMode;
-    UpDataStructsCollection: UpDataStructsCollection;
-    UpDataStructsCollectionField: UpDataStructsCollectionField;
-    UpDataStructsCollectionLimits: UpDataStructsCollectionLimits;
-    UpDataStructsCollectionMode: UpDataStructsCollectionMode;
-    UpDataStructsCollectionStats: UpDataStructsCollectionStats;
-    UpDataStructsCreateCollectionData: UpDataStructsCreateCollectionData;
-    UpDataStructsCreateFungibleData: UpDataStructsCreateFungibleData;
-    UpDataStructsCreateItemData: UpDataStructsCreateItemData;
-    UpDataStructsCreateItemExData: UpDataStructsCreateItemExData;
-    UpDataStructsCreateNftData: UpDataStructsCreateNftData;
-    UpDataStructsCreateNftExData: UpDataStructsCreateNftExData;
-    UpDataStructsCreateReFungibleData: UpDataStructsCreateReFungibleData;
-    UpDataStructsCreateRefungibleExData: UpDataStructsCreateRefungibleExData;
-    UpDataStructsNestingRule: UpDataStructsNestingRule;
-    UpDataStructsProperties: UpDataStructsProperties;
-    UpDataStructsPropertiesMapBoundedVec: UpDataStructsPropertiesMapBoundedVec;
-    UpDataStructsPropertiesMapPropertyPermission: UpDataStructsPropertiesMapPropertyPermission;
-    UpDataStructsProperty: UpDataStructsProperty;
-    UpDataStructsPropertyKeyPermission: UpDataStructsPropertyKeyPermission;
-    UpDataStructsPropertyPermission: UpDataStructsPropertyPermission;
-    UpDataStructsRmrkAccountIdOrCollectionNftTuple: UpDataStructsRmrkAccountIdOrCollectionNftTuple;
-    UpDataStructsRmrkBaseInfo: UpDataStructsRmrkBaseInfo;
-    UpDataStructsRmrkBasicResource: UpDataStructsRmrkBasicResource;
-    UpDataStructsRmrkCollectionInfo: UpDataStructsRmrkCollectionInfo;
-    UpDataStructsRmrkComposableResource: UpDataStructsRmrkComposableResource;
-    UpDataStructsRmrkEquippableList: UpDataStructsRmrkEquippableList;
-    UpDataStructsRmrkFixedPart: UpDataStructsRmrkFixedPart;
-    UpDataStructsRmrkNftChild: UpDataStructsRmrkNftChild;
-    UpDataStructsRmrkNftInfo: UpDataStructsRmrkNftInfo;
-    UpDataStructsRmrkPartType: UpDataStructsRmrkPartType;
-    UpDataStructsRmrkPropertyInfo: UpDataStructsRmrkPropertyInfo;
-    UpDataStructsRmrkResourceInfo: UpDataStructsRmrkResourceInfo;
-    UpDataStructsRmrkResourceTypes: UpDataStructsRmrkResourceTypes;
-    UpDataStructsRmrkRoyaltyInfo: UpDataStructsRmrkRoyaltyInfo;
-    UpDataStructsRmrkSlotPart: UpDataStructsRmrkSlotPart;
-    UpDataStructsRmrkSlotResource: UpDataStructsRmrkSlotResource;
-    UpDataStructsRmrkTheme: UpDataStructsRmrkTheme;
-    UpDataStructsRmrkThemeProperty: UpDataStructsRmrkThemeProperty;
-    UpDataStructsRpcCollection: UpDataStructsRpcCollection;
-    UpDataStructsSchemaVersion: UpDataStructsSchemaVersion;
-    UpDataStructsSponsoringRateLimit: UpDataStructsSponsoringRateLimit;
-    UpDataStructsSponsorshipState: UpDataStructsSponsorshipState;
-    UpDataStructsTokenData: UpDataStructsTokenData;
     UpgradeGoAhead: UpgradeGoAhead;
     UpgradeRestriction: UpgradeRestriction;
     UpwardMessage: UpwardMessage;
@@ -1296,7 +1095,6 @@
     WithdrawReasons: WithdrawReasons;
     Xcm: Xcm;
     XcmAssetId: XcmAssetId;
-    XcmDoubleEncoded: XcmDoubleEncoded;
     XcmError: XcmError;
     XcmErrorV0: XcmErrorV0;
     XcmErrorV1: XcmErrorV1;
@@ -1309,41 +1107,8 @@
     XcmOriginKind: XcmOriginKind;
     XcmpMessageFormat: XcmpMessageFormat;
     XcmV0: XcmV0;
-    XcmV0Junction: XcmV0Junction;
-    XcmV0JunctionBodyId: XcmV0JunctionBodyId;
-    XcmV0JunctionBodyPart: XcmV0JunctionBodyPart;
-    XcmV0JunctionNetworkId: XcmV0JunctionNetworkId;
-    XcmV0MultiAsset: XcmV0MultiAsset;
-    XcmV0MultiLocation: XcmV0MultiLocation;
-    XcmV0Order: XcmV0Order;
-    XcmV0OriginKind: XcmV0OriginKind;
-    XcmV0Response: XcmV0Response;
-    XcmV0Xcm: XcmV0Xcm;
     XcmV1: XcmV1;
-    XcmV1Junction: XcmV1Junction;
-    XcmV1MultiAsset: XcmV1MultiAsset;
-    XcmV1MultiassetAssetId: XcmV1MultiassetAssetId;
-    XcmV1MultiassetAssetInstance: XcmV1MultiassetAssetInstance;
-    XcmV1MultiassetFungibility: XcmV1MultiassetFungibility;
-    XcmV1MultiassetMultiAssetFilter: XcmV1MultiassetMultiAssetFilter;
-    XcmV1MultiassetMultiAssets: XcmV1MultiassetMultiAssets;
-    XcmV1MultiassetWildFungibility: XcmV1MultiassetWildFungibility;
-    XcmV1MultiassetWildMultiAsset: XcmV1MultiassetWildMultiAsset;
-    XcmV1MultiLocation: XcmV1MultiLocation;
-    XcmV1MultilocationJunctions: XcmV1MultilocationJunctions;
-    XcmV1Order: XcmV1Order;
-    XcmV1Response: XcmV1Response;
-    XcmV1Xcm: XcmV1Xcm;
     XcmV2: XcmV2;
-    XcmV2Instruction: XcmV2Instruction;
-    XcmV2Response: XcmV2Response;
-    XcmV2TraitsError: XcmV2TraitsError;
-    XcmV2TraitsOutcome: XcmV2TraitsOutcome;
-    XcmV2WeightLimit: XcmV2WeightLimit;
-    XcmV2Xcm: XcmV2Xcm;
     XcmVersion: XcmVersion;
-    XcmVersionedMultiAssets: XcmVersionedMultiAssets;
-    XcmVersionedMultiLocation: XcmVersionedMultiLocation;
-    XcmVersionedXcm: XcmVersionedXcm;
   } // InterfaceTypes
 } // declare module
modifiedtests/src/interfaces/types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/types.ts
+++ b/tests/src/interfaces/types.ts
@@ -1,5 +1,3 @@
 // Auto-generated via `yarn polkadot-types-from-defs`, do not edit
 /* eslint-disable */
 
-export * from './unique/types';
-export * from './rmrk/types';
modifiedtests/yarn.lockdiffbeforeafterboth
--- a/tests/yarn.lock
+++ b/tests/yarn.lock
@@ -8615,7 +8615,7 @@
   resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
   integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
 
-solc@^0.8.13:
+solc@0.8.13:
   version "0.8.13"
   resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.13.tgz#bafc7fcc11a627e2281e489076b80497123bb704"
   integrity sha512-C0yTN+rjEOGO6uVOXI8+EKa75SFMuZpQ2tryex4QxWIg0HRWZvCHKfVPuLZ5wx06Sb6GBp6uQA5yqQyXZnXOJw==