git.delta.rocks / unique-network / refs/commits / 833ab943fbb5

difftreelog

feat merge native erc20 adapter

Yaroslav Bolyukin2023-05-30parents: #103f351 #c43c309.patch.diff
in: master

78 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5871,6 +5871,7 @@
  "pallet-aura",
  "pallet-authorship",
  "pallet-balances",
+ "pallet-balances-adapter",
  "pallet-base-fee",
  "pallet-collator-selection",
  "pallet-common",
@@ -6252,6 +6253,27 @@
 ]
 
 [[package]]
+name = "pallet-balances-adapter"
+version = "0.1.0"
+dependencies = [
+ "evm-coder",
+ "frame-support",
+ "frame-system",
+ "pallet-balances",
+ "pallet-common",
+ "pallet-evm",
+ "pallet-evm-coder-substrate",
+ "pallet-evm-transaction-payment",
+ "pallet-structure",
+ "parity-scale-codec",
+ "scale-info",
+ "sp-core",
+ "sp-runtime",
+ "sp-std",
+ "up-data-structs",
+]
+
+[[package]]
 name = "pallet-base-fee"
 version = "1.0.0"
 source = "git+https://github.com/uniquenetwork/unique-frontier?branch=unique-polkadot-v0.9.42#75ad5b9a49e1942553c4068228f1546ffb1f7246"
@@ -6947,6 +6969,7 @@
  "frame-benchmarking",
  "frame-support",
  "frame-system",
+ "pallet-balances",
  "pallet-common",
  "pallet-evm",
  "pallet-evm-coder-substrate",
@@ -7411,6 +7434,7 @@
  "frame-benchmarking",
  "frame-support",
  "frame-system",
+ "pallet-balances-adapter",
  "pallet-common",
  "pallet-evm",
  "pallet-evm-coder-substrate",
@@ -9350,6 +9374,7 @@
  "pallet-aura",
  "pallet-authorship",
  "pallet-balances",
+ "pallet-balances-adapter",
  "pallet-base-fee",
  "pallet-collator-selection",
  "pallet-common",
@@ -12902,8 +12927,8 @@
  "frame-support",
  "frame-system",
  "pallet-balances",
+ "pallet-balances-adapter",
  "pallet-common",
- "pallet-configuration",
  "pallet-ethereum",
  "pallet-evm",
  "pallet-evm-coder-substrate",
@@ -12914,7 +12939,6 @@
  "pallet-timestamp",
  "pallet-transaction-payment",
  "pallet-unique",
- "pallet-xcm",
  "parity-scale-codec",
  "scale-info",
  "sp-core",
@@ -13767,6 +13791,7 @@
  "pallet-aura",
  "pallet-authorship",
  "pallet-balances",
+ "pallet-balances-adapter",
  "pallet-base-fee",
  "pallet-collator-selection",
  "pallet-common",
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,9 +19,9 @@
 panic = "unwind"
 
 [profile.production]
+codegen-units = 1
 inherits = "release"
 lto = true
-codegen-units = 1
 opt-level = 3
 
 [workspace.dependencies]
@@ -29,6 +29,7 @@
 app-promotion-rpc = { path = "primitives/app_promotion_rpc", default-features = false }
 evm-coder = { git = "https://github.com/uniquenetwork/evm-coder", tag = "v0.3.1", default-features = false }
 pallet-app-promotion = { path = "pallets/app-promotion", default-features = false }
+pallet-balances-adapter = { default-features = false, path = "pallets/balances-adapter" }
 pallet-charge-transaction = { package = "pallet-template-transaction-payment", default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = "polkadot-v0.9.42" }
 pallet-collator-selection = { default-features = false, path = "pallets/collator-selection" }
 pallet-common = { default-features = false, path = "pallets/common" }
@@ -85,15 +86,15 @@
 cumulus-client-consensus-common = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
 cumulus-client-network = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
 cumulus-client-service = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
-cumulus-pallet-aura-ext = { default-features = false , git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
-cumulus-pallet-dmp-queue = { default-features = false , git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
-cumulus-pallet-parachain-system = { default-features = false , git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
-cumulus-pallet-xcm = { default-features = false , git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
-cumulus-pallet-xcmp-queue = { default-features = false , git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
-cumulus-primitives-core = { default-features = false , git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
+cumulus-pallet-aura-ext = { default-features = false, git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
+cumulus-pallet-dmp-queue = { default-features = false, git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
+cumulus-pallet-parachain-system = { default-features = false, git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
+cumulus-pallet-xcm = { default-features = false, git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
+cumulus-pallet-xcmp-queue = { default-features = false, git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
+cumulus-primitives-core = { default-features = false, git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
 cumulus-primitives-parachain-inherent = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
-cumulus-primitives-timestamp = { default-features = false , git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
-cumulus-primitives-utility = { default-features = false , git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
+cumulus-primitives-timestamp = { default-features = false, git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
+cumulus-primitives-utility = { default-features = false, git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
 cumulus-relay-chain-inprocess-interface = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
 cumulus-relay-chain-interface = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
 cumulus-relay-chain-minimal-node = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
@@ -123,9 +124,9 @@
 sc-cli = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
 sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
 sc-consensus = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
+sc-consensus-grandpa = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
 sc-consensus-manual-seal = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
 sc-executor = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
-sc-consensus-grandpa = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
 sc-network = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
 sc-network-sync = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
 sc-rpc = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
@@ -177,11 +178,11 @@
 try-runtime-cli = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
 
 # ORML
-orml-tokens = { default-features = false , git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
-orml-traits = { default-features = false , git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
-orml-vesting = { default-features = false , git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
-orml-xtokens = { default-features = false , git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
-orml-xcm-support = { default-features = false , git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
+orml-tokens = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
+orml-traits = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
+orml-vesting = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
+orml-xcm-support = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
+orml-xtokens = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
 
 # Other
 derivative = { version = "2.2.0", features = ["use_core"] }
@@ -194,4 +195,3 @@
 num_enum = { version = "0.5.3", default-features = false }
 serde = { default-features = false, features = ['derive'], version = "1.0.136" }
 smallvec = "1.6.1"
-
modifiedMakefilediffbeforeafterboth
--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,9 @@
 	@echo "  bench-evm-migration"
 	@echo "  bench-unique"
 
+NATIVE_FUNGIBLE_EVM_STUBS=./pallets/balances-adapter/src/stubs
+NATIVE_FUNGIBLE_EVM_ABI=./tests/src/eth/abi/nativeFungible.json
+
 FUNGIBLE_EVM_STUBS=./pallets/fungible/src/stubs
 FUNGIBLE_EVM_ABI=./tests/src/eth/abi/fungible.json
 
@@ -27,6 +30,10 @@
 .PHONY: regenerate_solidity
 regenerate_solidity: UniqueFungible.sol UniqueNFT.sol UniqueRefungible.sol UniqueRefungibleToken.sol ContractHelpers.sol CollectionHelpers.sol
 
+UniqueNativeFungible.sol:
+	PACKAGE=pallet-balances-adapter NAME=erc::gen_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh
+	PACKAGE=pallet-balances-adapter NAME=erc::gen_impl OUTPUT=$(NATIVE_FUNGIBLE_EVM_STUBS)/$@ ./.maintain/scripts/generate_sol.sh
+	
 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
@@ -51,6 +58,10 @@
 	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
 
+UniqueNativeFungible: UniqueNativeFungible.sol
+	INPUT=$(NATIVE_FUNGIBLE_EVM_STUBS)/$< OUTPUT=$(NATIVE_FUNGIBLE_EVM_STUBS)/UniqueNativeFungible.raw ./.maintain/scripts/compile_stub.sh
+	INPUT=$(NATIVE_FUNGIBLE_EVM_STUBS)/$< OUTPUT=$(NATIVE_FUNGIBLE_EVM_ABI) ./.maintain/scripts/generate_abi.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
modifiednode/cli/src/chain_spec.rsdiffbeforeafterboth
--- a/node/cli/src/chain_spec.rs
+++ b/node/cli/src/chain_spec.rs
@@ -267,7 +267,7 @@
 pub fn development_config() -> DefaultChainSpec {
 	let mut properties = Map::new();
 	properties.insert("tokenSymbol".into(), default_runtime::TOKEN_SYMBOL.into());
-	properties.insert("tokenDecimals".into(), 18.into());
+	properties.insert("tokenDecimals".into(), default_runtime::DECIMALS.into());
 	properties.insert(
 		"ss58Format".into(),
 		default_runtime::SS58Prefix::get().into(),
@@ -341,7 +341,7 @@
 pub fn local_testnet_config() -> DefaultChainSpec {
 	let mut properties = Map::new();
 	properties.insert("tokenSymbol".into(), default_runtime::TOKEN_SYMBOL.into());
-	properties.insert("tokenDecimals".into(), 18.into());
+	properties.insert("tokenDecimals".into(), default_runtime::DECIMALS.into());
 	properties.insert(
 		"ss58Format".into(),
 		default_runtime::SS58Prefix::get().into(),
addedpallets/balances-adapter/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/pallets/balances-adapter/Cargo.toml
@@ -0,0 +1,45 @@
+[package]
+edition = "2021"
+license = "GPLv3"
+name = "pallet-balances-adapter"
+version = "0.1.0"
+
+[dependencies]
+# Substrate
+frame-support = { workspace = true }
+frame-system = { workspace = true }
+pallet-balances = { workspace = true }
+pallet-structure = { workspace = true }
+sp-core = { workspace = true }
+sp-runtime = { workspace = true }
+sp-std = { workspace = true }
+
+#Parity
+codec = { workspace = true, package = "parity-scale-codec" }
+scale-info = { workspace = true }
+
+# Unique
+evm-coder = { workspace = true }
+pallet-evm = { workspace = true }
+
+#Local
+pallet-common = { workspace = true }
+pallet-evm-coder-substrate = { workspace = true }
+pallet-evm-transaction-payment = { workspace = true }
+up-data-structs = { workspace = true, features = ['serde1'] }
+
+[features]
+default = ["std"]
+std = [
+	"evm-coder/std",
+	"frame-support/std",
+	"frame-system/std",
+	"pallet-balances/std",
+	"pallet-common/std",
+	"pallet-evm-coder-substrate/std",
+	"pallet-evm/std",
+	"sp-core/std",
+	"sp-std/std",
+]
+stubgen = ["evm-coder/stubgen", "pallet-common/stubgen"]
+try-runtime = ["frame-support/try-runtime"]
addedpallets/balances-adapter/src/common.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/balances-adapter/src/common.rs
@@ -0,0 +1,359 @@
+use alloc::{vec, vec::Vec};
+use core::marker::PhantomData;
+use crate::{Config, NativeFungibleHandle, Pallet};
+use frame_support::{fail, weights::Weight};
+use pallet_balances::{weights::SubstrateWeight as BalancesWeight, WeightInfo};
+use pallet_common::{CommonCollectionOperations, CommonWeightInfo};
+use up_data_structs::TokenId;
+
+pub struct CommonWeights<T: Config>(PhantomData<T>);
+
+// All implementations with `Weight::default` used in methods that return error `UnsupportedOperation`.
+impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {
+	fn create_multiple_items(_amount: &[up_data_structs::CreateItemData]) -> Weight {
+		Weight::default()
+	}
+
+	fn create_multiple_items_ex(
+		_cost: &up_data_structs::CreateItemExData<T::CrossAccountId>,
+	) -> Weight {
+		Weight::default()
+	}
+
+	fn burn_item() -> Weight {
+		Weight::default()
+	}
+
+	fn set_collection_properties(_amount: u32) -> Weight {
+		Weight::default()
+	}
+
+	fn delete_collection_properties(_amount: u32) -> Weight {
+		Weight::default()
+	}
+
+	fn set_token_properties(_amount: u32) -> Weight {
+		Weight::default()
+	}
+
+	fn delete_token_properties(_amount: u32) -> Weight {
+		Weight::default()
+	}
+
+	fn set_token_property_permissions(_amount: u32) -> Weight {
+		Weight::default()
+	}
+
+	fn transfer() -> Weight {
+		<BalancesWeight<T> as WeightInfo>::transfer_allow_death()
+	}
+
+	fn approve() -> Weight {
+		Weight::default()
+	}
+
+	fn approve_from() -> Weight {
+		Weight::default()
+	}
+
+	fn transfer_from() -> Weight {
+		<BalancesWeight<T> as WeightInfo>::transfer_allow_death()
+	}
+
+	fn burn_from() -> Weight {
+		Weight::default()
+	}
+
+	fn burn_recursively_self_raw() -> Weight {
+		Weight::default()
+	}
+
+	fn burn_recursively_breadth_raw(_amount: u32) -> Weight {
+		Weight::default()
+	}
+
+	fn token_owner() -> Weight {
+		Weight::default()
+	}
+
+	fn set_allowance_for_all() -> Weight {
+		Weight::default()
+	}
+
+	fn force_repair_item() -> Weight {
+		Weight::default()
+	}
+}
+
+/// Implementation of `CommonCollectionOperations` for `FungibleHandle`. It wraps FungibleHandle Pallet
+/// methods and adds weight info.
+impl<T: Config> CommonCollectionOperations<T> for NativeFungibleHandle<T> {
+	fn create_item(
+		&self,
+		_sender: <T>::CrossAccountId,
+		_to: <T>::CrossAccountId,
+		_data: up_data_structs::CreateItemData,
+		_nesting_budget: &dyn up_data_structs::budget::Budget,
+	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+	}
+
+	fn create_multiple_items(
+		&self,
+		_sender: <T>::CrossAccountId,
+		_to: <T>::CrossAccountId,
+		_data: Vec<up_data_structs::CreateItemData>,
+		_nesting_budget: &dyn up_data_structs::budget::Budget,
+	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+	}
+
+	fn create_multiple_items_ex(
+		&self,
+		_sender: <T>::CrossAccountId,
+		_data: up_data_structs::CreateItemExData<<T>::CrossAccountId>,
+		_nesting_budget: &dyn up_data_structs::budget::Budget,
+	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+	}
+
+	fn burn_item(
+		&self,
+		_sender: <T>::CrossAccountId,
+		_token: TokenId,
+		_amount: u128,
+	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+	}
+
+	fn burn_item_recursively(
+		&self,
+		_sender: <T>::CrossAccountId,
+		_token: TokenId,
+		_self_budget: &dyn up_data_structs::budget::Budget,
+		_breadth_budget: &dyn up_data_structs::budget::Budget,
+	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+	}
+
+	fn set_collection_properties(
+		&self,
+		_sender: <T>::CrossAccountId,
+		_properties: Vec<up_data_structs::Property>,
+	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+	}
+
+	fn delete_collection_properties(
+		&self,
+		_sender: &<T>::CrossAccountId,
+		_property_keys: Vec<up_data_structs::PropertyKey>,
+	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+	}
+
+	fn set_token_properties(
+		&self,
+		_sender: <T>::CrossAccountId,
+		_token_id: TokenId,
+		_properties: Vec<up_data_structs::Property>,
+		_budget: &dyn up_data_structs::budget::Budget,
+	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+	}
+
+	fn delete_token_properties(
+		&self,
+		_sender: <T>::CrossAccountId,
+		_token_id: TokenId,
+		_property_keys: Vec<up_data_structs::PropertyKey>,
+		_budget: &dyn up_data_structs::budget::Budget,
+	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+	}
+
+	fn set_token_property_permissions(
+		&self,
+		_sender: &<T>::CrossAccountId,
+		_property_permissions: Vec<up_data_structs::PropertyKeyPermission>,
+	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+	}
+
+	fn transfer(
+		&self,
+		sender: <T>::CrossAccountId,
+		to: <T>::CrossAccountId,
+		_token: TokenId,
+		amount: u128,
+		budget: &dyn up_data_structs::budget::Budget,
+	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+		<Pallet<T>>::transfer(self, &sender, &to, amount, budget)
+	}
+
+	fn approve(
+		&self,
+		_sender: <T>::CrossAccountId,
+		_spender: <T>::CrossAccountId,
+		_token: TokenId,
+		_amount: u128,
+	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+	}
+
+	fn approve_from(
+		&self,
+		_sender: <T>::CrossAccountId,
+		_from: <T>::CrossAccountId,
+		_to: <T>::CrossAccountId,
+		_token: TokenId,
+		_amount: u128,
+	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+	}
+
+	fn transfer_from(
+		&self,
+		sender: <T>::CrossAccountId,
+		from: <T>::CrossAccountId,
+		to: <T>::CrossAccountId,
+		_token: TokenId,
+		amount: u128,
+		budget: &dyn up_data_structs::budget::Budget,
+	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+		<Pallet<T>>::transfer_from(self, &sender, &from, &to, amount, budget)
+	}
+
+	fn burn_from(
+		&self,
+		_sender: <T>::CrossAccountId,
+		_from: <T>::CrossAccountId,
+		_token: TokenId,
+		_amount: u128,
+		_budget: &dyn up_data_structs::budget::Budget,
+	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+	}
+
+	fn check_nesting(
+		&self,
+		_sender: <T>::CrossAccountId,
+		_from: (up_data_structs::CollectionId, TokenId),
+		_under: TokenId,
+		_budget: &dyn up_data_structs::budget::Budget,
+	) -> frame_support::sp_runtime::DispatchResult {
+		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+	}
+
+	fn nest(&self, _under: TokenId, _to_nest: (up_data_structs::CollectionId, TokenId)) {}
+
+	fn unnest(&self, _under: TokenId, _to_nest: (up_data_structs::CollectionId, TokenId)) {}
+
+	fn account_tokens(&self, account: <T>::CrossAccountId) -> Vec<TokenId> {
+		let balance = <Pallet<T>>::total_balance(&account);
+		if balance != 0 {
+			vec![TokenId::default()]
+		} else {
+			vec![]
+		}
+	}
+
+	fn collection_tokens(&self) -> Vec<TokenId> {
+		vec![TokenId::default()]
+	}
+
+	fn token_exists(&self, token: TokenId) -> bool {
+		token == TokenId::default()
+	}
+
+	fn last_token_id(&self) -> TokenId {
+		TokenId::default()
+	}
+
+	fn token_owner(
+		&self,
+		_token: TokenId,
+	) -> Result<<T>::CrossAccountId, up_data_structs::TokenOwnerError> {
+		Err(up_data_structs::TokenOwnerError::MultipleOwners)
+	}
+
+	fn token_owners(&self, _token: TokenId) -> Vec<<T>::CrossAccountId> {
+		vec![]
+	}
+
+	fn token_property(
+		&self,
+		_token_id: TokenId,
+		_key: &up_data_structs::PropertyKey,
+	) -> Option<up_data_structs::PropertyValue> {
+		None
+	}
+
+	fn token_properties(
+		&self,
+		_token: TokenId,
+		_keys: Option<Vec<up_data_structs::PropertyKey>>,
+	) -> Vec<up_data_structs::Property> {
+		vec![]
+	}
+
+	fn total_supply(&self) -> u32 {
+		1
+	}
+
+	fn account_balance(&self, account: T::CrossAccountId) -> u32 {
+		let balance = <Pallet<T>>::balance_of(&account);
+		(balance != 0).into()
+	}
+
+	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128 {
+		if token != TokenId::default() {
+			return 0;
+		}
+		<Pallet<T>>::balance_of(&account)
+	}
+
+	fn total_pieces(&self, token: TokenId) -> Option<u128> {
+		if token != TokenId::default() {
+			return None;
+		}
+		Some(<Pallet<T>>::total_issuance())
+	}
+
+	fn allowance(
+		&self,
+		_sender: <T>::CrossAccountId,
+		_spender: <T>::CrossAccountId,
+		_token: TokenId,
+	) -> u128 {
+		0
+	}
+
+	fn refungible_extensions(&self) -> Option<&dyn pallet_common::RefungibleExtensions<T>> {
+		None
+	}
+
+	fn set_allowance_for_all(
+		&self,
+		_owner: <T>::CrossAccountId,
+		_operator: <T>::CrossAccountId,
+		_approve: bool,
+	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+	}
+
+	fn allowance_for_all(
+		&self,
+		_owner: <T>::CrossAccountId,
+		_operator: <T>::CrossAccountId,
+	) -> bool {
+		false
+	}
+
+	fn repair_item(
+		&self,
+		_token: TokenId,
+	) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+		fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+	}
+}
addedpallets/balances-adapter/src/erc.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/balances-adapter/src/erc.rs
@@ -0,0 +1,165 @@
+use crate::{Config, NativeFungibleHandle, Pallet, SelfWeightOf};
+use evm_coder::{abi::AbiType, generate_stubgen, solidity_interface, types::*};
+use pallet_balances::WeightInfo;
+use pallet_common::{
+	erc::{CommonEvmHandler, CrossAccountId, PrecompileHandle, PrecompileResult},
+	eth::CrossAddress,
+};
+use pallet_evm_coder_substrate::{
+	call, dispatch_to_evm,
+	execution::{PreDispatch, Result},
+	frontier_contract, WithRecorder,
+};
+use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};
+use sp_core::{U256, Get};
+
+frontier_contract! {
+	macro_rules! NativeFungibleHandle_result {...}
+	impl<T: Config> Contract for NativeFungibleHandle<T> {...}
+}
+
+#[solidity_interface(name = ERC20, enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x942e8b22)]
+impl<T: Config> NativeFungibleHandle<T> {
+	fn allowance(&self, _owner: Address, _spender: Address) -> Result<U256> {
+		Ok(U256::zero())
+	}
+
+	fn approve(&mut self, _caller: Caller, _spender: Address, _amount: U256) -> Result<bool> {
+		Err("Approve not supported".into())
+	}
+
+	fn balance_of(&self, owner: Address) -> Result<U256> {
+		self.consume_store_reads(1)?;
+		let owner = T::CrossAccountId::from_eth(owner);
+		let balance = <Pallet<T>>::balance_of(&owner);
+		Ok(balance.into())
+	}
+
+	fn decimals(&self) -> Result<u8> {
+		Ok(T::Decimals::get())
+	}
+
+	fn name(&self) -> Result<String> {
+		Ok(T::Name::get())
+	}
+
+	fn symbol(&self) -> Result<String> {
+		Ok(T::Symbol::get())
+	}
+
+	fn total_supply(&self) -> Result<U256> {
+		self.consume_store_reads(1)?;
+		Ok(<Pallet<T>>::total_issuance().into())
+	}
+
+	#[weight(<SelfWeightOf<T>>::transfer_allow_death())]
+	fn transfer(&mut self, caller: Caller, to: Address, amount: U256) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = T::CrossAccountId::from_eth(to);
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+		let budget = self
+			.recorder()
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		<Pallet<T>>::transfer(self, &caller, &to, amount, &budget)
+			.map_err(|e| dispatch_to_evm::<T>(e.error))?;
+		Ok(true)
+	}
+
+	#[weight(<SelfWeightOf<T>>::transfer_allow_death())]
+	fn transfer_from(
+		&mut self,
+		caller: Caller,
+		from: Address,
+		to: Address,
+		amount: U256,
+	) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let from = T::CrossAccountId::from_eth(from);
+		let to = T::CrossAccountId::from_eth(to);
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+		let budget = self
+			.recorder()
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		<Pallet<T>>::transfer_from(self, &caller, &from, &to, amount, &budget)
+			.map_err(|e| dispatch_to_evm::<T>(e.error))?;
+		Ok(true)
+	}
+}
+
+#[solidity_interface(name = ERC20UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]
+impl<T: Config> NativeFungibleHandle<T>
+where
+	T::AccountId: From<[u8; 32]>,
+{
+	fn balance_of_cross(&self, owner: CrossAddress) -> Result<U256> {
+		self.consume_store_reads(1)?;
+		let owner = owner.into_sub_cross_account::<T>()?;
+		let balance = <Pallet<T>>::balance_of(&owner);
+		Ok(balance.into())
+	}
+
+	#[weight(<SelfWeightOf<T>>::transfer_allow_death())]
+	fn transfer_cross(&mut self, caller: Caller, to: CrossAddress, amount: U256) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = to.into_sub_cross_account::<T>()?;
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+		let budget = self
+			.recorder()
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		<Pallet<T>>::transfer(self, &caller, &to, amount, &budget)
+			.map_err(|e| dispatch_to_evm::<T>(e.error))?;
+
+		Ok(true)
+	}
+
+	#[weight(<SelfWeightOf<T>>::transfer_allow_death())]
+	fn transfer_from_cross(
+		&mut self,
+		caller: Caller,
+		from: CrossAddress,
+		to: CrossAddress,
+		amount: U256,
+	) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let from = from.into_sub_cross_account::<T>()?;
+		let to = to.into_sub_cross_account::<T>()?;
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+
+		if from != caller {
+			return Err("no permission".into());
+		}
+
+		let budget = self
+			.recorder()
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		<Pallet<T>>::transfer_from(self, &caller, &from, &to, amount, &budget)
+			.map_err(|e| dispatch_to_evm::<T>(e.error))?;
+
+		Ok(true)
+	}
+}
+
+#[solidity_interface(
+	name = UniqueNativeFungible,
+	is(ERC20, ERC20UniqueExtensions),
+	enum(derive(PreDispatch))
+)]
+impl<T: Config> NativeFungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}
+
+generate_stubgen!(gen_impl, UniqueNativeFungibleCall<()>, true);
+generate_stubgen!(gen_iface, UniqueNativeFungibleCall<()>, false);
+
+impl<T: Config> CommonEvmHandler for NativeFungibleHandle<T>
+where
+	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,
+{
+	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNativeFungible.raw");
+
+	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
+		call::<T, UniqueNativeFungibleCall<T>, _, _>(handle, self)
+	}
+}
addedpallets/balances-adapter/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/balances-adapter/src/lib.rs
@@ -0,0 +1,206 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+
+extern crate alloc;
+use core::ops::Deref;
+
+use frame_support::sp_runtime::DispatchResult;
+use pallet_evm_coder_substrate::{WithRecorder, SubstrateRecorder};
+pub use pallet::*;
+
+pub mod common;
+pub mod erc;
+
+pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
+
+/// Handle for native fungible collection
+pub struct NativeFungibleHandle<T: Config>(SubstrateRecorder<T>);
+impl<T: Config> NativeFungibleHandle<T> {
+	/// Creates a handle
+	pub fn new() -> NativeFungibleHandle<T> {
+		Self(SubstrateRecorder::new(u64::MAX))
+	}
+
+	/// Creates a handle
+	pub fn new_with_gas_limit(gas_limit: u64) -> NativeFungibleHandle<T> {
+		Self(SubstrateRecorder::new(gas_limit))
+	}
+
+	/// Check if the collection is internal
+	pub fn check_is_internal(&self) -> DispatchResult {
+		Ok(())
+	}
+}
+
+impl<T: Config> WithRecorder<T> for NativeFungibleHandle<T> {
+	fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {
+		&self.0
+	}
+	fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {
+		self.0
+	}
+}
+
+impl<T: Config> Deref for NativeFungibleHandle<T> {
+	type Target = SubstrateRecorder<T>;
+
+	fn deref(&self) -> &Self::Target {
+		&self.0
+	}
+}
+#[frame_support::pallet]
+pub mod pallet {
+	use super::*;
+	use alloc::string::String;
+	use frame_support::{
+		dispatch::PostDispatchInfo,
+		ensure,
+		pallet_prelude::{DispatchResultWithPostInfo, Pays},
+		traits::{
+			Get,
+			fungible::{Inspect, Mutate},
+			tokens::Preservation,
+		},
+	};
+	use pallet_balances::WeightInfo;
+	use pallet_common::{erc::CrossAccountId, Error as CommonError, Pallet as PalletCommon};
+	use pallet_structure::Pallet as PalletStructure;
+	use sp_core::U256;
+	use sp_runtime::DispatchError;
+	use up_data_structs::{budget::Budget, mapping::TokenAddressMapping};
+
+	#[pallet::config]
+	pub trait Config:
+		frame_system::Config
+		+ pallet_evm_coder_substrate::Config
+		+ pallet_common::Config
+		+ pallet_structure::Config
+	{
+		/// Inspect from `pallet_balances`
+		type Inspect: frame_support::traits::tokens::fungible::Inspect<
+			Self::AccountId,
+			Balance = Self::CurrencyBalance,
+		>;
+
+		/// Mutate from `pallet_balances`
+		type Mutate: frame_support::traits::tokens::fungible::Mutate<
+			Self::AccountId,
+			Balance = Self::CurrencyBalance,
+		>;
+
+		/// Balance type of chain
+		type CurrencyBalance: Into<U256> + TryFrom<U256> + TryFrom<u128> + Into<u128>;
+
+		/// Decimals of balance
+		type Decimals: Get<u8>;
+		/// Collection name
+		type Name: Get<String>;
+		/// Collection symbol
+		type Symbol: Get<String>;
+
+		/// Weight information
+		type WeightInfo: WeightInfo;
+	}
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	impl<T: Config> Pallet<T> {
+		pub fn balance_of(account: &T::CrossAccountId) -> u128 {
+			T::Inspect::balance(account.as_sub()).into()
+		}
+
+		pub fn total_balance(account: &T::CrossAccountId) -> u128 {
+			T::Inspect::total_balance(account.as_sub()).into()
+		}
+
+		pub fn total_issuance() -> u128 {
+			T::Inspect::total_issuance().into()
+		}
+
+		/// Checks if a non-owner has (enough) allowance from the owner to perform operations on the tokens.
+		/// Returns the expected remaining allowance - it should be set manually if the transaction proceeds.
+		///
+		/// - `spender`: CrossAccountId who has the allowance rights.
+		/// - `from`: The owner of the tokens who sets the allowance.
+		/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
+		fn check_allowed(
+			spender: &T::CrossAccountId,
+			from: &T::CrossAccountId,
+			nesting_budget: &dyn Budget,
+		) -> Result<u128, DispatchError> {
+			if let Some((collection_id, token_id)) =
+				T::CrossTokenAddressMapping::address_to_token(from)
+			{
+				ensure!(
+					<PalletStructure<T>>::check_indirectly_owned(
+						spender.clone(),
+						collection_id,
+						token_id,
+						None,
+						nesting_budget
+					)?,
+					<CommonError<T>>::ApprovedValueTooLow,
+				);
+			} else if !spender.conv_eq(from) {
+				return Ok(0);
+			}
+
+			Ok(Self::balance_of(from))
+		}
+
+		/// Transfers the specified amount of tokens. Will check that
+		/// the transfer is allowed for the token.
+		///
+		/// - `collection`: Collection that contains the token.
+		/// - `from`: Owner of tokens to transfer.
+		/// - `to`: Recepient of transfered tokens.
+		/// - `amount`: Amount of tokens to transfer.
+		/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
+		pub fn transfer(
+			_collection: &NativeFungibleHandle<T>,
+			from: &T::CrossAccountId,
+			to: &T::CrossAccountId,
+			amount: u128,
+			_nesting_budget: &dyn Budget,
+		) -> DispatchResultWithPostInfo {
+			<PalletCommon<T>>::ensure_correct_receiver(to)?;
+
+			if from != to && amount != 0 {
+				let amount = amount
+					.try_into()
+					.map_err(|_| sp_runtime::ArithmeticError::Overflow)?;
+				T::Mutate::transfer(from.as_sub(), to.as_sub(), amount, Preservation::Expendable)?;
+			};
+
+			Ok(PostDispatchInfo {
+				actual_weight: Some(<SelfWeightOf<T>>::transfer_allow_death()),
+				pays_fee: Pays::Yes,
+			})
+		}
+
+		/// Transfer NFT token from one account to another.
+		///
+		/// Same as the [`Self::transfer`] but spender doesn't needs to be the owner of the token.
+		/// The owner should set allowance for the spender to transfer token.
+		///
+		/// - `collection`: Collection that contains the token.
+		/// - `spender`: Account that spend the money.
+		/// - `from`: Owner of tokens to transfer.
+		/// - `to`: Recepient of transfered tokens.
+		/// - `amount`: Amount of tokens to transfer.
+		/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
+		pub fn transfer_from(
+			collection: &NativeFungibleHandle<T>,
+			spender: &T::CrossAccountId,
+			from: &T::CrossAccountId,
+			to: &T::CrossAccountId,
+			amount: u128,
+			nesting_budget: &dyn Budget,
+		) -> DispatchResultWithPostInfo {
+			let allowance = Self::check_allowed(spender, from, nesting_budget)?;
+			if allowance < amount {
+				return Err(<CommonError<T>>::ApprovedValueTooLow.into());
+			}
+			Self::transfer(collection, from, to, amount, nesting_budget)
+		}
+	}
+}
addedpallets/balances-adapter/src/stubs/UniqueNativeFungible.rawdiffbeforeafterboth

binary blob — no preview

addedpallets/balances-adapter/src/stubs/UniqueNativeFungible.soldiffbeforeafterboth
--- /dev/null
+++ b/pallets/balances-adapter/src/stubs/UniqueNativeFungible.sol
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: OTHER
+// This code is automatically generated
+
+pragma solidity >=0.8.0 <0.9.0;
+
+/// @dev 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;
+	}
+}
+
+/// @dev the ERC-165 identifier for this interface is 0x1313556c
+contract ERC20UniqueExtensions is Dummy, ERC165 {
+	/// @dev EVM selector for this function is: 0xec069398,
+	///  or in textual repr: balanceOfCross((address,uint256))
+	function balanceOfCross(CrossAddress memory owner) public view returns (uint256) {
+		require(false, stub_error);
+		owner;
+		dummy;
+		return 0;
+	}
+
+	/// @dev EVM selector for this function is: 0x2ada85ff,
+	///  or in textual repr: transferCross((address,uint256),uint256)
+	function transferCross(CrossAddress memory to, uint256 amount) public returns (bool) {
+		require(false, stub_error);
+		to;
+		amount;
+		dummy = 0;
+		return false;
+	}
+
+	/// @dev EVM selector for this function is: 0xd5cf430b,
+	///  or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+	function transferFromCross(
+		CrossAddress memory from,
+		CrossAddress memory to,
+		uint256 amount
+	) public returns (bool) {
+		require(false, stub_error);
+		from;
+		to;
+		amount;
+		dummy = 0;
+		return false;
+	}
+}
+
+/// Cross account struct
+struct CrossAddress {
+	address eth;
+	uint256 sub;
+}
+
+/// @dev inlined interface
+contract ERC20Events {
+	event Transfer(address indexed from, address indexed to, uint256 value);
+	event Approval(address indexed owner, address indexed spender, uint256 value);
+}
+
+/// @dev the ERC-165 identifier for this interface is 0x942e8b22
+contract ERC20 is Dummy, ERC165, ERC20Events {
+	/// @dev EVM selector for this function is: 0xdd62ed3e,
+	///  or in textual repr: allowance(address,address)
+	function allowance(address owner, address spender) public view returns (uint256) {
+		require(false, stub_error);
+		owner;
+		spender;
+		dummy;
+		return 0;
+	}
+
+	/// @dev EVM selector for this function is: 0x095ea7b3,
+	///  or in textual repr: approve(address,uint256)
+	function approve(address spender, uint256 amount) public returns (bool) {
+		require(false, stub_error);
+		spender;
+		amount;
+		dummy = 0;
+		return false;
+	}
+
+	/// @dev EVM selector for this function is: 0x70a08231,
+	///  or in textual repr: balanceOf(address)
+	function balanceOf(address owner) public view returns (uint256) {
+		require(false, stub_error);
+		owner;
+		dummy;
+		return 0;
+	}
+
+	/// @dev EVM selector for this function is: 0x313ce567,
+	///  or in textual repr: decimals()
+	function decimals() public view returns (uint8) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
+
+	/// @dev EVM selector for this function is: 0x06fdde03,
+	///  or in textual repr: name()
+	function name() public view returns (string memory) {
+		require(false, stub_error);
+		dummy;
+		return "";
+	}
+
+	/// @dev EVM selector for this function is: 0x95d89b41,
+	///  or in textual repr: symbol()
+	function symbol() public view returns (string memory) {
+		require(false, stub_error);
+		dummy;
+		return "";
+	}
+
+	/// @dev EVM selector for this function is: 0x18160ddd,
+	///  or in textual repr: totalSupply()
+	function totalSupply() public view returns (uint256) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
+
+	/// @dev EVM selector for this function is: 0xa9059cbb,
+	///  or in textual repr: transfer(address,uint256)
+	function transfer(address to, uint256 amount) public returns (bool) {
+		require(false, stub_error);
+		to;
+		amount;
+		dummy = 0;
+		return false;
+	}
+
+	/// @dev EVM selector for this function is: 0x23b872dd,
+	///  or in textual repr: transferFrom(address,address,uint256)
+	function transferFrom(
+		address from,
+		address to,
+		uint256 amount
+	) public returns (bool) {
+		require(false, stub_error);
+		from;
+		to;
+		amount;
+		dummy = 0;
+		return false;
+	}
+}
+
+contract UniqueNativeFungible is Dummy, ERC165, ERC20, ERC20UniqueExtensions {}
modifiedpallets/common/src/dispatch.rsdiffbeforeafterboth
--- a/pallets/common/src/dispatch.rs
+++ b/pallets/common/src/dispatch.rs
@@ -11,7 +11,7 @@
 use sp_runtime::DispatchError;
 use up_data_structs::{CollectionId, CreateCollectionData, CollectionFlags};
 
-use crate::{pallet::Config, CommonCollectionOperations, CollectionHandle};
+use crate::{pallet::Config, CommonCollectionOperations};
 
 // TODO: move to benchmarking
 /// Price of [`dispatch_tx`] call with noop `call` argument
@@ -34,16 +34,11 @@
 	collection: CollectionId,
 	call: C,
 ) -> DispatchResultWithPostInfo {
-	let handle =
-		CollectionHandle::try_get(collection).map_err(|error| DispatchErrorWithPostInfo {
-			post_info: PostDispatchInfo {
-				actual_weight: Some(dispatch_weight::<T>()),
-				pays_fee: Pays::Yes,
-			},
-			error,
-		})?;
-	handle
-		.check_is_internal()
+	let dispatched = T::CollectionDispatch::dispatch(collection)
+		.and_then(|dispatched| {
+			dispatched.check_is_internal()?;
+			Ok(dispatched)
+		})
 		.map_err(|error| DispatchErrorWithPostInfo {
 			post_info: PostDispatchInfo {
 				actual_weight: Some(dispatch_weight::<T>()),
@@ -51,7 +46,6 @@
 			},
 			error,
 		})?;
-	let dispatched = T::CollectionDispatch::dispatch(handle);
 	let mut result = call(dispatched.as_dyn());
 	match &mut result {
 		Ok(PostDispatchInfo {
@@ -72,6 +66,9 @@
 
 /// Interface for working with different collections through the dispatcher.
 pub trait CollectionDispatch<T: Config> {
+	/// Check if the collection is internal.
+	fn check_is_internal(&self) -> DispatchResult;
+
 	/// Create a collection. The collection will be created according to the value of [`data.mode`](CreateCollectionData::mode).
 	///
 	/// * `sender` - The user who will become the owner of the collection.
@@ -87,15 +84,14 @@
 	///
 	/// * `sender` - The owner of the collection.
 	/// * `handle` - Collection handle.
-	fn destroy(sender: T::CrossAccountId, handle: CollectionHandle<T>) -> DispatchResult;
+	fn destroy(sender: T::CrossAccountId, collection_id: CollectionId) -> DispatchResult;
 
 	/// Get a specialized collection from the handle.
 	///
 	/// * `handle` - Collection handle.
-	fn dispatch(handle: CollectionHandle<T>) -> Self;
-
-	/// Get the collection handle for the corresponding implementation.
-	fn into_inner(self) -> CollectionHandle<T>;
+	fn dispatch(collection_id: CollectionId) -> Result<Self, DispatchError>
+	where
+		Self: Sized;
 
 	/// Get the implementation of [`CommonCollectionOperations`].
 	fn as_dyn(&self) -> &dyn CommonCollectionOperations<T>;
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -72,7 +72,6 @@
 	dispatch::Pays,
 	transactional, fail,
 };
-use pallet_evm::GasWeightMapping;
 use up_data_structs::{
 	AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionFlags,
 	RpcCollectionFlags, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,
@@ -105,7 +104,7 @@
 /// Collection handle contains information about collection data and id.
 /// Also provides functionality to count consumed gas.
 ///
-/// CollectionHandle is used as a generic wrapper for collections of all types.
+/// CollectionHandle is used as a generic wrapper for collections of all types (except native fungible).
 /// It allows to perform common operations and queries on any collection type,
 /// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].
 #[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]
@@ -129,11 +128,7 @@
 impl<T: Config> CollectionHandle<T> {
 	/// Same as [CollectionHandle::new] but with an explicit gas limit.
 	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {
-		<CollectionById<T>>::get(id).map(|collection| Self {
-			id,
-			collection,
-			recorder: SubstrateRecorder::new(gas_limit),
-		})
+		Self::new_with_recorder(id, SubstrateRecorder::new(gas_limit))
 	}
 
 	/// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].
@@ -161,14 +156,7 @@
 		&self,
 		reads: u64,
 	) -> pallet_evm_coder_substrate::execution::Result<()> {
-		self.recorder
-			.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(
-				<T as frame_system::Config>::DbWeight::get()
-					.read
-					.saturating_mul(reads),
-				// TODO: measure proof
-				0,
-			)))
+		self.recorder().consume_store_reads(reads)
 	}
 
 	/// Consume gas for writing.
@@ -176,14 +164,7 @@
 		&self,
 		writes: u64,
 	) -> pallet_evm_coder_substrate::execution::Result<()> {
-		self.recorder
-			.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(
-				<T as frame_system::Config>::DbWeight::get()
-					.write
-					.saturating_mul(writes),
-				// TODO: measure proof
-				0,
-			)))
+		self.recorder().consume_store_writes(writes)
 	}
 
 	/// Consume gas for reading and writing.
@@ -192,15 +173,8 @@
 		reads: u64,
 		writes: u64,
 	) -> pallet_evm_coder_substrate::execution::Result<()> {
-		let weight = <T as frame_system::Config>::DbWeight::get();
-		let reads = weight.read.saturating_mul(reads);
-		let writes = weight.read.saturating_mul(writes);
-		self.recorder
-			.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(
-				reads.saturating_add(writes),
-				// TODO: measure proof
-				0,
-			)))
+		self.recorder()
+			.consume_store_reads_and_writes(reads, writes)
 	}
 
 	/// Save collection to storage.
@@ -469,6 +443,8 @@
 	}
 
 	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
+	/// Collection id for native fungible collction.
+	pub const NATIVE_FUNGIBLE_COLLECTION_ID: CollectionId = CollectionId(0);
 
 	#[pallet::pallet]
 	#[pallet::storage_version(STORAGE_VERSION)]
modifiedpallets/evm-coder-substrate/src/lib.rsdiffbeforeafterboth
--- a/pallets/evm-coder-substrate/src/lib.rs
+++ b/pallets/evm-coder-substrate/src/lib.rs
@@ -37,7 +37,7 @@
 	ExitError, ExitRevert, ExitSucceed, GasWeightMapping, PrecompileFailure, PrecompileOutput,
 	PrecompileResult, PrecompileHandle,
 };
-use sp_core::H160;
+use sp_core::{Get, H160};
 // #[cfg(feature = "runtime-benchmarks")]
 // pub mod benchmarking;
 pub mod execution;
@@ -204,6 +204,40 @@
 			Err(Error::Error(e)) => Err(e.into()),
 		})
 	}
+
+	/// Consume gas for reading.
+	pub fn consume_store_reads(&self, reads: u64) -> execution::Result<()> {
+		self.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(
+			<T as frame_system::Config>::DbWeight::get()
+				.read
+				.saturating_mul(reads),
+			// TODO: measure proof
+			0,
+		)))
+	}
+
+	/// Consume gas for writing.
+	pub fn consume_store_writes(&self, writes: u64) -> execution::Result<()> {
+		self.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(
+			<T as frame_system::Config>::DbWeight::get()
+				.write
+				.saturating_mul(writes),
+			// TODO: measure proof
+			0,
+		)))
+	}
+
+	/// Consume gas for reading and writing.
+	pub fn consume_store_reads_and_writes(&self, reads: u64, writes: u64) -> execution::Result<()> {
+		let weight = <T as frame_system::Config>::DbWeight::get();
+		let reads = weight.read.saturating_mul(reads);
+		let writes = weight.read.saturating_mul(writes);
+		self.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(
+			reads.saturating_add(writes),
+			// TODO: measure proof
+			0,
+		)))
+	}
 }
 
 pub fn dispatch_to_evm<T: Config>(err: DispatchError) -> execution::Error {
modifiedpallets/fungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -109,7 +109,8 @@
 			.recorder
 			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::transfer(self, &caller, &to, amount, &budget).map_err(|_| "transfer error")?;
+		<Pallet<T>>::transfer(self, &caller, &to, amount, &budget)
+			.map_err(|e| dispatch_to_evm::<T>(e.error))?;
 		Ok(true)
 	}
 
modifiedpallets/nonfungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/nonfungible/Cargo.toml
+++ b/pallets/nonfungible/Cargo.toml
@@ -12,6 +12,7 @@
 frame-benchmarking = { workspace = true, optional = true }
 frame-support = { workspace = true }
 frame-system = { workspace = true }
+pallet-balances = { workspace = true }
 pallet-common = { workspace = true }
 pallet-evm = { workspace = true }
 pallet-evm-coder-substrate = { workspace = true }
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -166,7 +166,11 @@
 
 	#[pallet::config]
 	pub trait Config:
-		frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config
+		frame_system::Config
+		+ pallet_common::Config
+		+ pallet_structure::Config
+		+ pallet_evm::Config
+		+ pallet_balances::Config
 	{
 		type WeightInfo: WeightInfo;
 	}
@@ -1325,11 +1329,15 @@
 	}
 
 	fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {
-		<TokenChildren<T>>::insert((under.0, under.1, (to_nest.0, to_nest.1)), true);
+		if to_nest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+			<TokenChildren<T>>::insert((under.0, under.1, to_nest), true);
+		}
 	}
 
 	fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {
-		<TokenChildren<T>>::remove((under.0, under.1, to_unnest));
+		if to_unnest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+			<TokenChildren<T>>::remove((under.0, under.1, to_unnest));
+		}
 	}
 
 	fn collection_has_tokens(collection_id: CollectionId) -> bool {
modifiedpallets/structure/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/structure/src/benchmarking.rs
+++ b/pallets/structure/src/benchmarking.rs
@@ -42,7 +42,7 @@
 			},
 			CollectionFlags::default(),
 		)?;
-		let dispatch = T::CollectionDispatch::dispatch(CollectionHandle::try_get(CollectionId(1))?);
+		let dispatch = T::CollectionDispatch::dispatch(CollectionId(1))?;
 		let dispatch = dispatch.as_dyn();
 
 		dispatch.create_item(caller_cross.clone(), caller_cross.clone(), CreateItemData::NFT(CreateNftData::default()), &Unlimited)?;
modifiedpallets/structure/src/lib.rsdiffbeforeafterboth
--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -60,7 +60,7 @@
 use frame_support::dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo};
 use frame_support::fail;
 pub use pallet::*;
-use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};
+use pallet_common::{dispatch::CollectionDispatch};
 use up_data_structs::{
 	CollectionId, TokenId, mapping::TokenAddressMapping, budget::Budget, TokenOwnerError,
 };
@@ -155,11 +155,10 @@
 		token: TokenId,
 	) -> Result<Parent<T::CrossAccountId>, DispatchError> {
 		// TODO: Reduce cost by not reading collection config
-		let handle = match CollectionHandle::try_get(collection) {
+		let handle = match T::CollectionDispatch::dispatch(collection) {
 			Ok(v) => v,
 			Err(_) => return Ok(Parent::TokenNotFound),
 		};
-		let handle = T::CollectionDispatch::dispatch(handle);
 		let handle = handle.as_dyn();
 
 		Ok(match handle.token_owner(token) {
@@ -279,8 +278,7 @@
 		self_budget: &dyn Budget,
 		breadth_budget: &dyn Budget,
 	) -> DispatchResultWithPostInfo {
-		let handle = <CollectionHandle<T>>::try_get(collection)?;
-		let dispatch = T::CollectionDispatch::dispatch(handle);
+		let dispatch = T::CollectionDispatch::dispatch(collection)?;
 		let dispatch = dispatch.as_dyn();
 		dispatch.burn_item_recursively(from.clone(), token, self_budget, breadth_budget)
 	}
@@ -405,9 +403,7 @@
 			return Ok(())
 		};
 
-		let handle = <CollectionHandle<T>>::try_get(collection)?;
-
-		let dispatch = T::CollectionDispatch::dispatch(handle);
+		let dispatch = T::CollectionDispatch::dispatch(collection)?;
 		let dispatch = dispatch.as_dyn();
 
 		action(dispatch, token)
modifiedpallets/unique/Cargo.tomldiffbeforeafterboth
--- a/pallets/unique/Cargo.toml
+++ b/pallets/unique/Cargo.toml
@@ -24,6 +24,7 @@
 	'frame-benchmarking/std',
 	'frame-support/std',
 	'frame-system/std',
+	'pallet-balances-adapter/std',
 	'pallet-common/std',
 	'pallet-evm-coder-substrate/std',
 	'pallet-evm/std',
@@ -46,6 +47,7 @@
 frame-benchmarking = { workspace = true, optional = true }
 frame-support = { workspace = true }
 frame-system = { workspace = true }
+pallet-balances-adapter = { workspace = true }
 pallet-common = { workspace = true }
 pallet-evm = { workspace = true }
 pallet-evm-coder-substrate = { workspace = true }
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -440,6 +440,10 @@
 			collection_id: CollectionId,
 			address: T::CrossAccountId,
 		) -> DispatchResult {
+			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+				fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+			}
+
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			collection.check_is_internal()?;
@@ -467,6 +471,10 @@
 			collection_id: CollectionId,
 			address: T::CrossAccountId,
 		) -> DispatchResult {
+			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+				fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+			}
+
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			collection.check_is_internal()?;
@@ -493,6 +501,9 @@
 			collection_id: CollectionId,
 			new_owner: T::AccountId,
 		) -> DispatchResult {
+			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+				fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+			}
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let new_owner = T::CrossAccountId::from_sub(new_owner);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
@@ -522,6 +533,9 @@
 			collection_id: CollectionId,
 			new_admin_id: T::CrossAccountId,
 		) -> DispatchResult {
+			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+				fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+			}
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)
@@ -548,6 +562,9 @@
 			collection_id: CollectionId,
 			account_id: T::CrossAccountId,
 		) -> DispatchResult {
+			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+				fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+			}
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)
@@ -573,6 +590,9 @@
 			collection_id: CollectionId,
 			new_sponsor: T::AccountId,
 		) -> DispatchResult {
+			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+				fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+			}
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			target_collection.set_sponsor(&sender, new_sponsor.clone())
@@ -597,6 +617,9 @@
 			origin: OriginFor<T>,
 			collection_id: CollectionId,
 		) -> DispatchResult {
+			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+				fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+			}
 			let sender = ensure_signed(origin)?;
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			target_collection.confirm_sponsorship(&sender)
@@ -617,6 +640,9 @@
 			origin: OriginFor<T>,
 			collection_id: CollectionId,
 		) -> DispatchResult {
+			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+				fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+			}
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			target_collection.remove_sponsor(&sender)
@@ -894,6 +920,9 @@
 			collection_id: CollectionId,
 			value: bool,
 		) -> DispatchResult {
+			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+				fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+			}
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			target_collection.check_is_internal()?;
@@ -1146,6 +1175,9 @@
 			collection_id: CollectionId,
 			new_limit: CollectionLimits,
 		) -> DispatchResult {
+			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+				fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+			}
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			<PalletCommon<T>>::update_limits(&sender, &mut target_collection, new_limit)
@@ -1170,6 +1202,9 @@
 			collection_id: CollectionId,
 			new_permission: CollectionPermissions,
 		) -> DispatchResult {
+			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+				fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+			}
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			<PalletCommon<T>>::update_permissions(&sender, &mut target_collection, new_permission)
@@ -1238,6 +1273,9 @@
 			origin: OriginFor<T>,
 			collection_id: CollectionId,
 		) -> DispatchResult {
+			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+				fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+			}
 			ensure_root(origin)?;
 			<PalletCommon<T>>::repair_collection(collection_id)
 		}
@@ -1296,10 +1334,7 @@
 			sender: T::CrossAccountId,
 			collection_id: CollectionId,
 		) -> DispatchResult {
-			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
-			collection.check_is_internal()?;
-
-			T::CollectionDispatch::destroy(sender, collection)?;
+			T::CollectionDispatch::destroy(sender, collection_id)?;
 
 			// TODO: basket cleanup should be moved elsewhere
 			// Maybe runtime dispatch.rs should perform it?
modifiedruntime/common/config/pallets/mod.rsdiffbeforeafterboth
--- a/runtime/common/config/pallets/mod.rs
+++ b/runtime/common/config/pallets/mod.rs
@@ -14,6 +14,7 @@
 // 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 alloc::string::{String, ToString};
 use frame_support::parameter_types;
 use sp_runtime::traits::AccountIdConversion;
 use crate::{
@@ -23,9 +24,10 @@
 		weights::CommonWeights,
 		RelayChainBlockNumberProvider,
 	},
-	Runtime, RuntimeEvent, RuntimeCall, RuntimeOrigin, Balances,
+	Runtime, RuntimeEvent, RuntimeCall, RuntimeOrigin, RUNTIME_NAME, TOKEN_SYMBOL, DECIMALS,
+	Balances,
 };
-use frame_support::traits::{ConstU32, ConstU64};
+use frame_support::traits::{ConstU32, ConstU64, Currency};
 use up_common::{
 	types::{AccountId, Balance, BlockNumber},
 	constants::*,
@@ -51,8 +53,8 @@
 pub mod preimage;
 
 parameter_types! {
+	pub const CollectionCreationPrice: Balance = 2 * UNIQUE;
 	pub TreasuryAccountId: AccountId = TreasuryModuleId::get().into_account_truncating();
-	pub const CollectionCreationPrice: Balance = 2 * UNIQUE;
 }
 
 impl pallet_common::Config for Runtime {
@@ -85,6 +87,21 @@
 }
 
 parameter_types! {
+	pub const Decimals: u8 = DECIMALS;
+	pub Name: String = RUNTIME_NAME.to_string();
+	pub Symbol: String = TOKEN_SYMBOL.to_string();
+}
+impl pallet_balances_adapter::Config for Runtime {
+	type Inspect = Balances;
+	type Mutate = Balances;
+	type CurrencyBalance = <Balances as Currency<Self::AccountId>>::Balance;
+	type Decimals = Decimals;
+	type Name = Name;
+	type Symbol = Symbol;
+	type WeightInfo = pallet_balances::weights::SubstrateWeight<Self>;
+}
+
+parameter_types! {
 	pub const InflationBlockInterval: BlockNumber = 100; // every time per how many blocks inflation is applied
 }
 
modifiedruntime/common/construct_runtime.rsdiffbeforeafterboth
--- a/runtime/common/construct_runtime.rs
+++ b/runtime/common/construct_runtime.rs
@@ -106,6 +106,8 @@
 
 				Maintenance: pallet_maintenance = 154,
 
+				BalancesAdapter: pallet_balances_adapter = 155,
+
 				#[cfg(feature = "pallet-test-utils")]
 				TestUtils: pallet_test_utils = 255,
 			}
modifiedruntime/common/dispatch.rsdiffbeforeafterboth
--- a/runtime/common/dispatch.rs
+++ b/runtime/common/dispatch.rs
@@ -14,7 +14,7 @@
 // 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 frame_support::{dispatch::DispatchResult, ensure};
+use frame_support::{dispatch::DispatchResult, ensure, fail};
 use pallet_evm::{PrecompileHandle, PrecompileResult};
 use sp_core::H160;
 use sp_runtime::DispatchError;
@@ -25,6 +25,7 @@
 };
 pub use pallet_common::dispatch::CollectionDispatch;
 use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};
+use pallet_balances_adapter::{NativeFungibleHandle};
 use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};
 use pallet_refungible::{
 	Pallet as PalletRefungible, RefungibleHandle, erc_token::RefungibleTokenHandle,
@@ -39,20 +40,35 @@
 
 pub enum CollectionDispatchT<T>
 where
-	T: pallet_fungible::Config + pallet_nonfungible::Config + pallet_refungible::Config,
+	T: pallet_fungible::Config
+		+ pallet_nonfungible::Config
+		+ pallet_refungible::Config
+		+ pallet_balances_adapter::Config,
 {
 	Fungible(FungibleHandle<T>),
 	Nonfungible(NonfungibleHandle<T>),
 	Refungible(RefungibleHandle<T>),
+	NativeFungible(NativeFungibleHandle<T>),
 }
+
 impl<T> CollectionDispatch<T> for CollectionDispatchT<T>
 where
 	T: pallet_common::Config
 		+ pallet_unique::Config
 		+ pallet_fungible::Config
 		+ pallet_nonfungible::Config
-		+ pallet_refungible::Config,
+		+ pallet_refungible::Config
+		+ pallet_balances_adapter::Config,
 {
+	fn check_is_internal(&self) -> DispatchResult {
+		match self {
+			Self::Fungible(h) => h.check_is_internal(),
+			Self::Nonfungible(h) => h.check_is_internal(),
+			Self::Refungible(h) => h.check_is_internal(),
+			Self::NativeFungible(h) => h.check_is_internal(),
+		}
+	}
+
 	fn create(
 		sender: T::CrossAccountId,
 		payer: T::CrossAccountId,
@@ -83,7 +99,13 @@
 		Ok(id)
 	}
 
-	fn destroy(sender: T::CrossAccountId, collection: CollectionHandle<T>) -> DispatchResult {
+	fn destroy(sender: T::CrossAccountId, collection_id: CollectionId) -> DispatchResult {
+		if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+			fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+		}
+
+		let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+
 		match collection.mode {
 			CollectionMode::ReFungible => {
 				PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?
@@ -98,20 +120,19 @@
 		Ok(())
 	}
 
-	fn dispatch(handle: CollectionHandle<T>) -> Self {
-		match handle.mode {
+	fn dispatch(collection_id: CollectionId) -> Result<Self, DispatchError> {
+		if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+			return Ok(Self::NativeFungible(
+				NativeFungibleHandle::new_with_gas_limit(u64::MAX),
+			));
+		}
+
+		let handle = <CollectionHandle<T>>::try_get(collection_id)?;
+		Ok(match handle.mode {
 			CollectionMode::Fungible(_) => Self::Fungible(FungibleHandle::cast(handle)),
 			CollectionMode::NFT => Self::Nonfungible(NonfungibleHandle::cast(handle)),
 			CollectionMode::ReFungible => Self::Refungible(RefungibleHandle::cast(handle)),
-		}
-	}
-
-	fn into_inner(self) -> CollectionHandle<T> {
-		match self {
-			Self::Fungible(f) => f.into_inner(),
-			Self::Nonfungible(f) => f.into_inner(),
-			Self::Refungible(f) => f.into_inner(),
-		}
+		})
 	}
 
 	fn as_dyn(&self) -> &dyn CommonCollectionOperations<T> {
@@ -119,6 +140,7 @@
 			Self::Fungible(h) => h,
 			Self::Nonfungible(h) => h,
 			Self::Refungible(h) => h,
+			Self::NativeFungible(h) => h,
 		}
 	}
 }
@@ -129,7 +151,8 @@
 		+ pallet_unique::Config
 		+ pallet_fungible::Config
 		+ pallet_nonfungible::Config
-		+ pallet_refungible::Config,
+		+ pallet_refungible::Config
+		+ pallet_balances_adapter::Config,
 	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,
 {
 	fn is_reserved(target: &H160) -> bool {
@@ -166,14 +189,19 @@
 	}
 	fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
 		if let Some(collection_id) = map_eth_to_id(&handle.code_address()) {
-			let collection =
-				<CollectionHandle<T>>::new_with_gas_limit(collection_id, handle.remaining_gas())?;
-			let dispatched = Self::dispatch(collection);
+			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+				<NativeFungibleHandle<T>>::new_with_gas_limit(handle.remaining_gas()).call(handle)
+			} else {
+				let collection = <CollectionHandle<T>>::new_with_gas_limit(
+					collection_id,
+					handle.remaining_gas(),
+				)?;
 
-			match dispatched {
-				Self::Fungible(h) => h.call(handle),
-				Self::Nonfungible(h) => h.call(handle),
-				Self::Refungible(h) => h.call(handle),
+				match collection.mode {
+					CollectionMode::Fungible(_) => FungibleHandle::cast(collection).call(handle),
+					CollectionMode::NFT => NonfungibleHandle::cast(collection).call(handle),
+					CollectionMode::ReFungible => RefungibleHandle::cast(collection).call(handle),
+				}
 			}
 		} else if let Some((collection_id, token_id)) =
 			<T as pallet_common::Config>::EvmTokenAddressMapping::address_to_token(
modifiedruntime/common/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -17,7 +17,7 @@
 #[macro_export]
 macro_rules! dispatch_unique_runtime {
 	($collection:ident.$method:ident($($name:ident),*) $($rest:tt)*) => {{
-		let collection = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch(<pallet_common::CollectionHandle<Runtime>>::try_get($collection)?);
+		let collection = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch($collection)?;
 		let dispatch = collection.as_dyn();
 
 		Ok::<_, DispatchError>(dispatch.$method($($name),*) $($rest)*)
modifiedruntime/common/weights/mod.rsdiffbeforeafterboth
--- a/runtime/common/weights/mod.rs
+++ b/runtime/common/weights/mod.rs
@@ -18,6 +18,9 @@
 use frame_support::{weights::Weight};
 use pallet_common::{CommonWeightInfo, dispatch::dispatch_weight, RefungibleExtensionsWeightInfo};
 
+use pallet_balances_adapter::{
+	Config as NativeFungibleConfig, common::CommonWeights as NativeFungibleWeights,
+};
 use pallet_fungible::{Config as FungibleConfig, common::CommonWeights as FungibleWeights};
 use pallet_nonfungible::{Config as NonfungibleConfig, common::CommonWeights as NonfungibleWeights};
 
@@ -32,6 +35,7 @@
 macro_rules! max_weight_of {
 	($method:ident ( $($args:tt)* )) => {{
 		let max_weight = <FungibleWeights<T>>::$method($($args)*)
+			.max(<NativeFungibleWeights<T>>::$method($($args)*))
 			.max(<NonfungibleWeights<T>>::$method($($args)*));
 
 		#[cfg(feature = "refungible")]
@@ -42,16 +46,22 @@
 }
 
 #[cfg(not(feature = "refungible"))]
-pub trait CommonWeightConfigs: FungibleConfig + NonfungibleConfig {}
+pub trait CommonWeightConfigs: FungibleConfig + NativeFungibleConfig + NonfungibleConfig {}
 
 #[cfg(not(feature = "refungible"))]
-impl<T: FungibleConfig + NonfungibleConfig> CommonWeightConfigs for T {}
+impl<T: FungibleConfig + NativeFungibleConfig + NonfungibleConfig> CommonWeightConfigs for T {}
 
 #[cfg(feature = "refungible")]
-pub trait CommonWeightConfigs: FungibleConfig + NonfungibleConfig + RefungibleConfig {}
+pub trait CommonWeightConfigs:
+	FungibleConfig + NativeFungibleConfig + NonfungibleConfig + RefungibleConfig
+{
+}
 
 #[cfg(feature = "refungible")]
-impl<T: FungibleConfig + NonfungibleConfig + RefungibleConfig> CommonWeightConfigs for T {}
+impl<T: FungibleConfig + NativeFungibleConfig + NonfungibleConfig + RefungibleConfig>
+	CommonWeightConfigs for T
+{
+}
 
 pub struct CommonWeights<T>(PhantomData<T>);
 
modifiedruntime/opal/Cargo.tomldiffbeforeafterboth
--- a/runtime/opal/Cargo.toml
+++ b/runtime/opal/Cargo.toml
@@ -18,9 +18,10 @@
 [features]
 default = ['opal-runtime', 'std']
 limit-testing = ['pallet-unique/limit-testing', 'up-data-structs/limit-testing']
-opal-runtime = ['app-promotion', 'collator-selection', 'foreign-assets', 'preimage', 'pallet-test-utils', 'refungible']
+opal-runtime = ['app-promotion', 'collator-selection', 'foreign-assets', 'pallet-test-utils', 'preimage', 'refungible']
 pov-estimate = []
 runtime-benchmarks = [
+	"pallet-preimage/runtime-benchmarks",
 	'cumulus-pallet-parachain-system/runtime-benchmarks',
 	'frame-benchmarking',
 	'frame-support/runtime-benchmarks',
@@ -41,7 +42,6 @@
 	'pallet-inflation/runtime-benchmarks',
 	'pallet-maintenance/runtime-benchmarks',
 	'pallet-nonfungible/runtime-benchmarks',
-	"pallet-preimage/runtime-benchmarks",
 	'pallet-refungible/runtime-benchmarks',
 	'pallet-structure/runtime-benchmarks',
 	'pallet-timestamp/runtime-benchmarks',
@@ -70,8 +70,8 @@
 	# 'pallet-contracts-primitives/std',
 	# 'pallet-contracts-rpc-runtime-api/std',
 	# 'pallet-contract-helpers/std',
-	"pallet-preimage/std",
 	"pallet-authorship/std",
+	"pallet-preimage/std",
 	"pallet-session/std",
 	"sp-consensus-aura/std",
 	'app-promotion-rpc/std',
@@ -79,6 +79,7 @@
 	'fp-rpc/std',
 	'fp-self-contained/std',
 	'pallet-app-promotion/std',
+	'pallet-balances-adapter/std',
 	'pallet-base-fee/std',
 	'pallet-charge-transaction/std',
 	'pallet-collator-selection/std',
@@ -128,8 +129,8 @@
 	"orml-tokens/std",
 	"orml-traits/std",
 	"orml-vesting/std",
-	"orml-xtokens/std",
 	"orml-xcm-support/std",
+	"orml-xtokens/std",
 	"pallet-foreign-assets/std",
 
 	'pallet-maintenance/std',
@@ -139,8 +140,8 @@
 	"pallet-authorship/try-runtime",
 	"pallet-collator-selection/try-runtime",
 	"pallet-identity/try-runtime",
+	"pallet-preimage/try-runtime",
 	"pallet-session/try-runtime",
-	"pallet-preimage/try-runtime",
 	'cumulus-pallet-aura-ext/try-runtime',
 	'cumulus-pallet-dmp-queue/try-runtime',
 	'cumulus-pallet-parachain-system/try-runtime',
@@ -158,6 +159,7 @@
 	'pallet-app-promotion/try-runtime',
 	'pallet-aura/try-runtime',
 	'pallet-balances/try-runtime',
+	'pallet-balances-adapter/try-runtime',
 	'pallet-base-fee/try-runtime',
 	'pallet-charge-transaction/try-runtime',
 	'pallet-common/try-runtime',
@@ -189,8 +191,8 @@
 app-promotion = []
 collator-selection = []
 foreign-assets = []
-preimage = []
 pallet-test-utils = []
+preimage = []
 refungible = []
 scheduler = []
 
@@ -216,8 +218,8 @@
 orml-tokens = { workspace = true }
 orml-traits = { workspace = true }
 orml-vesting = { workspace = true }
+orml-xcm-support = { workspace = true }
 orml-xtokens = { workspace = true }
-orml-xcm-support = { workspace = true }
 pallet-aura = { workspace = true }
 pallet-authorship = { workspace = true }
 pallet-balances = { features = ["insecure_zero_ed"], workspace = true }
@@ -258,6 +260,7 @@
 log = { workspace = true }
 num_enum = { workspace = true }
 pallet-app-promotion = { workspace = true }
+pallet-balances-adapter = { workspace = true }
 pallet-base-fee = { workspace = true }
 pallet-charge-transaction = { workspace = true }
 pallet-collator-selection = { workspace = true }
@@ -291,11 +294,11 @@
 ################################################################################
 # Optional dependencies
 
-frame-system-benchmarking = { workspace = true, optional = true}
 frame-benchmarking = { workspace = true, optional = true }
+frame-system-benchmarking = { workspace = true, optional = true }
 frame-try-runtime = { workspace = true, optional = true }
+hex-literal = { workspace = true, optional = true }
 serde = { workspace = true, optional = true }
-hex-literal = { workspace = true, optional = true }
 
 ################################################################################
 # Test dependencies
modifiedruntime/opal/src/lib.rsdiffbeforeafterboth
--- a/runtime/opal/src/lib.rs
+++ b/runtime/opal/src/lib.rs
@@ -45,6 +45,7 @@
 
 pub const RUNTIME_NAME: &str = "opal";
 pub const TOKEN_SYMBOL: &str = "OPL";
+pub const DECIMALS: u8 = 18;
 
 /// This runtime version.
 pub const VERSION: RuntimeVersion = RuntimeVersion {
modifiedruntime/quartz/Cargo.tomldiffbeforeafterboth
--- a/runtime/quartz/Cargo.toml
+++ b/runtime/quartz/Cargo.toml
@@ -22,6 +22,7 @@
 pov-estimate = []
 quartz-runtime = ['app-promotion', 'collator-selection', 'foreign-assets', 'preimage', 'refungible']
 runtime-benchmarks = [
+	"pallet-preimage/runtime-benchmarks",
 	'cumulus-pallet-parachain-system/runtime-benchmarks',
 	'frame-benchmarking',
 	'frame-support/runtime-benchmarks',
@@ -42,7 +43,6 @@
 	'pallet-inflation/runtime-benchmarks',
 	'pallet-maintenance/runtime-benchmarks',
 	'pallet-nonfungible/runtime-benchmarks',
-	"pallet-preimage/runtime-benchmarks",
 	'pallet-refungible/runtime-benchmarks',
 	'pallet-structure/runtime-benchmarks',
 	'pallet-timestamp/runtime-benchmarks',
@@ -66,13 +66,14 @@
 	'frame-try-runtime/std',
 	'pallet-aura/std',
 	'pallet-balances/std',
+	'pallet-balances-adapter/std',
 	# 'pallet-contracts/std',
 	# 'pallet-contracts-primitives/std',
 	# 'pallet-contracts-rpc-runtime-api/std',
 	# 'pallet-contract-helpers/std',
-	"pallet-preimage/std",
 	"pallet-authorship/std",
 	"pallet-identity/std",
+	"pallet-preimage/std",
 	"pallet-session/std",
 	"sp-consensus-aura/std",
 	'app-promotion-rpc/std',
@@ -127,8 +128,8 @@
 	"orml-tokens/std",
 	"orml-traits/std",
 	"orml-vesting/std",
+	"orml-xcm-support/std",
 	"orml-xtokens/std",
-	"orml-xcm-support/std",
 	"pallet-foreign-assets/std",
 	"pallet-maintenance/std",
 ]
@@ -136,8 +137,8 @@
 	"pallet-authorship/try-runtime",
 	"pallet-collator-selection/try-runtime",
 	"pallet-identity/try-runtime",
+	"pallet-preimage/try-runtime",
 	"pallet-session/try-runtime",
-	"pallet-preimage/try-runtime",
 	'cumulus-pallet-aura-ext/try-runtime',
 	'cumulus-pallet-dmp-queue/try-runtime',
 	'cumulus-pallet-parachain-system/try-runtime',
@@ -154,6 +155,7 @@
 	'pallet-app-promotion/try-runtime',
 	'pallet-aura/try-runtime',
 	'pallet-balances/try-runtime',
+	'pallet-balances-adapter/try-runtime',
 	'pallet-charge-transaction/try-runtime',
 	'pallet-common/try-runtime',
 	'pallet-configuration/try-runtime',
@@ -208,8 +210,8 @@
 orml-tokens = { workspace = true }
 orml-traits = { workspace = true }
 orml-vesting = { workspace = true }
+orml-xcm-support = { workspace = true }
 orml-xtokens = { workspace = true }
-orml-xcm-support = { workspace = true }
 pallet-aura = { workspace = true }
 pallet-authorship = { workspace = true }
 pallet-balances = { features = ["insecure_zero_ed"], workspace = true }
@@ -246,6 +248,7 @@
 fp-evm = { workspace = true }
 log = { workspace = true }
 pallet-app-promotion = { workspace = true }
+pallet-balances-adapter = { workspace = true }
 pallet-collator-selection = { workspace = true }
 pallet-common = { workspace = true }
 pallet-configuration = { workspace = true }
@@ -283,11 +286,11 @@
 ################################################################################
 # Optional dependencies
 
+frame-benchmarking = { workspace = true, optional = true }
 frame-system-benchmarking = { workspace = true, optional = true }
-frame-benchmarking = { workspace = true, optional = true }
 frame-try-runtime = { workspace = true, optional = true }
+hex-literal = { workspace = true, optional = true }
 serde = { workspace = true, optional = true }
-hex-literal = { workspace = true, optional = true }
 
 
 ################################################################################
modifiedruntime/quartz/src/lib.rsdiffbeforeafterboth
--- a/runtime/quartz/src/lib.rs
+++ b/runtime/quartz/src/lib.rs
@@ -48,6 +48,7 @@
 #[cfg(not(feature = "become-sapphire"))]
 pub const RUNTIME_NAME: &str = "quartz";
 pub const TOKEN_SYMBOL: &str = "QTZ";
+pub const DECIMALS: u8 = 18;
 
 /// This runtime version.
 pub const VERSION: RuntimeVersion = RuntimeVersion {
modifiedruntime/tests/Cargo.tomldiffbeforeafterboth
--- a/runtime/tests/Cargo.toml
+++ b/runtime/tests/Cargo.toml
@@ -26,6 +26,7 @@
 pallet-ethereum = { workspace = true }
 pallet-evm = { workspace = true }
 
+pallet-balances-adapter = { workspace = true }
 pallet-common = { workspace = true }
 pallet-fungible = { workspace = true }
 pallet-nonfungible = { workspace = true }
@@ -41,5 +42,3 @@
 evm-coder = { workspace = true }
 up-sponsorship = { workspace = true }
 xcm = { workspace = true }
-pallet-xcm = { workspace = true }
-pallet-configuration = { workspace = true }
modifiedruntime/tests/src/lib.rsdiffbeforeafterboth
--- a/runtime/tests/src/lib.rs
+++ b/runtime/tests/src/lib.rs
@@ -19,7 +19,7 @@
 use sp_core::{H160, H256, U256};
 use frame_support::{
 	parameter_types,
-	traits::{Everything, ConstU32, ConstU64},
+	traits::{Everything, ConstU32, ConstU64, Currency},
 	weights::IdentityFee,
 	pallet_prelude::Weight,
 };
@@ -266,6 +266,19 @@
 impl pallet_nonfungible::Config for Test {
 	type WeightInfo = ();
 }
+parameter_types! {
+	pub const Decimals: u8 = 18;
+	pub Name: String = "Test".to_string();
+	pub Symbol: String = "TST".to_string();
+}
+impl pallet_balances_adapter::Config for Test {
+	type Currency = Balances;
+	type CurrencyBalance = <Balances as Currency<Self::AccountId>>::Balance;
+	type Decimals = Decimals;
+	type Name = Name;
+	type Symbol = Symbol;
+	type WeightInfo = ();
+}
 
 parameter_types! {
 	// 0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f
modifiedruntime/unique/Cargo.tomldiffbeforeafterboth
--- a/runtime/unique/Cargo.toml
+++ b/runtime/unique/Cargo.toml
@@ -20,6 +20,7 @@
 limit-testing = ['pallet-unique/limit-testing', 'up-data-structs/limit-testing']
 pov-estimate = []
 runtime-benchmarks = [
+	"pallet-preimage/runtime-benchmarks",
 	'frame-benchmarking',
 	'frame-support/runtime-benchmarks',
 	'frame-system-benchmarking',
@@ -39,7 +40,6 @@
 	'pallet-inflation/runtime-benchmarks',
 	'pallet-maintenance/runtime-benchmarks',
 	'pallet-nonfungible/runtime-benchmarks',
-	"pallet-preimage/runtime-benchmarks",
 	'pallet-refungible/runtime-benchmarks',
 	'pallet-structure/runtime-benchmarks',
 	'pallet-timestamp/runtime-benchmarks',
@@ -68,9 +68,9 @@
 	# 'pallet-contracts-primitives/std',
 	# 'pallet-contracts-rpc-runtime-api/std',
 	# 'pallet-contract-helpers/std',
-	"pallet-preimage/std",
 	"pallet-authorship/std",
 	"pallet-identity/std",
+	"pallet-preimage/std",
 	"pallet-session/std",
 	"sp-consensus-aura/std",
 	'app-promotion-rpc/std',
@@ -78,6 +78,7 @@
 	'fp-rpc/std',
 	'fp-self-contained/std',
 	'pallet-app-promotion/std',
+	'pallet-balances-adapter/std',
 	'pallet-base-fee/std',
 	'pallet-charge-transaction/std',
 	'pallet-collator-selection/std',
@@ -124,8 +125,8 @@
 	"orml-tokens/std",
 	"orml-traits/std",
 	"orml-vesting/std",
+	"orml-xcm-support/std",
 	"orml-xtokens/std",
-	"orml-xcm-support/std",
 	"pallet-foreign-assets/std",
 	"pallet-maintenance/std",
 ]
@@ -134,8 +135,8 @@
 	"pallet-authorship/try-runtime",
 	"pallet-collator-selection/try-runtime",
 	"pallet-identity/try-runtime",
+	"pallet-preimage/try-runtime",
 	"pallet-session/try-runtime",
-	"pallet-preimage/try-runtime",
 	'cumulus-pallet-aura-ext/try-runtime',
 	'cumulus-pallet-dmp-queue/try-runtime',
 	'cumulus-pallet-parachain-system/try-runtime',
@@ -152,6 +153,7 @@
 	'pallet-app-promotion/try-runtime',
 	'pallet-aura/try-runtime',
 	'pallet-balances/try-runtime',
+	'pallet-balances-adapter/try-runtime',
 	'pallet-charge-transaction/try-runtime',
 	'pallet-common/try-runtime',
 	'pallet-configuration/try-runtime',
@@ -207,8 +209,8 @@
 orml-tokens = { workspace = true }
 orml-traits = { workspace = true }
 orml-vesting = { workspace = true }
+orml-xcm-support = { workspace = true }
 orml-xtokens = { workspace = true }
-orml-xcm-support = { workspace = true }
 pallet-aura = { workspace = true }
 pallet-authorship = { workspace = true }
 pallet-balances = { features = ["insecure_zero_ed"], workspace = true }
@@ -244,6 +246,7 @@
 derivative = { workspace = true }
 log = { workspace = true }
 pallet-app-promotion = { workspace = true }
+pallet-balances-adapter = { workspace = true }
 pallet-collator-selection = { workspace = true }
 pallet-common = { workspace = true }
 pallet-configuration = { workspace = true }
@@ -282,11 +285,11 @@
 ################################################################################
 # Optional dependencies
 
+frame-benchmarking = { workspace = true, optional = true }
 frame-system-benchmarking = { workspace = true, optional = true }
-frame-benchmarking = { workspace = true, optional = true }
 frame-try-runtime = { workspace = true, optional = true }
+hex-literal = { workspace = true, optional = true }
 serde = { workspace = true, optional = true }
-hex-literal = { workspace = true, optional = true }
 
 ################################################################################
 # Test dependencies
modifiedruntime/unique/src/lib.rsdiffbeforeafterboth
--- a/runtime/unique/src/lib.rs
+++ b/runtime/unique/src/lib.rs
@@ -45,6 +45,7 @@
 
 pub const RUNTIME_NAME: &str = "unique";
 pub const TOKEN_SYMBOL: &str = "UNQ";
+pub const DECIMALS: u8 = 18;
 
 /// This runtime version.
 pub const VERSION: RuntimeVersion = RuntimeVersion {
modifiedtests/.eslintrc.jsondiffbeforeafterboth
--- a/tests/.eslintrc.json
+++ b/tests/.eslintrc.json
@@ -86,6 +86,14 @@
         "array-bracket-spacing": "warn",
         "template-curly-spacing": "warn",
         "space-in-parens": "warn",
+        "arrow-body-style": ["warn", "as-needed"],
+        "no-restricted-syntax": [
+            "warn",
+            {
+                "message": "toHuman results in horrible, hard to debug conversions with no stability guarantees, use Codec/at least .toJson instead",
+                "selector": "MemberExpression > Identifier[name=\"toHuman\"]"
+            }
+        ],
         "@typescript-eslint/naming-convention": [
             "warn",
             {
modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -39,12 +39,14 @@
     "testParallelFull": "yarn testParallel && yarn testSequential",
     "testParallel": "yarn _testParallel './src/**/*.test.ts'",
     "testSequential": "yarn _test './src/**/*.seqtest.ts'",
-    "testStructure": "yarn setup && yarn _test ./**/nesting/*.*test.ts",
-    "testEth": "yarn setup && yarn _test './**/eth/**/*.*test.ts'",
-    "testEthNesting": "yarn setup && yarn _test './**/eth/nesting/**/*.*test.ts'",
-    "testEthFractionalizer": "yarn setup && yarn _test './**/eth/fractionalizer/**/*.*test.ts'",
-    "testEthMarketplace": "yarn setup && yarn _test './**/eth/marketplace/**/*.*test.ts'",
-    "testEvent": "yarn setup && yarn _test ./src/check-event/*.*test.ts",
+    "testStructure": "yarn _test ./**/nesting/*.*test.ts",
+    "testEth": "yarn _test './**/eth/**/*.*test.ts'",
+    "testEthNesting": "yarn _test './**/eth/nesting/**/*.*test.ts'",
+    "testEthFractionalizer": "yarn _test './**/eth/fractionalizer/**/*.*test.ts'",
+    "testEthMarketplace": "yarn _test './**/eth/marketplace/**/*.*test.ts'",
+    "testSub": "yarn _test './**/sub/**/*.*test.ts'",
+    "testSubNesting": "yarn _test './**/sub/nesting/**/*.*test.ts'",
+    "testEvent": "yarn _test ./src/check-event/*.*test.ts",
     "testEthPayable": "yarn _test './**/eth/payable.test.ts'",
     "testEvmCoder": "yarn _test './**/eth/evmCoder.test.ts'",
     "testNesting": "yarn _test ./**/nest.test.ts",
modifiedtests/src/addCollectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/addCollectionAdmin.test.ts
+++ b/tests/src/addCollectionAdmin.test.ts
@@ -16,6 +16,7 @@
 
 import {IKeyringPair} from '@polkadot/types/types';
 import {itSub, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
 
 describe('Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => {
   let donor: IKeyringPair;
@@ -82,7 +83,7 @@
 
   itSub("Can't add collection admin of not existing collection.", async ({helper}) => {
     const [alice, bob] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);
-    const collectionId = (1 << 32) - 1;
+    const collectionId = NON_EXISTENT_COLLECTION_ID;
 
     await expect(helper.collection.addAdmin(alice, collectionId, {Substrate: bob.address})).to.be.rejectedWith(/common\.CollectionNotFound/);
 
modifiedtests/src/allowLists.test.tsdiffbeforeafterboth
--- a/tests/src/allowLists.test.ts
+++ b/tests/src/allowLists.test.ts
@@ -16,7 +16,7 @@
 
 import {IKeyringPair} from '@polkadot/types/types';
 import {usingPlaygrounds, expect, itSub} from './util';
-import {ICollectionPermissions} from './util/playgrounds/types';
+import {ICollectionPermissions, NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
 
 describe('Integration Test ext. Allow list tests', () => {
   let alice: IKeyringPair;
@@ -60,7 +60,7 @@
 
   describe('Negative', () => {
     itSub('Nobody can add address to allow list of non-existing collection', async ({helper}) => {
-      const collectionId = (1<<32) - 1;
+      const collectionId = NON_EXISTENT_COLLECTION_ID;
       await expect(helper.nft.addToAllowList(bob, collectionId, {Substrate: charlie.address}))
         .to.be.rejectedWith(/common\.CollectionNotFound/);
     });
@@ -140,7 +140,7 @@
     });
 
     itSub('Nobody can remove address from allow list of non-existing collection', async ({helper}) => {
-      const collectionId = (1<<32) - 1;
+      const collectionId = NON_EXISTENT_COLLECTION_ID;
       await expect(helper.collection.removeFromAllowList(bob, collectionId, {Substrate: charlie.address}))
         .to.be.rejectedWith(/common\.CollectionNotFound/);
     });
modifiedtests/src/benchmarks/utils/common.tsdiffbeforeafterboth
--- a/tests/src/benchmarks/utils/common.ts
+++ b/tests/src/benchmarks/utils/common.ts
@@ -91,4 +91,4 @@
     await collection.setTokenPropertyPermissions(donor, permissions);
 
   return collection;
-}
\ No newline at end of file
+}
modifiedtests/src/change-collection-owner.test.tsdiffbeforeafterboth
--- a/tests/src/change-collection-owner.test.ts
+++ b/tests/src/change-collection-owner.test.ts
@@ -16,6 +16,7 @@
 
 import {IKeyringPair} from '@polkadot/types/types';
 import {usingPlaygrounds, expect, itSub} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
 
 describe('Integration Test changeCollectionOwner(collection_id, new_owner):', () => {
   let alice: IKeyringPair;
@@ -127,7 +128,7 @@
   });
 
   itSub('Can\'t change owner of a non-existing collection.', async ({helper}) => {
-    const collectionId = (1 << 32) - 1;
+    const collectionId = NON_EXISTENT_COLLECTION_ID;
     const changeOwnerTx = () => helper.collection.changeOwner(bob, collectionId, bob.address);
     await expect(changeOwnerTx()).to.be.rejectedWith(/common\.CollectionNotFound/);
   });
modifiedtests/src/confirmSponsorship.test.tsdiffbeforeafterboth
--- a/tests/src/confirmSponsorship.test.ts
+++ b/tests/src/confirmSponsorship.test.ts
@@ -16,6 +16,7 @@
 
 import {IKeyringPair} from '@polkadot/types/types';
 import {usingPlaygrounds, expect, itSub, Pallets} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
 
 async function setSponsorHelper(collection: any, signer: IKeyringPair, sponsorAddress: string) {
   await collection.setSponsor(signer, sponsorAddress);
@@ -198,7 +199,7 @@
   });
 
   itSub('(!negative test!) Confirm sponsorship for a collection that never existed', async ({helper}) => {
-    const collectionId = (1 << 32) - 1;
+    const collectionId = NON_EXISTENT_COLLECTION_ID;
     const confirmSponsorshipTx = () => helper.collection.confirmSponsorship(bob, collectionId);
     await expect(confirmSponsorshipTx()).to.be.rejectedWith(/common\.CollectionNotFound/);
   });
modifiedtests/src/creditFeesToTreasury.seqtest.tsdiffbeforeafterboth
--- a/tests/src/creditFeesToTreasury.seqtest.ts
+++ b/tests/src/creditFeesToTreasury.seqtest.ts
@@ -88,7 +88,7 @@
     expect(treasuryIncrease).to.be.equal(fee);
   });
 
-  itSub.only('Treasury balance increased by failed tx fee', async ({helper}) => {
+  itSub('Treasury balance increased by failed tx fee', async ({helper}) => {
     const api = helper.getApi();
     await helper.wait.newBlocks(1);
 
addedtests/src/eth/abi/nativeFungible.jsondiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/abi/nativeFungible.json
@@ -0,0 +1,201 @@
+[
+  {
+    "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": [
+      {
+        "components": [
+          { "internalType": "address", "name": "eth", "type": "address" },
+          { "internalType": "uint256", "name": "sub", "type": "uint256" }
+        ],
+        "internalType": "struct CrossAddress",
+        "name": "owner",
+        "type": "tuple"
+      }
+    ],
+    "name": "balanceOfCross",
+    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "decimals",
+    "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "name",
+    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+    "stateMutability": "view",
+    "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": [
+      {
+        "components": [
+          { "internalType": "address", "name": "eth", "type": "address" },
+          { "internalType": "uint256", "name": "sub", "type": "uint256" }
+        ],
+        "internalType": "struct CrossAddress",
+        "name": "to",
+        "type": "tuple"
+      },
+      { "internalType": "uint256", "name": "amount", "type": "uint256" }
+    ],
+    "name": "transferCross",
+    "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"
+  },
+  {
+    "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "eth", "type": "address" },
+          { "internalType": "uint256", "name": "sub", "type": "uint256" }
+        ],
+        "internalType": "struct CrossAddress",
+        "name": "from",
+        "type": "tuple"
+      },
+      {
+        "components": [
+          { "internalType": "address", "name": "eth", "type": "address" },
+          { "internalType": "uint256", "name": "sub", "type": "uint256" }
+        ],
+        "internalType": "struct CrossAddress",
+        "name": "to",
+        "type": "tuple"
+      },
+      { "internalType": "uint256", "name": "amount", "type": "uint256" }
+    ],
+    "name": "transferFromCross",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  }
+]
addedtests/src/eth/api/UniqueNativeFungible.soldiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/api/UniqueNativeFungible.sol
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: OTHER
+// This code is automatically generated
+
+pragma solidity >=0.8.0 <0.9.0;
+
+/// @dev common stubs holder
+interface Dummy {
+
+}
+
+interface ERC165 is Dummy {
+	function supportsInterface(bytes4 interfaceID) external view returns (bool);
+}
+
+/// @dev the ERC-165 identifier for this interface is 0x1313556c
+interface ERC20UniqueExtensions is Dummy, ERC165 {
+	/// @dev EVM selector for this function is: 0xec069398,
+	///  or in textual repr: balanceOfCross((address,uint256))
+	function balanceOfCross(CrossAddress memory owner) external view returns (uint256);
+
+	/// @dev EVM selector for this function is: 0x2ada85ff,
+	///  or in textual repr: transferCross((address,uint256),uint256)
+	function transferCross(CrossAddress memory to, uint256 amount) external returns (bool);
+
+	/// @dev EVM selector for this function is: 0xd5cf430b,
+	///  or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+	function transferFromCross(
+		CrossAddress memory from,
+		CrossAddress memory to,
+		uint256 amount
+	) external returns (bool);
+}
+
+/// Cross account struct
+struct CrossAddress {
+	address eth;
+	uint256 sub;
+}
+
+/// @dev inlined interface
+interface ERC20Events {
+	event Transfer(address indexed from, address indexed to, uint256 value);
+	event Approval(address indexed owner, address indexed spender, uint256 value);
+}
+
+/// @dev the ERC-165 identifier for this interface is 0x942e8b22
+interface ERC20 is Dummy, ERC165, ERC20Events {
+	/// @dev EVM selector for this function is: 0xdd62ed3e,
+	///  or in textual repr: allowance(address,address)
+	function allowance(address owner, address spender) external view returns (uint256);
+
+	/// @dev EVM selector for this function is: 0x095ea7b3,
+	///  or in textual repr: approve(address,uint256)
+	function approve(address spender, uint256 amount) external returns (bool);
+
+	/// @dev EVM selector for this function is: 0x70a08231,
+	///  or in textual repr: balanceOf(address)
+	function balanceOf(address owner) external view returns (uint256);
+
+	/// @dev EVM selector for this function is: 0x313ce567,
+	///  or in textual repr: decimals()
+	function decimals() external view returns (uint8);
+
+	/// @dev EVM selector for this function is: 0x06fdde03,
+	///  or in textual repr: name()
+	function name() external view returns (string memory);
+
+	/// @dev EVM selector for this function is: 0x95d89b41,
+	///  or in textual repr: symbol()
+	function symbol() external view returns (string memory);
+
+	/// @dev EVM selector for this function is: 0x18160ddd,
+	///  or in textual repr: totalSupply()
+	function totalSupply() external view returns (uint256);
+
+	/// @dev EVM selector for this function is: 0xa9059cbb,
+	///  or in textual repr: transfer(address,uint256)
+	function transfer(address to, uint256 amount) external returns (bool);
+
+	/// @dev EVM selector for this function is: 0x23b872dd,
+	///  or in textual repr: transferFrom(address,address,uint256)
+	function transferFrom(
+		address from,
+		address to,
+		uint256 amount
+	) external returns (bool);
+}
+
+interface UniqueNativeFungible is Dummy, ERC165, ERC20, ERC20UniqueExtensions {}
modifiedtests/src/eth/collectionSponsoring.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collectionSponsoring.test.ts
+++ b/tests/src/eth/collectionSponsoring.test.ts
@@ -18,7 +18,7 @@
 import {Pallets, requirePalletsOrSkip, usingPlaygrounds} from '../util/index';
 import {itEth, expect} from './util';
 
-describe.only('evm nft collection sponsoring', () => {
+describe('evm nft collection sponsoring', () => {
   let donor: IKeyringPair;
   let alice: IKeyringPair;
   let nominal: bigint;
@@ -319,7 +319,7 @@
   });
 });
 
-describe.only('evm RFT collection sponsoring', () => {
+describe('evm RFT collection sponsoring', () => {
   let donor: IKeyringPair;
   let alice: IKeyringPair;
   let nominal: bigint;
modifiedtests/src/eth/createFTCollection.seqtest.tsdiffbeforeafterboth
--- a/tests/src/eth/createFTCollection.seqtest.ts
+++ b/tests/src/eth/createFTCollection.seqtest.ts
@@ -20,7 +20,7 @@
 
 const DECIMALS = 18;
 
-describe.only('Create FT collection from EVM', () => {
+describe('Create FT collection from EVM', () => {
   let donor: IKeyringPair;
 
   before(async function() {
modifiedtests/src/eth/createFTCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createFTCollection.test.ts
+++ b/tests/src/eth/createFTCollection.test.ts
@@ -22,7 +22,7 @@
 
 const DECIMALS = 18;
 
-describe.only('Create FT collection from EVM', () => {
+describe('Create FT collection from EVM', () => {
   let donor: IKeyringPair;
 
   before(async function() {
@@ -129,7 +129,7 @@
   });
 });
 
-describe.only('(!negative tests!) Create FT collection from EVM', () => {
+describe('(!negative tests!) Create FT collection from EVM', () => {
   let donor: IKeyringPair;
   let nominal: bigint;
 
modifiedtests/src/eth/createNFTCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createNFTCollection.test.ts
+++ b/tests/src/eth/createNFTCollection.test.ts
@@ -21,7 +21,7 @@
 import {COLLECTION_HELPER} from '../util';
 
 
-describe.only('Create NFT collection from EVM', () => {
+describe('Create NFT collection from EVM', () => {
   let donor: IKeyringPair;
 
   before(async function () {
@@ -143,7 +143,7 @@
   });
 });
 
-describe.only('(!negative tests!) Create NFT collection from EVM', () => {
+describe('(!negative tests!) Create NFT collection from EVM', () => {
   let donor: IKeyringPair;
   let nominal: bigint;
 
modifiedtests/src/eth/createRFTCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createRFTCollection.test.ts
+++ b/tests/src/eth/createRFTCollection.test.ts
@@ -21,7 +21,7 @@
 import {CollectionLimitField} from './util/playgrounds/types';
 
 
-describe.only('Create RFT collection from EVM', () => {
+describe('Create RFT collection from EVM', () => {
   let donor: IKeyringPair;
 
   before(async function() {
@@ -154,7 +154,7 @@
   });
 });
 
-describe.only('(!negative tests!) Create RFT collection from EVM', () => {
+describe('(!negative tests!) Create RFT collection from EVM', () => {
   let donor: IKeyringPair;
   let nominal: bigint;
 
modifiedtests/src/eth/fractionalizer/fractionalizer.test.tsdiffbeforeafterboth
--- a/tests/src/eth/fractionalizer/fractionalizer.test.ts
+++ b/tests/src/eth/fractionalizer/fractionalizer.test.ts
@@ -80,7 +80,7 @@
 };
 
 
-describe.only('Fractionalizer contract usage', () => {
+describe('Fractionalizer contract usage', () => {
   let donor: IKeyringPair;
 
   before(async function() {
addedtests/src/eth/nativeFungible.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/nativeFungible.test.ts
@@ -0,0 +1,173 @@
+// Copyright 2019-2023 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 {IKeyringPair} from '@polkadot/types/types';
+import {expect, itEth, usingEthPlaygrounds} from './util';
+import {UniqueHelper} from '../util/playgrounds/unique';
+
+describe('NativeFungible: ERC20 calls', () => {
+  let donor: IKeyringPair;
+
+  before(async function() {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      donor = await privateKey({url: import.meta.url});
+    });
+  });
+
+  itEth('approve()', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const spender = helper.eth.createAccount();
+    const collectionAddress = helper.ethAddress.fromCollectionId(0);
+    const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+    await expect(contract.methods.approve(spender, 100).call({from: owner})).to.be.rejectedWith('Approve not supported');
+  });
+
+  itEth('balanceOf()', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor, 123n);
+    const collectionAddress = helper.ethAddress.fromCollectionId(0);
+    const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+    const balance = await contract.methods.balanceOf(owner).call({from: owner});
+    expect(balance).to.be.eq('123000000000000000000');
+  });
+
+  itEth('decimals()', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const collectionAddress = helper.ethAddress.fromCollectionId(0);
+    const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+    const realDecimals = (await helper.chain.getChainProperties().tokenDecimals)[0].toString();
+    const decimals = await contract.methods.decimals().call({from: owner});
+    expect(decimals).to.be.eq(realDecimals);
+  });
+
+  itEth('name()', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const collectionAddress = helper.ethAddress.fromCollectionId(0);
+    const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+    const realName = await UniqueHelper.detectNetwork(helper.getApi());
+    const name = await contract.methods.name().call({from: owner});
+    expect(name).to.be.eq(realName);
+  });
+
+  itEth('symbol()', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const collectionAddress = helper.ethAddress.fromCollectionId(0);
+    const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+    const realName = (await helper.chain.getChainProperties().tokenSymbol)[0];
+    const name = await contract.methods.symbol().call({from: owner});
+    expect(name).to.be.eq(realName);
+  });
+
+  itEth('totalSupply()', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const collectionAddress = helper.ethAddress.fromCollectionId(0);
+    const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+    const totalSupplyEth = BigInt(await contract.methods.totalSupply().call({from: owner}));
+    const totalSupplySub = await helper.balance.getTotalIssuance();
+    expect(totalSupplyEth).to.be.eq(totalSupplySub);
+  });
+
+  itEth('transfer()', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const receiver = await helper.eth.createAccountWithBalance(donor);
+    const collectionAddress = helper.ethAddress.fromCollectionId(0);
+    const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+    const balanceOwnerBefore = await helper.balance.getEthereum(owner);
+    const balanceReceiverBefore = await helper.balance.getEthereum(receiver);
+
+    await contract.methods.transfer(receiver, 50).send({from: owner});
+
+    const balanceOwnerAfter = await helper.balance.getEthereum(owner);
+    const balanceReceiverAfter = await helper.balance.getEthereum(receiver);
+
+    expect(balanceOwnerBefore - 50n > balanceOwnerAfter).to.be.true;
+    expect(balanceReceiverBefore + 50n).to.be.equal(balanceReceiverAfter);
+  });
+
+  itEth('transferFrom()', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const receiver = await helper.eth.createAccountWithBalance(donor);
+    const collectionAddress = helper.ethAddress.fromCollectionId(0);
+    const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+    const balanceOwnerBefore = await helper.balance.getEthereum(owner);
+    const balanceReceiverBefore = await helper.balance.getEthereum(receiver);
+
+    await contract.methods.transferFrom(owner, receiver, 50).send({from: owner});
+
+    const balanceOwnerAfter = await helper.balance.getEthereum(owner);
+    const balanceReceiverAfter = await helper.balance.getEthereum(receiver);
+
+    expect(balanceOwnerBefore - 50n > balanceOwnerAfter).to.be.true;
+    expect(balanceReceiverBefore === balanceReceiverAfter - 50n).to.be.true;
+
+    await expect(contract.methods.transferFrom(receiver, receiver, 50).call({from: owner})).to.be.rejectedWith('ApprovedValueTooLow');
+  });
+});
+
+describe('NativeFungible: ERC20UniqueExtensions calls', () => {
+  let donor: IKeyringPair;
+
+  before(async function() {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      donor = await privateKey({url: import.meta.url});
+    });
+  });
+
+  itEth('transferCross()', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const receiver = await helper.ethCrossAccount.createAccountWithBalance(donor);
+    const collectionAddress = helper.ethAddress.fromCollectionId(0);
+    const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+    const balanceOwnerBefore = await helper.balance.getEthereum(owner);
+    const balanceReceiverBefore = await helper.balance.getEthereum(receiver.eth);
+
+    await contract.methods.transferCross(receiver, 50).send({from: owner});
+
+    const balanceOwnerAfter = await helper.balance.getEthereum(owner);
+    const balanceReceiverAfter = await helper.balance.getEthereum(receiver.eth);
+
+    expect(balanceOwnerBefore - 50n > balanceOwnerAfter).to.be.true;
+    expect(balanceReceiverBefore === balanceReceiverAfter - 50n).to.be.true;
+  });
+
+  itEth('transferFromCross()', async ({helper}) => {
+    const owner = await helper.ethCrossAccount.createAccountWithBalance(donor);
+    const receiver = await helper.ethCrossAccount.createAccountWithBalance(donor);
+    const collectionAddress = helper.ethAddress.fromCollectionId(0);
+    const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner.eth);
+
+    const balanceOwnerBefore = await helper.balance.getEthereum(owner.eth);
+    const balanceReceiverBefore = await helper.balance.getEthereum(receiver.eth);
+
+    await contract.methods.transferFromCross(owner, receiver, 50).send({from: owner.eth});
+
+    const balanceOwnerAfter = await helper.balance.getEthereum(owner.eth);
+    const balanceReceiverAfter = await helper.balance.getEthereum(receiver.eth);
+
+    expect(balanceOwnerBefore - 50n > balanceOwnerAfter).to.be.true;
+    expect(balanceReceiverBefore === balanceReceiverAfter - 50n).to.be.true;
+
+    await expect(contract.methods.transferFromCross(receiver, receiver, 50).call({from: owner.eth})).to.be.rejectedWith('no permission');
+  });
+});
\ No newline at end of file
addedtests/src/eth/nativeRpc/estimateGas.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/nativeRpc/estimateGas.test.ts
@@ -0,0 +1,62 @@
+// 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, itEth, usingEthPlaygrounds} from '../util';
+import {IKeyringPair} from '@polkadot/types/types';
+
+
+describe('Ethereum native RPC calls', () => {
+  let donor: IKeyringPair;
+  const NATIVE_TOKEN_ADDRESS = '0x17c4e6453cc49aaaaeaca894e6d9683e00000000';
+
+  before(async function() {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      donor = await privateKey({url: import.meta.url});
+    });
+  });
+
+  itEth.skip('estimate gas', async ({helper}) => {
+    const BALANCE = 100n;
+    const BALANCE_TO_TRANSFER = 90n;
+
+    const owner = await helper.eth.createAccountWithBalance(donor, BALANCE);
+    const recepient = helper.eth.createAccount();
+
+    const web3 = helper.getWeb3();
+    // data: transfer(recepient, 90);
+    const data = web3.eth.abi.encodeFunctionCall({
+      name: 'transfer',
+      type: 'function',
+      inputs: [{
+        type: 'address',
+        name: 'to',
+      },{
+        type: 'uint256',
+        name: 'amount',
+      }],
+    }, [recepient, (BALANCE_TO_TRANSFER * (10n ** 18n)).toString()]);
+
+    const estimateGas = await web3.eth.estimateGas({
+      to: NATIVE_TOKEN_ADDRESS,
+      value: '0x0',
+      data,
+      from: owner,
+      maxFeePerGas: '0x14c9338c61d',
+    });
+
+    expect(estimateGas).to.be.greaterThan(40000).and.to.be.lessThan(60000);
+  });
+});
modifiedtests/src/eth/nesting/nest.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nesting/nest.test.ts
+++ b/tests/src/eth/nesting/nest.test.ts
@@ -187,4 +187,84 @@
         .call()).to.be.rejectedWith('SourceCollectionIsNotAllowedToNest');
     });
   });
+
+  describe('Fungible', () => {
+    async function createFungibleCollection(helper: EthUniqueHelper, owner: string, mode: 'ft' | 'native ft') {
+      if (mode === 'ft') {
+        const {collectionAddress} = await helper.eth.createFungibleCollection(owner, '', 18, '', '');
+        const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+        await contract.methods.mint(owner, 100n).send({from: owner});
+        return {collectionAddress, contract};
+      }
+
+      // native ft
+      const collectionAddress = helper.ethAddress.fromCollectionId(0);
+      const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+      return {collectionAddress, contract};
+    }
+
+    [
+      {mode: 'ft' as const},
+      {mode: 'native ft' as const},
+    ].map(testCase => {
+      itEth(`Allow nest [${testCase.mode}]`, async ({helper}) => {
+        const owner = await helper.eth.createAccountWithBalance(donor);
+        const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);
+        const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);
+
+        const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});
+        const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;
+        const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);
+
+        await ftContract.methods.transfer(targetTokenAddress, 10n).send({from: owner});
+        expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('10');
+      });
+    });
+
+    [
+      {mode: 'ft' as const},
+      {mode: 'native ft' as const},
+    ].map(testCase => {
+      itEth(`Allow partial/full unnest [${testCase.mode}]`, async ({helper}) => {
+        const owner = await helper.eth.createAccountWithBalance(donor);
+        const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);
+        const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);
+
+        const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});
+        const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;
+        const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);
+
+        await ftContract.methods.transfer(targetTokenAddress, 10n).send({from: owner});
+
+        await ftContract.methods.transferFrom(targetTokenAddress, owner, 5n).send({from: owner});
+        expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('5');
+
+        await ftContract.methods.transferFrom(targetTokenAddress, owner, 5n).send({from: owner});
+        expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('0');
+      });
+    });
+
+    [
+      {mode: 'ft' as const},
+      {mode: 'native ft' as const},
+    ].map(testCase => {
+      itEth(`Disallow nest into collection without nesting permission [${testCase.mode}] (except for native fungible collection)`, async ({helper}) => {
+        const owner = await helper.eth.createAccountWithBalance(donor);
+        const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);
+        await targetContract.methods.setCollectionNesting(false).send({from: owner});
+
+        const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);
+
+        const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});
+        const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;
+        const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);
+
+        if (testCase.mode === 'ft') {
+          await expect(ftContract.methods.transfer(targetTokenAddress, 10n).call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');
+        } else {
+          await expect(ftContract.methods.transfer(targetTokenAddress, 10n).call({from: owner})).to.be.not.rejected;
+        }
+      });
+    });
+  });
 });
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -929,7 +929,7 @@
     });
   });
 
-  itEth.only('Returns collection name', async ({helper}) => {
+  itEth('Returns collection name', async ({helper}) => {
     // FIXME: should not have balance to use .call()
     const caller = await helper.eth.createAccountWithBalance(donor);
     const tokenPropertyPermissions = [{
modifiedtests/src/eth/payable.test.tsdiffbeforeafterboth
--- a/tests/src/eth/payable.test.ts
+++ b/tests/src/eth/payable.test.ts
@@ -41,7 +41,7 @@
     expect(await contract.methods.getCollected().call()).to.be.equal('10000');
   });
 
-  itEth.only('Evm contract can receive wei from substrate account', async ({helper}) => {
+  itEth('Evm contract can receive wei from substrate account', async ({helper}) => {
     const deployer = await helper.eth.createAccountWithBalance(donor);
     const contract = await helper.eth.deployCollectorContract(deployer);
     const [alice] = await helper.arrange.createAccounts([40n], donor);
modifiedtests/src/eth/reFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -679,7 +679,7 @@
     });
   });
 
-  itEth.only('Returns collection name', async ({helper}) => {
+  itEth('Returns collection name', async ({helper}) => {
     // FIXME: should not have balance to use .call()
     const caller = await helper.eth.createAccountWithBalance(alice);
     const tokenPropertyPermissions = [{
modifiedtests/src/eth/tokenProperties.test.tsdiffbeforeafterboth
--- a/tests/src/eth/tokenProperties.test.ts
+++ b/tests/src/eth/tokenProperties.test.ts
@@ -314,7 +314,7 @@
       expect(result.length).to.equal(0);
     }));
 
-  itEth.only('Can be read', async({helper}) => {
+  itEth('Can be read', async({helper}) => {
     // FIXME: User with no balance should be able to call
     const caller = await helper.eth.createAccountWithBalance(alice);
     const collection = await helper.nft.mintCollection(alice, {
modifiedtests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -22,6 +22,7 @@
 
 // Native contracts ABI
 import collectionHelpersAbi from '../../abi/collectionHelpers.json' assert {type: 'json'};
+import nativeFungibleAbi from '../../abi/nativeFungible.json' assert {type: 'json'};
 import fungibleAbi from '../../abi/fungible.json' assert {type: 'json'};
 import fungibleDeprecatedAbi from '../../abi/fungibleDeprecated.json' assert {type: 'json'};
 import nonFungibleAbi from '../../abi/nonFungible.json' assert {type: 'json'};
@@ -112,7 +113,6 @@
       data: object,
       from: signer,
       gas: gas ?? this.helper.eth.DEFAULT_GAS,
-      gasPrice: await this.getGasPrice(),
     });
     return await contract.deploy({data: object}).send({from: signer});
   }
@@ -121,30 +121,33 @@
 
 class NativeContractGroup extends EthGroupBase {
 
-  async contractHelpers(caller: string): Promise<Contract> {
+  contractHelpers(caller: string) {
     const web3 = this.helper.getWeb3();
     return new web3.eth.Contract(contractHelpersAbi as any, this.helper.getApi().consts.evmContractHelpers.contractAddress.toString(), {
       from: caller,
       gas: this.helper.eth.DEFAULT_GAS,
-      gasPrice: await this.getGasPrice(),
     });
   }
 
-  async collectionHelpers(caller: string) {
+  collectionHelpers(caller: string) {
     const web3 = this.helper.getWeb3();
     return new web3.eth.Contract(collectionHelpersAbi as any, this.helper.getApi().consts.common.contractAddress.toString(), {
       from: caller,
       gas: this.helper.eth.DEFAULT_GAS,
-      gasPrice: await this.getGasPrice(),
     });
   }
 
-  async collection(address: string, mode: TCollectionMode, caller?: string, mergeDeprecated = false) {
-    let abi = {
-      'nft': nonFungibleAbi,
-      'rft': refungibleAbi,
-      'ft': fungibleAbi,
-    }[mode];
+  collection(address: string, mode: TCollectionMode, caller?: string, mergeDeprecated = false) {
+    let abi;
+    if (address === this.helper.ethAddress.fromCollectionId(0)) {
+      abi = nativeFungibleAbi;
+    } else {
+      abi ={
+        'nft': nonFungibleAbi,
+        'rft': refungibleAbi,
+        'ft': fungibleAbi,
+      }[mode];
+    }
     if (mergeDeprecated) {
       const deprecated = {
         'nft': nonFungibleDeprecatedAbi,
@@ -156,7 +159,6 @@
     const web3 = this.helper.getWeb3();
     return new web3.eth.Contract(abi as any, address, {
       gas: this.helper.eth.DEFAULT_GAS,
-      gasPrice: await this.getGasPrice(),
       ...(caller ? {from: caller} : {}),
     });
   }
@@ -165,12 +167,11 @@
     return this.collection(this.helper.ethAddress.fromCollectionId(collectionId), mode, caller, mergeDeprecated);
   }
 
-  async rftToken(address: string, caller?: string, mergeDeprecated = false) {
+  rftToken(address: string, caller?: string, mergeDeprecated = false) {
     const web3 = this.helper.getWeb3();
     const abi = mergeDeprecated ? [...refungibleTokenAbi, ...refungibleTokenDeprecatedAbi] : refungibleTokenAbi;
     return new web3.eth.Contract(abi as any, address, {
       gas: this.helper.eth.DEFAULT_GAS,
-      gasPrice: await this.getGasPrice(),
       ...(caller ? {from: caller} : {}),
     });
   }
@@ -210,6 +211,7 @@
   async sendEVM(signer: IKeyringPair, contractAddress: string, abi: string, value: string, gasLimit?: number) {
     if (!gasLimit) gasLimit = this.DEFAULT_GAS;
     const web3 = this.helper.getWeb3();
+    // FIXME: can't send legacy transaction using tx.evm.call
     const gasPrice = await web3.eth.getGasPrice();
     // TODO: check execution status
     await this.helper.executeExtrinsic(
@@ -377,7 +379,7 @@
 class EthAddressGroup extends EthGroupBase {
   extractCollectionId(address: string): number {
     if (!(address.length === 42 || address.length === 40)) throw new Error('address wrong format');
-    return parseInt(address.substr(address.length - 8), 16);
+    return parseInt(address.slice(address.length - 8), 16);
   }
 
   fromCollectionId(collectionId: number): string {
modifiedtests/src/maintenance.seqtest.tsdiffbeforeafterboth
--- a/tests/src/maintenance.seqtest.ts
+++ b/tests/src/maintenance.seqtest.ts
@@ -325,7 +325,7 @@
       expect(await helper.preimage.getPreimageInfo(preimageHashes[0])).to.have.property('unrequested');
     });
 
-    itSub.only('Does not allow execution of a preimage that would fail', async ({helper}) => {
+    itSub('Does not allow execution of a preimage that would fail', async ({helper}) => {
       const [zeroAccount] = await helper.arrange.createAccounts([0n], superuser);
 
       const preimage = helper.constructApiCall('api.tx.balances.forceTransfer', [
addedtests/src/nativeFungible.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/nativeFungible.test.ts
@@ -0,0 +1,221 @@
+// Copyright 2019-2023 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 {IKeyringPair} from '@polkadot/types/types';
+import {expect, itSub, usingPlaygrounds} from './util';
+
+describe('Native fungible', () => {
+  let root: IKeyringPair;
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      root = await privateKey('//Alice');
+      const donor = await privateKey({url: import.meta.url});
+      [alice, bob] = await helper.arrange.createAccounts([10n, 10n], donor);
+    });
+  });
+
+  itSub('destroy_collection()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    await expect(collection.burn(alice)).to.be.rejectedWith('common.UnsupportedOperation');
+    await expect(helper.executeExtrinsic(alice, 'api.tx.unique.destroyCollection', [0])).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('add_to_allow_list()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    await expect(collection.addToAllowList(alice, {Substrate: bob.address})).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('remove_from_allow_list()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    await expect(collection.removeFromAllowList(alice, {Substrate: bob.address})).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('change_collection_owner()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    await expect(collection.changeOwner(alice, bob.address)).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('add_collection_admin()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    await expect(collection.addAdmin(alice, {Substrate: bob.address})).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('remove_collection_admin()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    await expect(collection.removeAdmin(alice, {Substrate: bob.address})).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('set_collection_sponsor()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    await expect(collection.setSponsor(alice, bob.address)).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('confirm_sponsorship()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    await expect(collection.confirmSponsorship(alice)).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('remove_collection_sponsor()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    await expect(collection.removeSponsor(alice)).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('create_item()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    await expect(collection.mint(alice, 100n, {Substrate: bob.address})).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('set_collection_properties()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    await expect(collection.setProperties(alice, [{key: 'value'}])).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('delete_collection_properties()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    await expect(collection.deleteProperties(alice, ['key'])).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('set_token_properties()', async ({helper}) => {
+    await expect(helper.executeExtrinsic(
+      alice,
+      'api.tx.unique.setTokenProperties',
+      [0, 0, [{key: 'value'}]],
+      true,
+    )).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('delete_token_properties()', async ({helper}) => {
+    await expect(helper.executeExtrinsic(
+      alice,
+      'api.tx.unique.deleteTokenProperties',
+      [0, 0, ['key']],
+      true,
+    )).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('set_transfers_enabled_flag()', async ({helper}) => {
+    await expect(helper.executeExtrinsic(
+      alice,
+      'api.tx.unique.setTransfersEnabledFlag',
+      [0, true],
+      true,
+    )).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('burn_item()', async ({helper}) => {
+    await expect(helper.executeExtrinsic(
+      alice,
+      'api.tx.unique.burnItem',
+      [0, 0, 100n],
+      true,
+    )).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('burn_from()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    await expect(collection.burnTokens(alice, 100n)).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('transfer()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    const balanceAliceBefore = await helper.balance.getSubstrate(alice.address);
+    const balanceBobBefore = await helper.balance.getSubstrate(bob.address);
+    await collection.transfer(alice, {Substrate: bob.address}, 100n);
+    const balanceAliceAfter = await helper.balance.getSubstrate(alice.address);
+    const balanceBobAfter = await helper.balance.getSubstrate(bob.address);
+    expect(balanceAliceBefore - balanceAliceAfter > 100n).to.be.true;
+    expect(balanceBobAfter - balanceBobBefore === 100n).to.be.true;
+  });
+
+  itSub('transfer_from()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    const balanceAliceBefore = await helper.balance.getSubstrate(alice.address);
+    const balanceBobBefore = await helper.balance.getSubstrate(bob.address);
+
+    await collection.transferFrom(alice, {Substrate: alice.address}, {Substrate: bob.address}, 100n);
+
+    const balanceAliceAfter = await helper.balance.getSubstrate(alice.address);
+    const balanceBobAfter = await helper.balance.getSubstrate(bob.address);
+    expect(balanceAliceBefore - balanceAliceAfter > 100n).to.be.true;
+    expect(balanceBobAfter - balanceBobBefore === 100n).to.be.true;
+
+    await expect(collection.transferFrom(alice, {Substrate: bob.address}, {Substrate: alice.address}, 100n)).to.be.rejectedWith('common.ApprovedValueTooLow');
+  });
+
+  itSub('approve()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    await expect(collection.approveTokens(alice, {Substrate: bob.address}, 100n)).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('approve_from()', async ({helper}) => {
+    await expect(helper.executeExtrinsic(
+      alice,
+      'api.tx.unique.approveFrom',
+      [{Substrate: alice.address}, {Substrate: bob.address}, 0, 0, 100n],
+      true,
+    )).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('set_collection_limits()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    await expect(collection.setLimits(alice, {accountTokenOwnershipLimit: 1})).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('set_collection_permissions()', async ({helper}) => {
+    const collection = helper.ft.getCollectionObject(0);
+    await expect(collection.setPermissions(alice, {access: 'AllowList'})).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('repartition()', async ({helper}) => {
+    await expect(helper.executeExtrinsic(
+      alice,
+      'api.tx.unique.repartition',
+      [0, 0, 100n],
+      true,
+    )).to.be.rejectedWith('unique.RepartitionCalledOnNonRefungibleCollection');
+  });
+
+  itSub('force_repair_collection()', async ({helper}) => {
+    await expect(helper.executeExtrinsic(
+      alice,
+      'api.tx.unique.forceRepairCollection',
+      [0],
+      true,
+    )).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('force_repair_item()', async ({helper}) => {
+    await expect(helper.getSudo().executeExtrinsic(
+      root,
+      'api.tx.unique.forceRepairItem',
+      [0, 0],
+      true,
+    )).to.be.rejectedWith('common.UnsupportedOperation');
+  });
+
+  itSub('Nest into NFT token()', async ({helper}) => {
+    const nftCollection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+    const targetToken = await nftCollection.mintToken(alice);
+
+    const collection = helper.ft.getCollectionObject(0);
+    await collection.transfer(alice, targetToken.nestingAccount(), 100n);
+
+    await collection.transferFrom(alice, targetToken.nestingAccount(), {Substrate: alice.address}, 100n);
+  });
+});
\ No newline at end of file
modifiedtests/src/nesting/unnest.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/unnest.test.ts
+++ b/tests/src/nesting/unnest.test.ts
@@ -47,6 +47,20 @@
     await expect(nestedToken.getOwner()).to.be.rejected;
   });
 
+  itSub('NativeFungible: allows the owner to successfully unnest a token', async ({helper}) => {
+    const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+    const targetToken = await collection.mintToken(alice);
+
+    const collectionFT = helper.ft.getCollectionObject(0);
+
+    // Nest
+    await collectionFT.transfer(alice, targetToken.nestingAccount(), 10n);
+    // Unnest
+    await expect(collectionFT.transferFrom(alice, targetToken.nestingAccount(), {Substrate: alice.address}, 9n), 'while unnesting').to.be.fulfilled;
+
+    expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(1n);
+  });
+
   itSub('Fungible: allows the owner to successfully unnest a token', async ({helper}) => {
     const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
     const targetToken = await collection.mintToken(alice);
modifiedtests/src/pallet-presence.test.tsdiffbeforeafterboth
--- a/tests/src/pallet-presence.test.ts
+++ b/tests/src/pallet-presence.test.ts
@@ -19,6 +19,7 @@
 // Pallets that must always be present
 const requiredPallets = [
   'balances',
+  'balancesadapter',
   'common',
   'timestamp',
   'transactionpayment',
modifiedtests/src/removeCollectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/removeCollectionAdmin.test.ts
+++ b/tests/src/removeCollectionAdmin.test.ts
@@ -16,6 +16,7 @@
 
 import {IKeyringPair} from '@polkadot/types/types';
 import {itSub, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
 
 describe('Integration Test removeCollectionAdmin(collection_id, account_id):', () => {
   let alice: IKeyringPair;
@@ -68,7 +69,7 @@
   });
 
   itSub('Can\'t remove collection admin from not existing collection', async ({helper}) => {
-    const collectionId = (1 << 32) - 1;
+    const collectionId = NON_EXISTENT_COLLECTION_ID;
 
     await expect(helper.collection.removeAdmin(alice, collectionId, {Substrate: bob.address}))
       .to.be.rejectedWith(/common\.CollectionNotFound/);
modifiedtests/src/removeCollectionSponsor.test.tsdiffbeforeafterboth
--- a/tests/src/removeCollectionSponsor.test.ts
+++ b/tests/src/removeCollectionSponsor.test.ts
@@ -16,6 +16,7 @@
 
 import {IKeyringPair} from '@polkadot/types/types';
 import {itSub, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
 
 describe('integration test: ext. removeCollectionSponsor():', () => {
   let donor: IKeyringPair;
@@ -91,7 +92,7 @@
   });
 
   itSub('(!negative test!) Remove sponsor for a collection that never existed', async ({helper}) => {
-    const collectionId = (1 << 32) - 1;
+    const collectionId = NON_EXISTENT_COLLECTION_ID;
     await expect(helper.collection.removeSponsor(alice, collectionId)).to.be.rejectedWith(/common\.CollectionNotFound/);
   });
 
modifiedtests/src/setCollectionLimits.test.tsdiffbeforeafterboth
--- a/tests/src/setCollectionLimits.test.ts
+++ b/tests/src/setCollectionLimits.test.ts
@@ -17,6 +17,7 @@
 // https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits
 import {IKeyringPair} from '@polkadot/types/types';
 import {itSub, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
 
 const accountTokenOwnershipLimit = 0;
 const sponsoredDataSize = 0;
@@ -110,7 +111,7 @@
   });
 
   itSub('execute setCollectionLimits for not exists collection', async ({helper}) => {
-    const nonExistentCollectionId = (1 << 32) - 1;
+    const nonExistentCollectionId = NON_EXISTENT_COLLECTION_ID;
     await expect(helper.collection.setLimits(
       alice,
       nonExistentCollectionId,
modifiedtests/src/setCollectionSponsor.test.tsdiffbeforeafterboth
--- a/tests/src/setCollectionSponsor.test.ts
+++ b/tests/src/setCollectionSponsor.test.ts
@@ -16,6 +16,7 @@
 
 import {IKeyringPair} from '@polkadot/types/types';
 import {itSub, usingPlaygrounds, expect, Pallets} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
 
 describe('integration test: ext. setCollectionSponsor():', () => {
   let alice: IKeyringPair;
@@ -105,7 +106,7 @@
   });
 
   itSub('(!negative test!) Add sponsor to a collection that never existed', async ({helper}) => {
-    const collectionId = (1 << 32) - 1;
+    const collectionId = NON_EXISTENT_COLLECTION_ID;
     await expect(helper.collection.setSponsor(alice, collectionId, bob.address))
       .to.be.rejectedWith(/common\.CollectionNotFound/);
   });
modifiedtests/src/setPermissions.test.tsdiffbeforeafterboth
--- a/tests/src/setPermissions.test.ts
+++ b/tests/src/setPermissions.test.ts
@@ -16,6 +16,7 @@
 
 import {IKeyringPair} from '@polkadot/types/types';
 import {itSub, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
 
 describe('Integration Test: Set Permissions', () => {
   let alice: IKeyringPair;
@@ -85,7 +86,7 @@
   });
 
   itSub('fails on not existing collection', async ({helper}) => {
-    const collectionId = (1 << 32) - 1;
+    const collectionId = NON_EXISTENT_COLLECTION_ID;
     await expect(helper.collection.setPermissions(alice, collectionId, {access: 'AllowList', mintMode: true}))
       .to.be.rejectedWith(/common\.CollectionNotFound/);
   });
modifiedtests/src/sub/appPromotion/appPromotion.test.tsdiffbeforeafterboth
--- a/tests/src/sub/appPromotion/appPromotion.test.ts
+++ b/tests/src/sub/appPromotion/appPromotion.test.ts
@@ -157,7 +157,8 @@
       await helper.staking.stake(staker, 1000n * nominal);
       await helper.staking.stake(staker, 199n * nominal);
       // check balances
-      expect(await helper.balance.getLocked(staker.address)).to.deep.eq([{id: 'ormlvest', amount: 200n * nominal, reasons: 'All'}, {id: 'appstake', amount: 1199n * nominal, reasons: 'All'}]);
+      expect(await helper.balance.getLocked(staker.address)).to.deep.eq([{id: 'ormlvest', amount: 200n * nominal, reasons: 'All'}]);
+      expect(await helper.balance.getFrozen(staker.address)).to.deep.eq([{id: 'appstakeappstake', amount: 1199n * nominal}]);
       expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, frozen: 1199n * nominal});
       expect(await helper.balance.getSubstrate(staker.address) / nominal).to.eq(1199n);
       expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.eq(1199n * nominal);
@@ -453,7 +454,7 @@
       const [_unstake1, unstake2] = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});
       await helper.wait.forParachainBlockNumber(unstake2.block);
 
-      expect(await helper.balance.getLocked(staker.address)).to.deep.eq([]);
+      expect(await helper.balance.getFrozen(staker.address)).to.deep.eq([]);
       expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, frozen: 0n});
       expect(await helper.balance.getSubstrate(staker.address) / nominal).to.eq(999n);
       expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.eq(0n);
modifiedtests/src/sub/nesting/admin.test.tsdiffbeforeafterboth
--- a/tests/src/sub/nesting/admin.test.ts
+++ b/tests/src/sub/nesting/admin.test.ts
@@ -17,7 +17,7 @@
 import {IKeyringPair} from '@polkadot/types/types';
 import {expect, itSub, usingPlaygrounds} from '../../util';
 
-describe('Collection admin', () => {
+describe('Nesting by collection admin', () => {
   let alice: IKeyringPair;
   let bob: IKeyringPair;
   let charlie: IKeyringPair;
@@ -84,4 +84,3 @@
     expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
   });
 });
-
modifiedtests/src/sub/nesting/common.test.tsdiffbeforeafterboth
--- a/tests/src/sub/nesting/common.test.ts
+++ b/tests/src/sub/nesting/common.test.ts
@@ -21,102 +21,144 @@
 let alice: IKeyringPair;
 let bob: IKeyringPair;
 
-before(async () => {
-  await usingPlaygrounds(async (helper, privateKey) => {
-    const donor = await privateKey({url: import.meta.url});
-    [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
+describe('Common nesting tests', () => {
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      const donor = await privateKey({url: import.meta.url});
+      [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
+    });
   });
-});
 
-[
-  {mode: 'nft' as const, restrictedMode: true, requiredPallets: []},
-  {mode: 'nft' as const, restrictedMode: false, requiredPallets: []},
-  {mode: 'rft' as const, restrictedMode: true, requiredPallets: [Pallets.ReFungible]},
-  {mode: 'rft' as const, restrictedMode: false, requiredPallets: [Pallets.ReFungible]},
-].map(testCase => {
-  itSub.ifWithPallets(`Token owner can nest ${testCase.mode.toUpperCase()} in NFT if "tokenOwner" permission set ${testCase.restrictedMode ? 'in restricted mode': ''}`, testCase.requiredPallets, async ({helper}) => {
-    // Only NFT can be target for nesting in
-    const targetNFTCollection = await helper.nft.mintCollection(alice);
-    const targetTokenBob = await targetNFTCollection.mintToken(alice, {Substrate: bob.address});
+  [
+    {mode: 'nft' as const, restrictedMode: true, requiredPallets: []},
+    {mode: 'nft' as const, restrictedMode: false, requiredPallets: []},
+    {mode: 'rft' as const, restrictedMode: true, requiredPallets: [Pallets.ReFungible]},
+    {mode: 'rft' as const, restrictedMode: false, requiredPallets: [Pallets.ReFungible]},
+  ].map(testCase => {
+    itSub.ifWithPallets(`Token owner can nest ${testCase.mode.toUpperCase()} in NFT if "tokenOwner" permission set ${testCase.restrictedMode ? 'in restricted mode': ''}`, testCase.requiredPallets, async ({helper}) => {
+      // Only NFT can be target for nesting in
+      const targetNFTCollection = await helper.nft.mintCollection(alice);
+      const targetTokenBob = await targetNFTCollection.mintToken(alice, {Substrate: bob.address});
+
+      const collectionForNesting = await helper[testCase.mode].mintCollection(bob);
+      // permissions should be set:
+      await targetNFTCollection.setPermissions(alice, {
+        nesting: {tokenOwner: true, restricted: testCase.restrictedMode ? [collectionForNesting.collectionId] : null},
+      });
 
-    const collectionForNesting = await helper[testCase.mode].mintCollection(bob);
-    // permissions should be set:
-    await targetNFTCollection.setPermissions(alice, {
-      nesting: {tokenOwner: true, restricted: testCase.restrictedMode ? [collectionForNesting.collectionId] : null},
+      // 1. Bob can immediately create nested token:
+      const nestedToken1 = testCase.mode === 'nft'
+        ? await (collectionForNesting as UniqueNFTCollection).mintToken(bob, targetTokenBob.nestingAccount())
+        : await (collectionForNesting as UniqueRFTCollection).mintToken(bob, 10n, targetTokenBob.nestingAccount());
+      expect(await nestedToken1.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
+      expect(await nestedToken1.getOwner()).to.be.deep.equal(targetTokenBob.nestingAccount().toLowerCase());
+
+      // 2. Bob can mint and nest token:
+      const nestedToken2 = await collectionForNesting.mintToken(bob);
+      await nestedToken2.nest(bob, targetTokenBob);
+      expect(await nestedToken2.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
+      expect(await nestedToken2.getOwner()).to.be.deep.equal(targetTokenBob.nestingAccount().toLowerCase());
     });
+  });
 
-    // 1. Bob can immediately create nested token:
-    const nestedToken1 = testCase.mode === 'nft'
-      ? await (collectionForNesting as UniqueNFTCollection).mintToken(bob, targetTokenBob.nestingAccount())
-      : await (collectionForNesting as UniqueRFTCollection).mintToken(bob, 10n, targetTokenBob.nestingAccount());
-    expect(await nestedToken1.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
-    expect(await nestedToken1.getOwner()).to.be.deep.equal(targetTokenBob.nestingAccount().toLowerCase());
 
-    // 2. Bob can mint and nest token:
-    const nestedToken2 = await collectionForNesting.mintToken(bob);
-    await nestedToken2.nest(bob, targetTokenBob);
-    expect(await nestedToken2.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
-    expect(await nestedToken2.getOwner()).to.be.deep.equal(targetTokenBob.nestingAccount().toLowerCase());
+  [
+    {restrictedMode: true},
+    {restrictedMode: false},
+  ].map(testCase => {
+    itSub(`Token owner can nest FT in NFT if "tokenOwner" permission set  ${testCase.restrictedMode ? 'in restricted mode': ''}`, async ({helper}) => {
+      // Only NFT allows nesting, permissions should be set:
+      const targetNFTCollection = await helper.nft.mintCollection(alice);
+      const targetTokenBob = await targetNFTCollection.mintToken(alice, {Substrate: bob.address});
+
+      const collectionForNesting = await helper.ft.mintCollection(bob);
+      // permissions should be set:
+      await targetNFTCollection.setPermissions(alice, {
+        nesting: {tokenOwner: true, restricted: testCase.restrictedMode ? [collectionForNesting.collectionId] : null},
+      });
+
+      // 1. Alice can immediately create nested tokens:
+      await collectionForNesting.mint(bob, 100n, targetTokenBob.nestingAccount());
+      expect(await collectionForNesting.getTop10Owners()).deep.eq([targetTokenBob.nestingAccount().toLowerCase()]);
+      expect(await collectionForNesting.getBalance(targetTokenBob.nestingAccount())).eq(100n);
+
+      // 2. Alice can mint and nest token:
+      await collectionForNesting.mint(bob, 100n);
+      await collectionForNesting.transfer(bob, targetTokenBob.nestingAccount(), 50n);
+      expect(await collectionForNesting.getBalance(targetTokenBob.nestingAccount())).eq(150n);
+      expect(await targetTokenBob.getChildren()).to.be.deep.equal([{collectionId: collectionForNesting.collectionId, tokenId: 0}]);
+    });
   });
-});
 
+  [
+    {restrictedMode: true},
+    {restrictedMode: false},
+  ].map(testCase => {
+    itSub(`Token owner can nest Native FT in NFT if "tokenOwner" permission set  ${testCase.restrictedMode ? 'in restricted mode': ''}`, async ({helper}) => {
+      // Only NFT allows nesting, permissions should be set:
+      const targetNFTCollection = await helper.nft.mintCollection(alice);
+      const targetTokenBob = await targetNFTCollection.mintToken(alice, {Substrate: bob.address});
+      expect(await targetTokenBob.getChildren()).to.be.empty;
 
-[
-  {restrictedMode: true},
-  {restrictedMode: false},
-].map(testCase => {
-  itSub(`Token owner can nest FT in NFT if "tokenOwner" permission set  ${testCase.restrictedMode ? 'in restricted mode': ''}`, async ({helper}) => {
-    // Only NFT allows nesting, permissions should be set:
-    const targetNFTCollection = await helper.nft.mintCollection(alice);
-    const targetTokenBob = await targetNFTCollection.mintToken(alice, {Substrate: bob.address});
+      const collectionForNesting = helper.ft.getCollectionObject(0);
+      // permissions should be set:
+      await targetNFTCollection.setPermissions(alice, {
+        nesting: {tokenOwner: true, restricted: testCase.restrictedMode ? [collectionForNesting.collectionId] : null},
+      });
 
-    const collectionForNesting = await helper.ft.mintCollection(bob);
-    // permissions should be set:
-    await targetNFTCollection.setPermissions(alice, {
-      nesting: {tokenOwner: true, restricted: testCase.restrictedMode ? [collectionForNesting.collectionId] : null},
+      // Bob can nest Native FT into their NFT:
+      await collectionForNesting.transfer(bob, targetTokenBob.nestingAccount(), 50n);
+      expect(await collectionForNesting.getBalance(targetTokenBob.nestingAccount())).eq(50n);
+      // Native FT should't be visible in NFT children:
+      expect(await targetTokenBob.getChildren()).to.be.deep.equal([]);
     });
+  });
+
 
-    // 1. Alice can immediately create nested tokens:
-    await collectionForNesting.mint(bob, 100n, targetTokenBob.nestingAccount());
-    expect(await collectionForNesting.getTop10Owners()).deep.eq([targetTokenBob.nestingAccount().toLowerCase()]);
-    expect(await collectionForNesting.getBalance(targetTokenBob.nestingAccount())).eq(100n);
+  itSub.ifWithPallets('Owner can unnest tokens using transferFrom', [Pallets.ReFungible], async ({helper}) => {
+    const collectionToNest = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+    const tokenA = await collectionToNest.mintToken(alice);
+    const tokenB = await collectionToNest.mintToken(alice);
 
-    // 2. Alice can mint and nest token:
-    await collectionForNesting.mint(bob, 100n);
-    await collectionForNesting.transfer(bob, targetTokenBob.nestingAccount(), 50n);
-    expect(await collectionForNesting.getBalance(targetTokenBob.nestingAccount())).eq(150n);
-  });
-});
+    // Create a nested token
+    const nftCollectionToBeNested = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+    const rftCollectionToBeNested = await helper.rft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+    const ftCollectionToBeNested = await helper.ft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+    const nativeFtCollectionToBeNested = helper.ft.getCollectionObject(0);
 
+    const nestedNFT = await nftCollectionToBeNested.mintToken(alice, tokenA.nestingAccount());
+    const nestedRFT = await rftCollectionToBeNested.mintToken(alice, 100n, tokenA.nestingAccount());
+    const _nestedFT = await ftCollectionToBeNested.mint(alice, 100n, tokenA.nestingAccount());
+    await nativeFtCollectionToBeNested.transfer(alice, tokenA.nestingAccount(), 100n);
+    expect(await nestedNFT.getOwner()).to.be.deep.equal(tokenA.nestingAccount().toLowerCase());
+    expect(await nestedRFT.getOwner()).to.be.deep.equal(tokenA.nestingAccount().toLowerCase());
+    expect(await ftCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(100n);
+    expect(await nativeFtCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(100n);
 
-itSub.ifWithPallets('Owner can unnest tokens using transferFrom', [Pallets.ReFungible], async ({helper}) => {
-  const collectionToNest = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
-  const tokenA = await collectionToNest.mintToken(alice);
-  const tokenB = await collectionToNest.mintToken(alice);
+    expect(await tokenA.getChildren()).to.be.length(3);
+    expect(await tokenB.getChildren()).to.be.length(0);
 
-  // Create a nested token
-  const nftCollectionToBeNested = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
-  const rftCollectionToBeNested = await helper.rft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
-  const ftCollectionToBeNested = await helper.ft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+    // Transfer the nested token to another token
+    await nestedNFT.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount());
+    await nestedRFT.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount(), 25n);
+    await ftCollectionToBeNested.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount(), 25n);
+    await nativeFtCollectionToBeNested.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount(), 25n);
 
-  const nestedNFT = await nftCollectionToBeNested.mintToken(alice, tokenA.nestingAccount());
-  const nestedRFT = await rftCollectionToBeNested.mintToken(alice, 100n, tokenA.nestingAccount());
-  const _nestedFT = await ftCollectionToBeNested.mint(alice, 100n, tokenA.nestingAccount());
-  expect(await nestedNFT.getOwner()).to.be.deep.equal(tokenA.nestingAccount().toLowerCase());
-  expect(await nestedRFT.getOwner()).to.be.deep.equal(tokenA.nestingAccount().toLowerCase());
-  expect(await ftCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(100n);
+    expect(await nestedNFT.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
+    expect(await nestedNFT.getOwner()).to.be.deep.equal(tokenB.nestingAccount().toLowerCase());
 
-  // Transfer the nested token to another token
-  await nestedNFT.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount());
-  await nestedRFT.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount(), 25n);
-  await ftCollectionToBeNested.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount(), 25n);
+    expect(await nestedRFT.getBalance(tokenB.nestingAccount())).to.equal(25n);
+    expect(await nestedRFT.getBalance(tokenA.nestingAccount())).to.equal(75n);
 
-  expect(await nestedNFT.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
-  expect(await nestedNFT.getOwner()).to.be.deep.equal(tokenB.nestingAccount().toLowerCase());
+    expect(await ftCollectionToBeNested.getBalance(tokenB.nestingAccount())).to.equal(25n);
+    expect(await ftCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(75n);
 
-  expect(await nestedRFT.getBalance(tokenB.nestingAccount())).to.equal(25n);
-  expect(await nestedRFT.getBalance(tokenA.nestingAccount())).to.equal(75n);
+    expect(await nativeFtCollectionToBeNested.getBalance(tokenB.nestingAccount())).to.equal(25n);
+    expect(await nativeFtCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(75n);
 
-  expect(await ftCollectionToBeNested.getBalance(tokenB.nestingAccount())).to.equal(25n);
-  expect(await ftCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(75n);
+    // RFT, FT, and without native FT
+    expect(await tokenA.getChildren()).to.be.length(2);
+    // NFT, RFT, FT, and without native FT
+    expect(await tokenB.getChildren()).to.be.length(3);
+  });
 });
modifiedtests/src/sub/nesting/e2e.test.tsdiffbeforeafterboth
--- a/tests/src/sub/nesting/e2e.test.ts
+++ b/tests/src/sub/nesting/e2e.test.ts
@@ -32,6 +32,7 @@
     const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
     const collectionB = await helper.ft.mintCollection(alice);
     const collectionC = await helper.rft.mintCollection(alice);
+    const collectionNative = helper.ft.getCollectionObject(0);
 
     const targetToken = await collectionA.mintToken(alice);
     expect((await targetToken.getChildren()).length).to.be.equal(0, 'Children length check at creation');
@@ -73,6 +74,14 @@
       {tokenId: tokenC.tokenId, collectionId: collectionC.collectionId},
     ]).and.has.length(3);
 
+    // Nest native fungible token into another collection
+    await collectionNative.transfer(alice, targetToken.nestingAccount(), 2n);
+    expect(await targetToken.getChildren()).to.have.deep.members([
+      {tokenId: tokenA.tokenId, collectionId: collectionA.collectionId},
+      {tokenId: 0, collectionId: collectionB.collectionId},
+      {tokenId: tokenC.tokenId, collectionId: collectionC.collectionId},
+    ]).and.has.length(3);
+
     // Burn all nested pieces
     await tokenC.burnFrom(alice, targetToken.nestingAccount(), 2n);
     expect(await targetToken.getChildren()).to.have.deep.members([
modifiedtests/src/sub/nesting/nesting.negative.test.tsdiffbeforeafterboth
--- a/tests/src/sub/nesting/nesting.negative.test.ts
+++ b/tests/src/sub/nesting/nesting.negative.test.ts
@@ -23,238 +23,272 @@
 let bob: IKeyringPair;
 let charlie: IKeyringPair;
 
-before(async () => {
-  await usingPlaygrounds(async (helper, privateKey) => {
-    const donor = await privateKey({url: import.meta.url});
-    [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
+describe('Negative Test: Nesting', () => {
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      const donor = await privateKey({url: import.meta.url});
+      [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
+    });
   });
-});
 
-[
-  {mode: 'nft' as const, requiredPallets: []},
-  {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
-].map(testCase => {
-  itSub.ifWithPallets(`Owner cannot nest ${testCase.mode.toUpperCase()} if nesting is disabled`, testCase.requiredPallets, async ({helper}) => {
-    // Create default collection, permissions are not set:
-    const aliceNFTCollection = await helper.nft.mintCollection(alice);
-    const targetToken = await aliceNFTCollection.mintToken(alice);
+  [
+    {mode: 'nft' as const, requiredPallets: []},
+    {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+  ].map(testCase => {
+    itSub.ifWithPallets(`Owner cannot nest ${testCase.mode.toUpperCase()} if nesting is disabled`, testCase.requiredPallets, async ({helper}) => {
+      // Create default collection, permissions are not set:
+      const aliceNFTCollection = await helper.nft.mintCollection(alice);
+      const targetToken = await aliceNFTCollection.mintToken(alice);
 
-    const collectionForNesting = await helper[testCase.mode].mintCollection(alice);
+      const collectionForNesting = await helper[testCase.mode].mintCollection(alice);
 
-    // 1. Alice cannot create immediately nested tokens:
-    const nestingTx = testCase.mode === 'nft'
-      ? (collectionForNesting as UniqueNFTCollection).mintToken(alice, targetToken.nestingAccount())
-      : (collectionForNesting as UniqueRFTCollection).mintToken(alice, 10n, targetToken.nestingAccount());
-    await expect(nestingTx).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+      // 1. Alice cannot create immediately nested tokens:
+      const nestingTx = testCase.mode === 'nft'
+        ? (collectionForNesting as UniqueNFTCollection).mintToken(alice, targetToken.nestingAccount())
+        : (collectionForNesting as UniqueRFTCollection).mintToken(alice, 10n, targetToken.nestingAccount());
+      await expect(nestingTx).to.be.rejectedWith('common.UserIsNotAllowedToNest');
 
-    // 2. Alice cannot mint and nest token:
-    const nestedToken2 = await collectionForNesting.mintToken(alice);
-    await expect(nestedToken2.nest(alice, targetToken)).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+      // 2. Alice cannot mint and nest token:
+      const nestedToken2 = await collectionForNesting.mintToken(alice);
+      await expect(nestedToken2.nest(alice, targetToken)).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+    });
   });
-});
 
-itSub('Owner cannot nest FT if nesting is disabled', async ({helper}) => {
-  // Create default collection, permissions are not set:
-  const aliceNFTCollection = await helper.nft.mintCollection(alice);
-  const targetToken = await aliceNFTCollection.mintToken(alice);
+  [
+    {mode: 'ft'},
+    {mode: 'nativeFt'},
+  ].map(testCase => {
+    itSub(`Owner cannot nest [${testCase.mode}] if nesting is disabled (except for native fungible collection)`, async ({helper}) => {
+    // Create default collection, permissions are not set:
+      const aliceNFTCollection = await helper.nft.mintCollection(alice);
+      const targetToken = await aliceNFTCollection.mintToken(alice);
 
-  const collectionForNesting = await helper.ft.mintCollection(alice);
+      const collectionForNesting = testCase.mode === 'ft' ? await helper.ft.mintCollection(alice) : helper.ft.getCollectionObject(0);
 
-  // 1. Alice cannot create immediately nested tokens:
-  await expect(collectionForNesting.mint(alice, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+      // Alice cannot create immediately nested tokens:
+      if (testCase.mode === 'ft') {
+        await expect(collectionForNesting.mint(alice, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+      } else {
+        await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 100n)).to.be.not.rejected;
+      }
 
-  // 2. Alice can mint and nest token:
-  await collectionForNesting.mint(alice, 100n);
-  await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 50n)).to.be.rejectedWith('common.UserIsNotAllowedToNest');
-});
+      // Alice can't mint and nest tokens:
+      if (testCase.mode === 'ft') {
+        await collectionForNesting.mint(alice, 100n);
+      }
 
-[
-  {mode: 'nft' as const},
-  {mode: 'rft' as const},
-  {mode: 'ft' as const},
-].map(testCase => {
-  itSub(`Non-owner and non-admin cannot nest ${testCase.mode.toUpperCase()} in someone else's tokens`, async ({helper}) => {
-    const targetCollection = await helper.nft.mintCollection(alice, {permissions:
-      {nesting: {tokenOwner: true, collectionAdmin: true}},
+      if (testCase.mode === 'ft') {
+        await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 50n)).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+      } else {
+        await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 50n)).to.be.not.rejected;
+      }
     });
-    const targetToken = await targetCollection.mintToken(alice);
+  });
+
+  [
+    {mode: 'nft' as const},
+    {mode: 'rft' as const},
+    {mode: 'ft' as const},
+    {mode: 'native ft' as const},
+  ].map(testCase => {
+    itSub(`Non-owner and non-admin cannot nest ${testCase.mode.toUpperCase()} in someone else's tokens (except for native fungible collection)`, async ({helper}) => {
+      const targetCollection = await helper.nft.mintCollection(alice, {permissions:
+        {nesting: {tokenOwner: true, collectionAdmin: true}},
+      });
+      const targetToken = await targetCollection.mintToken(alice);
 
-    const nestedCollectionBob = await helper[testCase.mode].mintCollection(bob);
+      const nestedCollectionBob = await (
+        testCase.mode === 'native ft'
+          ? helper.ft.getCollectionObject(0)
+          : helper[testCase.mode].mintCollection(bob)
+      );
 
-    let nestedTokenBob: UniqueNFToken | UniqueRFToken;
-    switch (testCase.mode) {
-      case 'nft': nestedTokenBob = await (nestedCollectionBob as UniqueNFTCollection).mintToken(bob); break;
-      case 'rft': nestedTokenBob = await (nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n); break;
-      case 'ft': await (nestedCollectionBob as UniqueFTCollection).mint(bob, 100n); break;
-    }
+      let nestedTokenBob: UniqueNFToken | UniqueRFToken;
+      switch (testCase.mode) {
+        case 'nft': nestedTokenBob = await (nestedCollectionBob as UniqueNFTCollection).mintToken(bob); break;
+        case 'rft': nestedTokenBob = await (nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n); break;
+        case 'ft': await (nestedCollectionBob as UniqueFTCollection).mint(bob, 100n); break;
+        case 'native ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n)).to.be.rejectedWith('common.UnsupportedOperation'); break;
+      }
 
-    // Bob non-owner of targetToken and non admin of targetCollection, so
-    // 1. cannot mint nested token:
-    switch (testCase.mode) {
-      case 'nft': await expect((nestedCollectionBob as UniqueNFTCollection).mintToken(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
-      case 'rft': await expect((nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
-      case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
-    }
+      // Bob non-owner of targetToken and non admin of targetCollection, so
+      // 1. cannot mint nested token:
+      switch (testCase.mode) {
+        case 'nft': await expect((nestedCollectionBob as UniqueNFTCollection).mintToken(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
+        case 'rft': await expect((nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
+        case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
+        case 'native ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UnsupportedOperation'); break;
+      }
 
-    // 2. cannot nest existing token:
-    switch (testCase.mode) {
-      case 'nft':
-      case 'rft': await expect(nestedTokenBob!.transfer(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
-      case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).transfer(bob, targetToken.nestingAccount(), 100n)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
-    }
+      // 2. cannot nest existing token:
+      switch (testCase.mode) {
+        case 'nft':
+        case 'rft': await expect(nestedTokenBob!.transfer(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
+        case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).transfer(bob, targetToken.nestingAccount(), 100n)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
+        case 'native ft': await expect((nestedCollectionBob as UniqueFTCollection).transfer(bob, targetToken.nestingAccount(), 100n)).to.be.not.rejected; break;
+      }
+    });
   });
-});
 
-[
-  {mode: 'nft' as const, nesting: {tokenOwner: true,  collectionAdmin: false}},
-  {mode: 'nft' as const, nesting: {tokenOwner: false, collectionAdmin: true}},
-].map(testCase => {
-  itSub(`${testCase.nesting.tokenOwner ? 'Admin' : 'Token owner'} cannot nest when only ${testCase.nesting.tokenOwner ? 'tokenOwner' : 'collectionAdmin'} is allowed`, async ({helper}) => {
-    // Create collection with tokenOwner or create collection with collectionAdmin permission:
-    const targetCollection = await helper.nft.mintCollection(alice, {permissions: {nesting: testCase.nesting}});
-    const targetTokenCharlie = await targetCollection.mintToken(alice, {Substrate: charlie.address});
-    await targetCollection.addAdmin(alice, {Substrate: bob.address});
+  [
+    {mode: 'nft' as const, nesting: {tokenOwner: true,  collectionAdmin: false}},
+    {mode: 'nft' as const, nesting: {tokenOwner: false, collectionAdmin: true}},
+  ].map(testCase => {
+    itSub(`${testCase.nesting.tokenOwner ? 'Admin' : 'Token owner'} cannot nest when only ${testCase.nesting.tokenOwner ? 'tokenOwner' : 'collectionAdmin'} is allowed`, async ({helper}) => {
+      // Create collection with tokenOwner or create collection with collectionAdmin permission:
+      const targetCollection = await helper.nft.mintCollection(alice, {permissions: {nesting: testCase.nesting}});
+      const targetTokenCharlie = await targetCollection.mintToken(alice, {Substrate: charlie.address});
+      await targetCollection.addAdmin(alice, {Substrate: bob.address});
 
-    const nestedCollectionCharlie = await helper[testCase.mode].mintCollection(charlie);
-    const nestedCollectionBob = await helper[testCase.mode].mintCollection(bob);
-    // if nesting permissions restricted for token owner – minter is bob (admin),
-    // if collectionAdmin – charlie (owner)
-    testCase.nesting.tokenOwner
-      ? await expect(nestedCollectionBob.mintToken(bob, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/)
-      : await expect(nestedCollectionCharlie.mintToken(charlie, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);
+      const nestedCollectionCharlie = await helper[testCase.mode].mintCollection(charlie);
+      const nestedCollectionBob = await helper[testCase.mode].mintCollection(bob);
+      // if nesting permissions restricted for token owner – minter is bob (admin),
+      // if collectionAdmin – charlie (owner)
+      testCase.nesting.tokenOwner
+        ? await expect(nestedCollectionBob.mintToken(bob, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/)
+        : await expect(nestedCollectionCharlie.mintToken(charlie, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);
+    });
   });
-});
 
-itSub.ifWithPallets('Cannot nest in non existing token', [Pallets.ReFungible], async ({helper}) => {
-  const collection = await helper.nft.mintCollection(alice);
-  // To avoid UserIsNotAllowedToNest error
-  await helper.collection.setPermissions(alice, collection.collectionId, {nesting: {collectionAdmin: true}});
+  itSub.ifWithPallets('Cannot nest in non existing token (except for native fungible collection)', [Pallets.ReFungible], async ({helper}) => {
+    const collection = await helper.nft.mintCollection(alice);
+    // To avoid UserIsNotAllowedToNest error
+    await helper.collection.setPermissions(alice, collection.collectionId, {nesting: {collectionAdmin: true}});
 
-  // The list of non-existing tokens:
-  const tokenFromNonExistingCollection = helper.nft.getTokenObject(9999999, 1);
-  const tokenBurnt = await collection.mintToken(alice);
-  await tokenBurnt.burn(alice);
-  const tokenNotMintedYet = helper.nft.getTokenObject(collection.collectionId, 2);
+    // The list of non-existing tokens:
+    const tokenFromNonExistingCollection = helper.nft.getTokenObject(9999999, 1);
+    const tokenBurnt = await collection.mintToken(alice);
+    await tokenBurnt.burn(alice);
+    const tokenNotMintedYet = helper.nft.getTokenObject(collection.collectionId, 2);
 
-  // The list of collections to nest tokens from:
-  const nftCollectionForNesting = await helper.nft.mintCollection(alice);
-  const rftCollectionForNesting = await helper.rft.mintCollection(alice);
-  const ftCollectionForNesting = await helper.ft.mintCollection(alice);
+    // The list of collections to nest tokens from:
+    const nftCollectionForNesting = await helper.nft.mintCollection(alice);
+    const rftCollectionForNesting = await helper.rft.mintCollection(alice);
+    const ftCollectionForNesting = await helper.ft.mintCollection(alice);
+    const nativeFtCollectionForNesting = helper.ft.getCollectionObject(0);
 
-  const testCases = [
-    {token: tokenFromNonExistingCollection, error: 'CollectionNotFound'},
-    {token: tokenBurnt, error: 'TokenNotFound'},
-    {token: tokenNotMintedYet, error: 'TokenNotFound'},
-  ];
+    const testCases = [
+      {token: tokenFromNonExistingCollection, error: 'CollectionNotFound'},
+      {token: tokenBurnt, error: 'TokenNotFound'},
+      {token: tokenNotMintedYet, error: 'TokenNotFound'},
+    ];
 
-  for(const testCase of testCases) {
-    // 1. Alice cannot create nested token to non-existing token
-    await expect(nftCollectionForNesting.mintToken(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
-    await expect(rftCollectionForNesting.mintToken(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
-    await expect(ftCollectionForNesting.mint(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
+    for(const testCase of testCases) {
+      // 1. Alice cannot create nested token to non-existing token
+      await expect(nftCollectionForNesting.mintToken(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
+      await expect(rftCollectionForNesting.mintToken(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
+      await expect(ftCollectionForNesting.mint(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
 
-    // 2. Alice cannot mint and nest token:
-    const nft = await nftCollectionForNesting.mintToken(alice);
-    const rft = await rftCollectionForNesting.mintToken(alice, 100n);
-    const _ft = await ftCollectionForNesting.mint(alice, 100n);
-    await expect(nft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
-    await expect(rft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
-    await expect(ftCollectionForNesting.transfer(alice, testCase.token.nestingAccount(), 50n)).to.be.rejectedWith(testCase.error);
-  }
-});
+      // 2. Alice cannot mint and nest token:
+      const nft = await nftCollectionForNesting.mintToken(alice);
+      const rft = await rftCollectionForNesting.mintToken(alice, 100n);
+      const _ft = await ftCollectionForNesting.mint(alice, 100n);
+      await expect(nft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
+      await expect(rft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
+      await expect(ftCollectionForNesting.transfer(alice, testCase.token.nestingAccount(), 50n)).to.be.rejectedWith(testCase.error);
+      await expect(nativeFtCollectionForNesting.transfer(alice, testCase.token.nestingAccount(), 50n)).to.be.not.rejected;
+    }
+  });
 
-itEth.ifWithPallets('Cannot nest in collection address', [Pallets.ReFungible], async({helper}) => {
-  const existingCollection = await helper.nft.mintCollection(alice);
-  const existingCollectionAddress = helper.ethAddress.fromCollectionId(existingCollection.collectionId);
-  const futureCollectionAddress = helper.ethAddress.fromCollectionId(99999999);
+  itEth.ifWithPallets('Cannot nest in collection address', [Pallets.ReFungible], async({helper}) => {
+    const existingCollection = await helper.nft.mintCollection(alice);
+    const existingCollectionAddress = helper.ethAddress.fromCollectionId(existingCollection.collectionId);
+    const futureCollectionAddress = helper.ethAddress.fromCollectionId(99999999);
 
-  const nftCollectionForNesting = await helper.nft.mintCollection(alice);
+    const nftCollectionForNesting = await helper.nft.mintCollection(alice);
 
-  // 1. Alice cannot create nested token to collection address
-  await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
-  await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
+    // 1. Alice cannot create nested token to collection address
+    await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
+    await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
 
-  // 2. Alice cannot mint and nest token to collection address:
-  const nft = await nftCollectionForNesting.mintToken(alice);
-  await expect(nft.transfer(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
-  await expect(nft.transfer(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
-});
+    // 2. Alice cannot mint and nest token to collection address:
+    const nft = await nftCollectionForNesting.mintToken(alice);
+    await expect(nft.transfer(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
+    await expect(nft.transfer(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
+  });
 
-itEth.ifWithPallets('Cannot nest in RFT or FT', [Pallets.ReFungible], async ({helper}) => {
-  // Create default collection, permissions are not set:
-  const rftCollection = await helper.rft.mintCollection(alice);
-  const ftCollection = await helper.ft.mintCollection(alice);
+  itEth.ifWithPallets('Cannot nest in RFT or FT (except for native fungible collection)', [Pallets.ReFungible], async ({helper}) => {
+    // Create default collection, permissions are not set:
+    const rftCollection = await helper.rft.mintCollection(alice);
+    const ftCollection = await helper.ft.mintCollection(alice);
+    const nativeFtCollection = helper.ft.getCollectionObject(0);
 
-  const rftToken = await rftCollection.mintToken(alice);
-  const _ftToken = await ftCollection.mint(alice, 100n);
+    const rftToken = await rftCollection.mintToken(alice);
+    const _ftToken = await ftCollection.mint(alice, 100n);
 
-  const collectionForNesting = await helper.nft.mintCollection(alice);
+    const collectionForNesting = await helper.nft.mintCollection(alice);
 
-  // 1. Alice cannot create immediately nested tokens:
-  await expect(collectionForNesting.mintToken(alice, rftToken.nestingAccount())).to.be.rejectedWith('refungible.RefungibleDisallowsNesting');
-  await expect(collectionForNesting.mintToken(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting');
+    // 1. Alice cannot create immediately nested tokens:
+    await expect(collectionForNesting.mintToken(alice, rftToken.nestingAccount())).to.be.rejectedWith('refungible.RefungibleDisallowsNesting');
+    await expect(collectionForNesting.mintToken(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting');
+    await expect(collectionForNesting.mintToken(alice, {Ethereum: helper.ethAddress.fromTokenId(nativeFtCollection.collectionId, 0)})).to.be.rejectedWith('common.UnsupportedOperation');
 
-  // 2. Alice cannot mint and nest token:
-  const nestedToken2 = await collectionForNesting.mintToken(alice);
-  await expect(nestedToken2.nest(alice, rftToken)).to.be.rejectedWith('refungible.RefungibleDisallowsNesting');
-  await expect(ftCollection.transfer(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting');
-});
+    // 2. Alice cannot mint and nest token:
+    const nestedToken2 = await collectionForNesting.mintToken(alice);
+    await expect(nestedToken2.nest(alice, rftToken)).to.be.rejectedWith('refungible.RefungibleDisallowsNesting');
+    await expect(ftCollection.transfer(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting');
+    await expect(nativeFtCollection.transfer(alice, {Ethereum: helper.ethAddress.fromTokenId(nativeFtCollection.collectionId, 0)})).to.be.not.rejected;
+  });
 
-itSub('Cannot nest in restricted collection if collection is not in the list', async ({helper}) => {
-  const {collectionId: allowedCollectionId} = await helper.nft.mintCollection(alice);
-  const notAllowedCollectionNFT = await helper.nft.mintCollection(alice);
-  const notAllowedCollectionRFT = await helper.rft.mintCollection(alice);
-  const notAllowedCollectionFT = await helper.ft.mintCollection(alice);
+  itSub('Cannot nest in restricted collection if collection is not in the list (except native fungible collection)', async ({helper}) => {
+    const {collectionId: allowedCollectionId} = await helper.nft.mintCollection(alice);
+    const notAllowedCollectionNFT = await helper.nft.mintCollection(alice);
+    const notAllowedCollectionRFT = await helper.rft.mintCollection(alice);
+    const notAllowedCollectionFT = await helper.ft.mintCollection(alice);
+    const allowedCollectionNativeFT = helper.ft.getCollectionObject(0);
 
-  // Collection restricted to allowedCollectionId
-  const restrictedCollectionA = await helper.nft.mintCollection(alice, {permissions:
-    {nesting: {tokenOwner: true, restricted: [allowedCollectionId]}},
-  });
-  // Create collection with restricted nesting -- even self is not allowed
-  const restrictedCollectionB = await helper.nft.mintCollection(alice, {permissions:
-    {nesting: {tokenOwner: true, restricted: []}},
-  });
+    // Collection restricted to allowedCollectionId
+    const restrictedCollectionA = await helper.nft.mintCollection(alice, {permissions:
+      {nesting: {tokenOwner: true, restricted: [allowedCollectionId]}},
+    });
+    // Create collection with restricted nesting -- even self is not allowed
+    const restrictedCollectionB = await helper.nft.mintCollection(alice, {permissions:
+      {nesting: {tokenOwner: true, restricted: []}},
+    });
 
-  const targetTokenA = await restrictedCollectionA.mintToken(alice);
-  const targetTokenB = await restrictedCollectionB.mintToken(alice);
+    const targetTokenA = await restrictedCollectionA.mintToken(alice);
+    const targetTokenB = await restrictedCollectionB.mintToken(alice);
 
-  // 1. Cannot mint in own collection after allowlisting the accounts:
-  await expect(restrictedCollectionA.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
-  await expect(restrictedCollectionB.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+    // 1. Cannot mint in own collection after allowlisting the accounts:
+    await expect(restrictedCollectionA.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+    await expect(restrictedCollectionB.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
 
-  // 2. Cannot mint from notAllowedCollection:
-  await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
-  await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
-  await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
-  await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
-  await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
-  await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
-});
+    // 2. Cannot mint from notAllowedCollection:
+    await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+    await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+    await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+    await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+    await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+    await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+    await expect(allowedCollectionNativeFT.transfer(alice, targetTokenA.nestingAccount(), 100n)).to.be.not.rejected;
+    await expect(allowedCollectionNativeFT.transfer(alice, targetTokenB.nestingAccount(), 100n)).to.be.not.rejected;
+  });
 
-itSub('Cannot create nesting chains greater than 5', async ({helper}) => {
-  const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
-  let token = await collection.mintToken(alice);
+  itSub('Cannot create nesting chains greater than 5', async ({helper}) => {
+    const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+    let token = await collection.mintToken(alice);
 
-  const maxNestingLevel = 5;
+    const maxNestingLevel = 5;
 
-  // Create a nested-token matryoshka
-  for (let i = 0; i < maxNestingLevel; i++) {
-    token = await collection.mintToken(alice, token.nestingAccount());
-  }
+    // Create a nested-token matryoshka
+    for (let i = 0; i < maxNestingLevel; i++) {
+      token = await collection.mintToken(alice, token.nestingAccount());
+    }
 
-  // The nesting depth is limited by `maxNestingLevel`
-  // 1. Cannot mint:
-  await expect(collection.mintToken(alice, token.nestingAccount()))
-    .to.be.rejectedWith(/structure\.DepthLimit/);
-  // 2. Cannot transfer:
-  const anotherToken = await collection.mintToken(alice);
-  await expect(anotherToken.transfer(alice, token.nestingAccount()))
-    .to.be.rejectedWith(/structure\.DepthLimit/);
-  // 3. Cannot nest FT pieces:
-  const ftCollection = await helper.ft.mintCollection(alice);
-  await expect(ftCollection.mint(alice, 100n, token.nestingAccount()))
-    .to.be.rejectedWith(/structure\.DepthLimit/);
+    // The nesting depth is limited by `maxNestingLevel`
+    // 1. Cannot mint:
+    await expect(collection.mintToken(alice, token.nestingAccount()))
+      .to.be.rejectedWith(/structure\.DepthLimit/);
+    // 2. Cannot transfer:
+    const anotherToken = await collection.mintToken(alice);
+    await expect(anotherToken.transfer(alice, token.nestingAccount()))
+      .to.be.rejectedWith(/structure\.DepthLimit/);
+    // 3. Cannot nest FT pieces:
+    const ftCollection = await helper.ft.mintCollection(alice);
+    await expect(ftCollection.mint(alice, 100n, token.nestingAccount()))
+      .to.be.rejectedWith(/structure\.DepthLimit/);
 
-  expect(await token.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
-  expect(await token.getChildren()).to.has.length(0);
+    expect(await token.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
+    expect(await token.getChildren()).to.has.length(0);
+  });
 });
modifiedtests/src/sub/nesting/refungible.test.tsdiffbeforeafterboth
--- a/tests/src/sub/nesting/refungible.test.ts
+++ b/tests/src/sub/nesting/refungible.test.ts
@@ -17,44 +17,45 @@
 import {IKeyringPair} from '@polkadot/types/types';
 import {expect, itSub, Pallets, usingPlaygrounds} from '../../util';
 
-// ReFungible specific nesting tests
-let alice: IKeyringPair;
+describe('ReFungible-specific nesting tests', () => {
+  let alice: IKeyringPair;
 
-before(async () => {
-  await usingPlaygrounds(async (helper, privateKey) => {
-    const donor = await privateKey({url: import.meta.url});
-    [alice] = await helper.arrange.createAccounts([200n], donor);
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      const donor = await privateKey({url: import.meta.url});
+      [alice] = await helper.arrange.createAccounts([200n], donor);
+    });
   });
-});
 
-itSub.ifWithPallets('ReFungible: getTopmostOwner works correctly with Nesting', [Pallets.ReFungible], async({helper}) => {
-  const collectionNFT = await helper.nft.mintCollection(alice, {
-    permissions: {
-      nesting: {
-        tokenOwner: true,
+  itSub.ifWithPallets('ReFungible: getTopmostOwner works correctly with Nesting', [Pallets.ReFungible], async({helper}) => {
+    const collectionNFT = await helper.nft.mintCollection(alice, {
+      permissions: {
+        nesting: {
+          tokenOwner: true,
+        },
       },
-    },
-  });
-  const collectionRFT = await helper.rft.mintCollection(alice);
+    });
+    const collectionRFT = await helper.rft.mintCollection(alice);
 
-  const nft = await collectionNFT.mintToken(alice, {Substrate: alice.address});
-  const rft = await collectionRFT.mintToken(alice, 100n, {Substrate: alice.address});
+    const nft = await collectionNFT.mintToken(alice, {Substrate: alice.address});
+    const rft = await collectionRFT.mintToken(alice, 100n, {Substrate: alice.address});
 
-  expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
+    expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
 
-  await rft.transfer(alice, nft.nestingAccount(), 40n);
+    await rft.transfer(alice, nft.nestingAccount(), 40n);
 
-  expect(await rft.getTopmostOwner()).deep.equal(null);
+    expect(await rft.getTopmostOwner()).deep.equal(null);
 
-  await rft.transfer(alice, nft.nestingAccount(), 60n);
+    await rft.transfer(alice, nft.nestingAccount(), 60n);
 
-  expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
+    expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
 
-  await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 30n);
+    await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 30n);
 
-  expect(await rft.getTopmostOwner()).deep.equal(null);
+    expect(await rft.getTopmostOwner()).deep.equal(null);
 
-  await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 70n);
+    await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 70n);
 
-  expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
+    expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
+  });
 });
modifiedtests/src/sub/nesting/unnesting.negative.test.tsdiffbeforeafterboth
--- a/tests/src/sub/nesting/unnesting.negative.test.ts
+++ b/tests/src/sub/nesting/unnesting.negative.test.ts
@@ -52,12 +52,19 @@
   });
 
   [
-    {restrictedMode: true},
-    {restrictedMode: false},
+    {mode: 'ft' as const},
+    {mode: 'native ft' as const},
+  ].map(md => [
+    {mode: md.mode, restrictedMode: true},
+    {mode: md.mode, restrictedMode: false},
   ].map(testCase => {
-    itSub(`Fungible: disallows a non-Owner to unnest someone else's token ${testCase.restrictedMode ? '(Restricted nesting)' : ''}`, async ({helper}) => {
+    itSub(`Fungible: disallows a non-Owner to unnest someone else's token [${testCase.mode}${testCase.restrictedMode ? ' (Restricted nesting)' : ''}]`, async ({helper}) => {
       const collectionNFT = await helper.nft.mintCollection(alice);
-      const collectionFT = await helper.ft.mintCollection(alice);
+      const collectionFT = await (
+        testCase.mode === 'ft'
+          ? helper.ft.mintCollection(alice)
+          : helper.ft.getCollectionObject(0)
+      );
       const targetToken = await collectionNFT.mintToken(alice, {Substrate: bob.address});
 
       await collectionNFT.setPermissions(alice, {nesting: {
@@ -65,12 +72,16 @@
       }});
 
       // Nest some tokens as Alice into Bob's token
-      await collectionFT.mint(alice, 5n, targetToken.nestingAccount());
+      await (
+        testCase.mode === 'ft'
+          ? collectionFT.mint(alice, 5n, targetToken.nestingAccount())
+          : collectionFT.transfer(alice, targetToken.nestingAccount(), 5n)
+      );
 
       // Try to pull it out as Alice still
       await expect(collectionFT.transferFrom(alice, targetToken.nestingAccount(), {Substrate: bob.address}, 1n))
         .to.be.rejectedWith(/common\.ApprovedValueTooLow/);
       expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(5n);
     });
-  });
+  }));
 });
modifiedtests/src/transfer.test.tsdiffbeforeafterboth
before · tests/src/transfer.test.ts
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/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {itEth, usingEthPlaygrounds} from './eth/util';19import {itSub, Pallets, usingPlaygrounds, expect} from './util';2021describe('Integration Test Transfer(recipient, collection_id, item_id, value)', () => {22  let donor: IKeyringPair;23  let alice: IKeyringPair;24  let bob: IKeyringPair;2526  before(async () => {27    await usingPlaygrounds(async (helper, privateKey) => {28      donor = await privateKey({url: import.meta.url});29      [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);30    });31  });3233  itSub('Balance transfers and check balance', async ({helper}) => {34    const alicesBalanceBefore = await helper.balance.getSubstrate(alice.address);35    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);3637    expect(await helper.balance.transferToSubstrate(alice, bob.address, 1n)).to.be.true;3839    const alicesBalanceAfter = await helper.balance.getSubstrate(alice.address);40    const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address);4142    expect(alicesBalanceAfter < alicesBalanceBefore).to.be.true;43    expect(bobsBalanceAfter > bobsBalanceBefore).to.be.true;44  });4546  itSub('Inability to pay fees error message is correct', async ({helper}) => {47    const [zero] = await helper.arrange.createAccounts([0n], donor);4849    // console.error = () => {};50    // The following operation throws an error into the console and the logs. Pay it no heed as long as the test succeeds.51    await expect(helper.balance.transferToSubstrate(zero, donor.address, 1n))52      .to.be.rejectedWith('Inability to pay some fees , e.g. account balance too low');53  });5455  itSub('[nft] User can transfer owned token', async ({helper}) => {56    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-1-NFT', description: '', tokenPrefix: 'T'});57    const nft = await collection.mintToken(alice);5859    await nft.transfer(alice, {Substrate: bob.address});60    expect(await nft.getOwner()).to.be.deep.equal({Substrate: bob.address});61  });6263  itSub('[fungible] User can transfer owned token', async ({helper}) => {64    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-1-FT', description: '', tokenPrefix: 'T'});65    await collection.mint(alice, 10n);6667    await collection.transfer(alice, {Substrate: bob.address}, 9n);68    expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(9n);69    expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1n);70  });7172  itSub.ifWithPallets('[refungible] User can transfer owned token', [Pallets.ReFungible], async ({helper}) => {73    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-1-RFT', description: '', tokenPrefix: 'T'});74    const rft = await collection.mintToken(alice, 10n);7576    await rft.transfer(alice, {Substrate: bob.address}, 9n);77    expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(9n);78    expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(1n);79  });8081  itSub('[nft] Collection admin can transfer owned token', async ({helper}) => {82    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-2-NFT', description: '', tokenPrefix: 'T'});83    await collection.addAdmin(alice, {Substrate: bob.address});8485    const nft = await collection.mintToken(bob, {Substrate: bob.address});86    await nft.transfer(bob, {Substrate: alice.address});8788    expect(await nft.getOwner()).to.be.deep.equal({Substrate: alice.address});89  });9091  itSub('[fungible] Collection admin can transfer owned token', async ({helper}) => {92    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-2-FT', description: '', tokenPrefix: 'T'});93    await collection.addAdmin(alice, {Substrate: bob.address});9495    await collection.mint(bob, 10n, {Substrate: bob.address});96    await collection.transfer(bob, {Substrate: alice.address}, 1n);9798    expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(9n);99    expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1n);100  });101102  itSub.ifWithPallets('[refungible] Collection admin can transfer owned token', [Pallets.ReFungible], async ({helper}) => {103    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-2-RFT', description: '', tokenPrefix: 'T'});104    await collection.addAdmin(alice, {Substrate: bob.address});105106    const rft = await collection.mintToken(bob, 10n, {Substrate: bob.address});107    await rft.transfer(bob, {Substrate: alice.address}, 1n);108109    expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(9n);110    expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(1n);111  });112});113114describe('Negative Integration Test Transfer(recipient, collection_id, item_id, value)', () => {115  let alice: IKeyringPair;116  let bob: IKeyringPair;117118  before(async () => {119    await usingPlaygrounds(async (helper, privateKey) => {120      const donor = await privateKey({url: import.meta.url});121      [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);122    });123  });124125126  itSub('[nft] Transfer with not existed collection_id', async ({helper}) => {127    const collectionId = (1 << 32) - 1;128    await expect(helper.nft.transferToken(alice, collectionId, 1, {Substrate: bob.address}))129      .to.be.rejectedWith(/common\.CollectionNotFound/);130  });131132  itSub('[fungible] Transfer with not existed collection_id', async ({helper}) => {133    const collectionId = (1 << 32) - 1;134    await expect(helper.ft.transfer(alice, collectionId, {Substrate: bob.address}))135      .to.be.rejectedWith(/common\.CollectionNotFound/);136  });137138  itSub.ifWithPallets('[refungible] Transfer with not existed collection_id', [Pallets.ReFungible], async ({helper}) => {139    const collectionId = (1 << 32) - 1;140    await expect(helper.rft.transferToken(alice, collectionId, 1, {Substrate: bob.address}))141      .to.be.rejectedWith(/common\.CollectionNotFound/);142  });143144  itSub('[nft] Transfer with deleted collection_id', async ({helper}) => {145    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-1-NFT', description: '', tokenPrefix: 'T'});146    const nft = await collection.mintToken(alice);147148    await nft.burn(alice);149    await collection.burn(alice);150151    await expect(nft.transfer(alice, {Substrate: bob.address}))152      .to.be.rejectedWith(/common\.CollectionNotFound/);153  });154155  itSub('[fungible] Transfer with deleted collection_id', async ({helper}) => {156    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-1-FT', description: '', tokenPrefix: 'T'});157    await collection.mint(alice, 10n);158159    await collection.burnTokens(alice, 10n);160    await collection.burn(alice);161162    await expect(collection.transfer(alice, {Substrate: bob.address}))163      .to.be.rejectedWith(/common\.CollectionNotFound/);164  });165166  itSub.ifWithPallets('[refungible] Transfer with deleted collection_id', [Pallets.ReFungible], async ({helper}) => {167    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-1-RFT', description: '', tokenPrefix: 'T'});168    const rft = await collection.mintToken(alice, 10n);169170    await rft.burn(alice, 10n);171    await collection.burn(alice);172173    await expect(rft.transfer(alice, {Substrate: bob.address}))174      .to.be.rejectedWith(/common\.CollectionNotFound/);175  });176177  itSub('[nft] Transfer with not existed item_id', async ({helper}) => {178    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-2-NFT', description: '', tokenPrefix: 'T'});179    await expect(collection.transferToken(alice, 1, {Substrate: bob.address}))180      .to.be.rejectedWith(/common\.TokenNotFound/);181  });182183  itSub('[fungible] Transfer with not existed item_id', async ({helper}) => {184    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-2-FT', description: '', tokenPrefix: 'T'});185    await expect(collection.transfer(alice, {Substrate: bob.address}))186      .to.be.rejectedWith(/common\.TokenValueTooLow/);187  });188189  itSub.ifWithPallets('[refungible] Transfer with not existed item_id', [Pallets.ReFungible], async ({helper}) => {190    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-2-RFT', description: '', tokenPrefix: 'T'});191    await expect(collection.transferToken(alice, 1, {Substrate: bob.address}))192      .to.be.rejectedWith(/common\.TokenValueTooLow/);193  });194195  itSub('Zero transfer NFT', async ({helper}) => {196    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-3-NFT', description: '', tokenPrefix: 'T'});197    const tokenAlice = await collection.mintToken(alice, {Substrate: alice.address});198    const tokenBob = await collection.mintToken(alice, {Substrate: bob.address});199    // 1. Zero transfer of own tokens allowed:200    await helper.executeExtrinsic(alice, 'api.tx.unique.transfer', [{Substrate: bob.address}, collection.collectionId, tokenAlice.tokenId, 0]);201    // 2. Zero transfer of non-owned tokens not allowed:202    await expect(helper.executeExtrinsic(alice, 'api.tx.unique.transfer', [{Substrate: alice.address}, collection.collectionId, tokenBob.tokenId, 0])).to.be.rejectedWith('common.NoPermission');203    // 3. Zero transfer of non-existing tokens not allowed:204    await expect(helper.executeExtrinsic(alice, 'api.tx.unique.transfer', [{Substrate: alice.address}, collection.collectionId, 10, 0])).to.be.rejectedWith('common.TokenNotFound');205    expect(await tokenAlice.getOwner()).to.deep.eq({Substrate: alice.address});206    expect(await tokenBob.getOwner()).to.deep.eq({Substrate: bob.address});207    // 4. Storage is not corrupted:208    await tokenAlice.transfer(alice, {Substrate: bob.address});209    await tokenBob.transfer(bob, {Substrate: alice.address});210    expect(await tokenAlice.getOwner()).to.deep.eq({Substrate: bob.address});211    expect(await tokenBob.getOwner()).to.deep.eq({Substrate: alice.address});212  });213214  itSub('[nft] Transfer with deleted item_id', async ({helper}) => {215    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-3-NFT', description: '', tokenPrefix: 'T'});216    const nft = await collection.mintToken(alice);217218    await nft.burn(alice);219220    await expect(nft.transfer(alice, {Substrate: bob.address}))221      .to.be.rejectedWith(/common\.TokenNotFound/);222  });223224  itSub('[fungible] Transfer with deleted item_id', async ({helper}) => {225    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-3-FT', description: '', tokenPrefix: 'T'});226    await collection.mint(alice, 10n);227228    await collection.burnTokens(alice, 10n);229230    await expect(collection.transfer(alice, {Substrate: bob.address}))231      .to.be.rejectedWith(/common\.TokenValueTooLow/);232  });233234  itSub.ifWithPallets('[refungible] Transfer with deleted item_id', [Pallets.ReFungible], async ({helper}) => {235    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-3-RFT', description: '', tokenPrefix: 'T'});236    const rft = await collection.mintToken(alice, 10n);237238    await rft.burn(alice, 10n);239240    await expect(rft.transfer(alice, {Substrate: bob.address}))241      .to.be.rejectedWith(/common\.TokenValueTooLow/);242  });243244  itSub('[nft] Transfer with recipient that is not owner', async ({helper}) => {245    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-4-NFT', description: '', tokenPrefix: 'T'});246    const nft = await collection.mintToken(alice);247248    await expect(nft.transfer(bob, {Substrate: bob.address}))249      .to.be.rejectedWith(/common\.NoPermission/);250    expect(await nft.getOwner()).to.be.deep.equal({Substrate: alice.address});251  });252253  itSub('[fungible] Transfer with recipient that is not owner', async ({helper}) => {254    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-4-FT', description: '', tokenPrefix: 'T'});255    await collection.mint(alice, 10n);256257    await expect(collection.transfer(bob, {Substrate: bob.address}, 9n))258      .to.be.rejectedWith(/common\.TokenValueTooLow/);259    expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(0n);260    expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(10n);261  });262263  itSub.ifWithPallets('[refungible] Transfer with recipient that is not owner', [Pallets.ReFungible], async ({helper}) => {264    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-1-RFT', description: '', tokenPrefix: 'T'});265    const rft = await collection.mintToken(alice, 10n);266267    await expect(rft.transfer(bob, {Substrate: bob.address}, 9n))268      .to.be.rejectedWith(/common\.TokenValueTooLow/);269    expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(0n);270    expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(10n);271  });272});273274describe('Transfers to self (potentially over substrate-evm boundary)', () => {275  let donor: IKeyringPair;276277  before(async function() {278    await usingEthPlaygrounds(async (_, privateKey) => {279      donor = await privateKey({url: import.meta.url});280    });281  });282283  itEth('Transfers to self. In case of same frontend', async ({helper}) => {284    const [owner] = await helper.arrange.createAccounts([10n], donor);285    const collection = await helper.ft.mintCollection(owner, {});286    await collection.mint(owner, 100n);287288    const ownerProxy = helper.address.substrateToEth(owner.address);289290    // transfer to own proxy291    await collection.transfer(owner, {Ethereum: ownerProxy}, 10n);292    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);293    expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);294295    // transfer-from own proxy to own proxy again296    await collection.transferFrom(owner, {Ethereum: ownerProxy}, {Ethereum: ownerProxy}, 5n);297    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);298    expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);299  });300301  itEth('Transfers to self. In case of substrate-evm boundary', async ({helper}) => {302    const [owner] = await helper.arrange.createAccounts([10n], donor);303    const collection = await helper.ft.mintCollection(owner, {});304    await collection.mint(owner, 100n);305306    const ownerProxy = helper.address.substrateToEth(owner.address);307308    // transfer to own proxy309    await collection.transfer(owner, {Ethereum: ownerProxy}, 10n);310    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);311    expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);312313    // transfer-from own proxy to self314    await collection.transferFrom(owner, {Ethereum: ownerProxy}, {Substrate: owner.address}, 5n);315    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(95n);316    expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(5n);317  });318319  itEth('Transfers to self. In case of inside substrate-evm', async ({helper}) => {320    const [owner] = await helper.arrange.createAccounts([10n], donor);321    const collection = await helper.ft.mintCollection(owner, {});322    await collection.mint(owner, 100n);323324    // transfer to self again325    await collection.transfer(owner, {Substrate: owner.address}, 10n);326    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(100n);327328    // transfer-from self to self again329    await collection.transferFrom(owner, {Substrate: owner.address}, {Substrate: owner.address}, 5n);330    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(100n);331  });332333  itEth('Transfers to self. In case of inside substrate-evm when not enought "Fungibles"', async ({helper}) => {334    const [owner] = await helper.arrange.createAccounts([10n], donor);335    const collection = await helper.ft.mintCollection(owner, {});336    await collection.mint(owner, 10n);337338    // transfer to self again339    await expect(collection.transfer(owner, {Substrate: owner.address}, 11n))340      .to.be.rejectedWith(/common\.TokenValueTooLow/);341342    // transfer-from self to self again343    await expect(collection.transferFrom(owner, {Substrate: owner.address}, {Substrate: owner.address}, 12n))344      .to.be.rejectedWith(/common\.TokenValueTooLow/);345    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(10n);346  });347});
after · tests/src/transfer.test.ts
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/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {itEth, usingEthPlaygrounds} from './eth/util';19import {itSub, Pallets, usingPlaygrounds, expect} from './util';20import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';2122describe('Integration Test Transfer(recipient, collection_id, item_id, value)', () => {23  let donor: IKeyringPair;24  let alice: IKeyringPair;25  let bob: IKeyringPair;2627  before(async () => {28    await usingPlaygrounds(async (helper, privateKey) => {29      donor = await privateKey({url: import.meta.url});30      [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);31    });32  });3334  itSub('Balance transfers and check balance', async ({helper}) => {35    const alicesBalanceBefore = await helper.balance.getSubstrate(alice.address);36    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);3738    expect(await helper.balance.transferToSubstrate(alice, bob.address, 1n)).to.be.true;3940    const alicesBalanceAfter = await helper.balance.getSubstrate(alice.address);41    const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address);4243    expect(alicesBalanceAfter < alicesBalanceBefore).to.be.true;44    expect(bobsBalanceAfter > bobsBalanceBefore).to.be.true;45  });4647  itSub('Inability to pay fees error message is correct', async ({helper}) => {48    const [zero] = await helper.arrange.createAccounts([0n], donor);4950    // console.error = () => {};51    // The following operation throws an error into the console and the logs. Pay it no heed as long as the test succeeds.52    await expect(helper.balance.transferToSubstrate(zero, donor.address, 1n))53      .to.be.rejectedWith('Inability to pay some fees , e.g. account balance too low');54  });5556  itSub('[nft] User can transfer owned token', async ({helper}) => {57    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-1-NFT', description: '', tokenPrefix: 'T'});58    const nft = await collection.mintToken(alice);5960    await nft.transfer(alice, {Substrate: bob.address});61    expect(await nft.getOwner()).to.be.deep.equal({Substrate: bob.address});62  });6364  itSub('[fungible] User can transfer owned token', async ({helper}) => {65    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-1-FT', description: '', tokenPrefix: 'T'});66    await collection.mint(alice, 10n);6768    await collection.transfer(alice, {Substrate: bob.address}, 9n);69    expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(9n);70    expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1n);71  });7273  itSub.ifWithPallets('[refungible] User can transfer owned token', [Pallets.ReFungible], async ({helper}) => {74    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-1-RFT', description: '', tokenPrefix: 'T'});75    const rft = await collection.mintToken(alice, 10n);7677    await rft.transfer(alice, {Substrate: bob.address}, 9n);78    expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(9n);79    expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(1n);80  });8182  itSub('[nft] Collection admin can transfer owned token', async ({helper}) => {83    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-2-NFT', description: '', tokenPrefix: 'T'});84    await collection.addAdmin(alice, {Substrate: bob.address});8586    const nft = await collection.mintToken(bob, {Substrate: bob.address});87    await nft.transfer(bob, {Substrate: alice.address});8889    expect(await nft.getOwner()).to.be.deep.equal({Substrate: alice.address});90  });9192  itSub('[fungible] Collection admin can transfer owned token', async ({helper}) => {93    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-2-FT', description: '', tokenPrefix: 'T'});94    await collection.addAdmin(alice, {Substrate: bob.address});9596    await collection.mint(bob, 10n, {Substrate: bob.address});97    await collection.transfer(bob, {Substrate: alice.address}, 1n);9899    expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(9n);100    expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1n);101  });102103  itSub.ifWithPallets('[refungible] Collection admin can transfer owned token', [Pallets.ReFungible], async ({helper}) => {104    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-2-RFT', description: '', tokenPrefix: 'T'});105    await collection.addAdmin(alice, {Substrate: bob.address});106107    const rft = await collection.mintToken(bob, 10n, {Substrate: bob.address});108    await rft.transfer(bob, {Substrate: alice.address}, 1n);109110    expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(9n);111    expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(1n);112  });113});114115describe('Negative Integration Test Transfer(recipient, collection_id, item_id, value)', () => {116  let alice: IKeyringPair;117  let bob: IKeyringPair;118119  before(async () => {120    await usingPlaygrounds(async (helper, privateKey) => {121      const donor = await privateKey({url: import.meta.url});122      [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);123    });124  });125126127  itSub('[nft] Transfer with not existed collection_id', async ({helper}) => {128    await expect(helper.nft.transferToken(alice, NON_EXISTENT_COLLECTION_ID, 1, {Substrate: bob.address}))129      .to.be.rejectedWith(/common\.CollectionNotFound/);130  });131132  itSub('[fungible] Transfer with not existed collection_id', async ({helper}) => {133    await expect(helper.ft.transfer(alice, NON_EXISTENT_COLLECTION_ID, {Substrate: bob.address}))134      .to.be.rejectedWith(/common\.CollectionNotFound/);135  });136137  itSub.ifWithPallets('[refungible] Transfer with not existed collection_id', [Pallets.ReFungible], async ({helper}) => {138    await expect(helper.rft.transferToken(alice, NON_EXISTENT_COLLECTION_ID, 1, {Substrate: bob.address}))139      .to.be.rejectedWith(/common\.CollectionNotFound/);140  });141142  itSub('[nft] Transfer with deleted collection_id', async ({helper}) => {143    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-1-NFT', description: '', tokenPrefix: 'T'});144    const nft = await collection.mintToken(alice);145146    await nft.burn(alice);147    await collection.burn(alice);148149    await expect(nft.transfer(alice, {Substrate: bob.address}))150      .to.be.rejectedWith(/common\.CollectionNotFound/);151  });152153  itSub('[fungible] Transfer with deleted collection_id', async ({helper}) => {154    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-1-FT', description: '', tokenPrefix: 'T'});155    await collection.mint(alice, 10n);156157    await collection.burnTokens(alice, 10n);158    await collection.burn(alice);159160    await expect(collection.transfer(alice, {Substrate: bob.address}))161      .to.be.rejectedWith(/common\.CollectionNotFound/);162  });163164  itSub.ifWithPallets('[refungible] Transfer with deleted collection_id', [Pallets.ReFungible], async ({helper}) => {165    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-1-RFT', description: '', tokenPrefix: 'T'});166    const rft = await collection.mintToken(alice, 10n);167168    await rft.burn(alice, 10n);169    await collection.burn(alice);170171    await expect(rft.transfer(alice, {Substrate: bob.address}))172      .to.be.rejectedWith(/common\.CollectionNotFound/);173  });174175  itSub('[nft] Transfer with not existed item_id', async ({helper}) => {176    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-2-NFT', description: '', tokenPrefix: 'T'});177    await expect(collection.transferToken(alice, 1, {Substrate: bob.address}))178      .to.be.rejectedWith(/common\.TokenNotFound/);179  });180181  itSub('[fungible] Transfer with not existed item_id', async ({helper}) => {182    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-2-FT', description: '', tokenPrefix: 'T'});183    await expect(collection.transfer(alice, {Substrate: bob.address}))184      .to.be.rejectedWith(/common\.TokenValueTooLow/);185  });186187  itSub.ifWithPallets('[refungible] Transfer with not existed item_id', [Pallets.ReFungible], async ({helper}) => {188    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-2-RFT', description: '', tokenPrefix: 'T'});189    await expect(collection.transferToken(alice, 1, {Substrate: bob.address}))190      .to.be.rejectedWith(/common\.TokenValueTooLow/);191  });192193  itSub('Zero transfer NFT', async ({helper}) => {194    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-3-NFT', description: '', tokenPrefix: 'T'});195    const tokenAlice = await collection.mintToken(alice, {Substrate: alice.address});196    const tokenBob = await collection.mintToken(alice, {Substrate: bob.address});197    // 1. Zero transfer of own tokens allowed:198    await helper.executeExtrinsic(alice, 'api.tx.unique.transfer', [{Substrate: bob.address}, collection.collectionId, tokenAlice.tokenId, 0]);199    // 2. Zero transfer of non-owned tokens not allowed:200    await expect(helper.executeExtrinsic(alice, 'api.tx.unique.transfer', [{Substrate: alice.address}, collection.collectionId, tokenBob.tokenId, 0])).to.be.rejectedWith('common.NoPermission');201    // 3. Zero transfer of non-existing tokens not allowed:202    await expect(helper.executeExtrinsic(alice, 'api.tx.unique.transfer', [{Substrate: alice.address}, collection.collectionId, 10, 0])).to.be.rejectedWith('common.TokenNotFound');203    expect(await tokenAlice.getOwner()).to.deep.eq({Substrate: alice.address});204    expect(await tokenBob.getOwner()).to.deep.eq({Substrate: bob.address});205    // 4. Storage is not corrupted:206    await tokenAlice.transfer(alice, {Substrate: bob.address});207    await tokenBob.transfer(bob, {Substrate: alice.address});208    expect(await tokenAlice.getOwner()).to.deep.eq({Substrate: bob.address});209    expect(await tokenBob.getOwner()).to.deep.eq({Substrate: alice.address});210  });211212  itSub('[nft] Transfer with deleted item_id', async ({helper}) => {213    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-3-NFT', description: '', tokenPrefix: 'T'});214    const nft = await collection.mintToken(alice);215216    await nft.burn(alice);217218    await expect(nft.transfer(alice, {Substrate: bob.address}))219      .to.be.rejectedWith(/common\.TokenNotFound/);220  });221222  itSub('[fungible] Transfer with deleted item_id', async ({helper}) => {223    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-3-FT', description: '', tokenPrefix: 'T'});224    await collection.mint(alice, 10n);225226    await collection.burnTokens(alice, 10n);227228    await expect(collection.transfer(alice, {Substrate: bob.address}))229      .to.be.rejectedWith(/common\.TokenValueTooLow/);230  });231232  itSub.ifWithPallets('[refungible] Transfer with deleted item_id', [Pallets.ReFungible], async ({helper}) => {233    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-3-RFT', description: '', tokenPrefix: 'T'});234    const rft = await collection.mintToken(alice, 10n);235236    await rft.burn(alice, 10n);237238    await expect(rft.transfer(alice, {Substrate: bob.address}))239      .to.be.rejectedWith(/common\.TokenValueTooLow/);240  });241242  itSub('[nft] Transfer with recipient that is not owner', async ({helper}) => {243    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-4-NFT', description: '', tokenPrefix: 'T'});244    const nft = await collection.mintToken(alice);245246    await expect(nft.transfer(bob, {Substrate: bob.address}))247      .to.be.rejectedWith(/common\.NoPermission/);248    expect(await nft.getOwner()).to.be.deep.equal({Substrate: alice.address});249  });250251  itSub('[fungible] Transfer with recipient that is not owner', async ({helper}) => {252    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-4-FT', description: '', tokenPrefix: 'T'});253    await collection.mint(alice, 10n);254255    await expect(collection.transfer(bob, {Substrate: bob.address}, 9n))256      .to.be.rejectedWith(/common\.TokenValueTooLow/);257    expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(0n);258    expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(10n);259  });260261  itSub.ifWithPallets('[refungible] Transfer with recipient that is not owner', [Pallets.ReFungible], async ({helper}) => {262    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-1-RFT', description: '', tokenPrefix: 'T'});263    const rft = await collection.mintToken(alice, 10n);264265    await expect(rft.transfer(bob, {Substrate: bob.address}, 9n))266      .to.be.rejectedWith(/common\.TokenValueTooLow/);267    expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(0n);268    expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(10n);269  });270});271272describe('Transfers to self (potentially over substrate-evm boundary)', () => {273  let donor: IKeyringPair;274275  before(async function() {276    await usingEthPlaygrounds(async (_, privateKey) => {277      donor = await privateKey({url: import.meta.url});278    });279  });280281  itEth('Transfers to self. In case of same frontend', async ({helper}) => {282    const [owner] = await helper.arrange.createAccounts([10n], donor);283    const collection = await helper.ft.mintCollection(owner, {});284    await collection.mint(owner, 100n);285286    const ownerProxy = helper.address.substrateToEth(owner.address);287288    // transfer to own proxy289    await collection.transfer(owner, {Ethereum: ownerProxy}, 10n);290    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);291    expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);292293    // transfer-from own proxy to own proxy again294    await collection.transferFrom(owner, {Ethereum: ownerProxy}, {Ethereum: ownerProxy}, 5n);295    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);296    expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);297  });298299  itEth('Transfers to self. In case of substrate-evm boundary', async ({helper}) => {300    const [owner] = await helper.arrange.createAccounts([10n], donor);301    const collection = await helper.ft.mintCollection(owner, {});302    await collection.mint(owner, 100n);303304    const ownerProxy = helper.address.substrateToEth(owner.address);305306    // transfer to own proxy307    await collection.transfer(owner, {Ethereum: ownerProxy}, 10n);308    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);309    expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);310311    // transfer-from own proxy to self312    await collection.transferFrom(owner, {Ethereum: ownerProxy}, {Substrate: owner.address}, 5n);313    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(95n);314    expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(5n);315  });316317  itEth('Transfers to self. In case of inside substrate-evm', async ({helper}) => {318    const [owner] = await helper.arrange.createAccounts([10n], donor);319    const collection = await helper.ft.mintCollection(owner, {});320    await collection.mint(owner, 100n);321322    // transfer to self again323    await collection.transfer(owner, {Substrate: owner.address}, 10n);324    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(100n);325326    // transfer-from self to self again327    await collection.transferFrom(owner, {Substrate: owner.address}, {Substrate: owner.address}, 5n);328    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(100n);329  });330331  itEth('Transfers to self. In case of inside substrate-evm when not enought "Fungibles"', async ({helper}) => {332    const [owner] = await helper.arrange.createAccounts([10n], donor);333    const collection = await helper.ft.mintCollection(owner, {});334    await collection.mint(owner, 10n);335336    // transfer to self again337    await expect(collection.transfer(owner, {Substrate: owner.address}, 11n))338      .to.be.rejectedWith(/common\.TokenValueTooLow/);339340    // transfer-from self to self again341    await expect(collection.transferFrom(owner, {Substrate: owner.address}, {Substrate: owner.address}, 12n))342      .to.be.rejectedWith(/common\.TokenValueTooLow/);343    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(10n);344  });345});
modifiedtests/src/transferFrom.test.tsdiffbeforeafterboth
--- a/tests/src/transferFrom.test.ts
+++ b/tests/src/transferFrom.test.ts
@@ -16,6 +16,7 @@
 
 import {IKeyringPair} from '@polkadot/types/types';
 import {itSub, Pallets, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
 
 describe('Integration Test transferFrom(from, recipient, collection_id, item_id, value):', () => {
   let alice: IKeyringPair;
@@ -97,10 +98,9 @@
   });
 
   itSub('transferFrom for a collection that does not exist', async ({helper}) => {
-    const collectionId = (1 << 32) - 1;
-    await expect(helper.collection.approveToken(alice, collectionId, 0, {Substrate: bob.address}, 1n))
+    await expect(helper.collection.approveToken(alice, NON_EXISTENT_COLLECTION_ID, 0, {Substrate: bob.address}, 1n))
       .to.be.rejectedWith(/common\.CollectionNotFound/);
-    await expect(helper.collection.transferTokenFrom(bob, collectionId, 0, {Substrate: alice.address}, {Substrate: bob.address}, 1n))
+    await expect(helper.collection.transferTokenFrom(bob, NON_EXISTENT_COLLECTION_ID, 0, {Substrate: alice.address}, {Substrate: bob.address}, 1n))
       .to.be.rejectedWith(/common\.CollectionNotFound/);
   });
 
modifiedtests/src/util/playgrounds/types.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/types.ts
+++ b/tests/src/util/playgrounds/types.ts
@@ -3,6 +3,8 @@
 
 import {IKeyringPair} from '@polkadot/types/types';
 
+export const NON_EXISTENT_COLLECTION_ID = 4_294_967_295;
+
 export interface IEvent {
   section: string;
   method: string;
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -7,11 +7,8 @@
 
 import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';
 import {SignerOptions} from '@polkadot/api/types/submittable';
-import '../../interfaces/augment-api-tx';
+import '../../interfaces/augment-api';
 import {AugmentedSubmittables} from '@polkadot/api-base/types/submittable';
-import {RpcInterface} from '@polkadot/rpc-core/types';
-import {QueryableStorage} from '@polkadot/api-base/types/storage';
-import {DecoratedRpc} from '@polkadot/api-base/types/rpc';
 import {ApiInterfaceEvents} from '@polkadot/api/types';
 import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm, base58Encode, blake2AsU8a} from '@polkadot/util-crypto';
 import {IKeyringPair} from '@polkadot/types/types';
@@ -50,7 +47,7 @@
 } from './types';
 import {RuntimeDispatchInfo} from '@polkadot/types/interfaces';
 import type {Vec} from '@polkadot/types-codec';
-import {FrameSystemEventRecord} from '@polkadot/types/lookup';
+import {FrameSystemEventRecord, PalletBalancesIdAmount} from '@polkadot/types/lookup';
 
 export class CrossAccountId {
   Substrate!: TSubstrateAccount;
@@ -379,15 +376,6 @@
 type Get2<T, P extends string, E> =
   P extends `${infer Key}.${infer Key2}` ? Key extends keyof T ? Key2 extends keyof T[Key] ? T[Key][Key2] : E : E : E;
 type ForceFunction<T> = T extends (...args: any) => any ? T : (...args: any) => Invalid<'not a function'>;
-type ReturnTypeWithArgs<T extends (...args: any[]) => any, ARGS_T> =
-  Extract<
-    T extends { (...args: infer A1): infer R1; (...args: infer A2): infer R2; (...args: infer A3): infer R3; (...args: infer A4): infer R4; } ? [A1, R1] | [A2, R2] | [A3, R3] | [A4, R4] :
-    T extends { (...args: infer A1): infer R1; (...args: infer A2): infer R2; (...args: infer A3): infer R3; } ? [A1, R1] | [A2, R2] | [A3, R3] :
-    T extends { (...args: infer A1): infer R1; (...args: infer A2): infer R2; } ? [A1, R1] | [A2, R2] :
-    T extends { (...args: infer A1): infer R1; } ? [A1, R1] :
-    never,
-    [ARGS_T, any]
-  >[1]
 
 export class ChainHelperBase {
   helperBase: any;
@@ -498,7 +486,7 @@
 
     if (xcmChains.indexOf(spec.specName) > -1) return spec.specName;
 
-    if (['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;
+    if (['quartz', 'unique', 'sapphire'].indexOf(spec.specName) > -1) return spec.specName;
     return 'opal';
   }
 
@@ -677,12 +665,12 @@
   async executeExtrinsic<
     E extends string,
     V extends (
-...args: any) => any = ForceFunction<
-      Get2<
-        AugmentedSubmittables<'promise'>,
-        E, (...args: any) => Invalid<'not found'>
+      ...args: any) => any = ForceFunction<
+        Get2<
+          AugmentedSubmittables<'promise'>,
+          E, (...args: any) => Invalid<'not found'>
+        >
       >
-    >
   >(
     sender: TSigner,
     extrinsic: `api.tx.${E}`,
@@ -1756,9 +1744,7 @@
       children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);
     }
 
-    return children.toJSON().map((x: any) => {
-      return {collectionId: x.collection, tokenId: x.token};
-    });
+    return children.toJSON().map((x: any) => ({collectionId: x.collection, tokenId: x.token}));
   }
 
   /**
@@ -2400,14 +2386,22 @@
     return {free: accountInfo.free.toBigInt(), frozen: accountInfo.frozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};
   }
 
+  /**
+   * Get total issuance
+   * @returns
+   */
+  async getTotalIssuance(): Promise<bigint> {
+    const total = (await this.helper.callRpc('api.query.balances.totalIssuance', []));
+    return total.toBigInt();
+  }
+
   async getLocked(address: TSubstrateAccount): Promise<[{ id: string, amount: bigint, reason: string }]> {
     const locks = (await this.helper.callRpc('api.query.balances.locks', [address])).toHuman();
-    return locks.map((lock: any) => { return {id: lock.id, amount: BigInt(lock.amount.replace(/,/g, '')), reasons: lock.reasons}; });
+    return locks.map((lock: any) => ({id: lock.id, amount: BigInt(lock.amount.replace(/,/g, '')), reasons: lock.reasons}));
   }
-
-  async getFrozen(address: TSubstrateAccount): Promise<[{ id: string, amount: bigint}]> {
-    const locks = (await this.helper.callRpc('api.query.balances.freezes', [address])).toHuman();
-    return locks.map((lock: any) => { return {id: lock.id, amount: BigInt(lock.amount.replace(/,/g, ''))}; });
+  async getFrozen(address: TSubstrateAccount): Promise<{ id: string, amount: bigint }[]> {
+    const locks = await this.helper.api!.query.balances.freezes(address);
+    return locks.map(lock => ({id: lock.id.toString(), amount: lock.amount.toBigInt()}));
   }
 }
 
@@ -2493,9 +2487,18 @@
   }
 
   /**
+   * Get total issuance
+   * @returns
+   */
+  getTotalIssuance(): Promise<bigint> {
+    return this.subBalanceGroup.getTotalIssuance();
+  }
+
+  /**
    * Get locked balances
    * @param address substrate address
    * @returns locked balances with reason via api.query.balances.locks
+   * @deprecated all the methods should switch to getFrozen
    */
   getLocked(address: TSubstrateAccount) {
     return this.subBalanceGroup.getLocked(address);
modifiedtests/src/vesting.test.tsdiffbeforeafterboth
--- a/tests/src/vesting.test.ts
+++ b/tests/src/vesting.test.ts
@@ -103,7 +103,7 @@
     expect(balanceSender.reserved).to.eq(0n);
   });
 
-  itSub.only('cannot send more tokens than have', async ({helper}) => {
+  itSub('cannot send more tokens than have', async ({helper}) => {
     const [sender, receiver] = await helper.arrange.createAccounts([1000n, 1n], donor);
     const schedule = {start: 0n, period: 1n, periodCount: 1n, perPeriod: 100n * nominal};
     const manyPeriodsSchedule = {start: 0n, period: 1n, periodCount: 100n, perPeriod: 10n * nominal};