git.delta.rocks / unique-network / refs/commits / 64ceec5bba92

difftreelog

Merge pull request #298 from UniqueNetwork/feature/create-multiple-items-ex

kozyrevdev2022-03-01parents: #c9e84df #9d95058.patch.diff
in: master
Add createMultipleItemsEx call

27 files changed

modified.maintain/frame-weight-template.hbsdiffbeforeafterboth
--- a/.maintain/frame-weight-template.hbs
+++ b/.maintain/frame-weight-template.hbs
@@ -7,7 +7,7 @@
 //! EXECUTION: {{cmd.execution}}, WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}}
 
 // Executed Command:
-{{#each args as |arg|~}}
+{{#each args as |arg|}}
 // {{arg}}
 {{/each}}
 
@@ -21,76 +21,80 @@
 
 /// Weight functions needed for {{pallet}}.
 pub trait WeightInfo {
-	{{~#each benchmarks as |benchmark|}}
+	{{#each benchmarks as |benchmark|}}
 	fn {{benchmark.name~}}
 	(
 		{{~#each benchmark.components as |c| ~}}
 		{{c.name}}: u32, {{/each~}}
 	) -> Weight;
-	{{~/each}}
+	{{/each}}
 }
 
 /// Weights for {{pallet}} using the Substrate node and recommended hardware.
 pub struct SubstrateWeight<T>(PhantomData<T>);
+{{#if (eq pallet "frame_system")}}
+impl<T: crate::Config> WeightInfo for SubstrateWeight<T> {
+{{else}}
 impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
-	{{~#each benchmarks as |benchmark|}}
-	{{~#each benchmark.comments as |comment|}}
+{{/if}}
+	{{#each benchmarks as |benchmark|}}
+	{{#each benchmark.comments as |comment|}}
 	// {{comment}}
-	{{~/each}}
+	{{/each}}
 	fn {{benchmark.name~}}
 	(
 		{{~#each benchmark.components as |c| ~}}
 		{{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}}
 	) -> Weight {
 		({{underscore benchmark.base_weight}} as Weight)
-			{{~#each benchmark.component_weight as |cw|}}
+			{{#each benchmark.component_weight as |cw|}}
 			// Standard Error: {{underscore cw.error}}
 			.saturating_add(({{underscore cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight))
-			{{~/each}}
-			{{~#if (ne benchmark.base_reads "0")}}
+			{{/each}}
+			{{#if (ne benchmark.base_reads "0")}}
 			.saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}} as Weight))
-			{{~/if}}
-			{{~#each benchmark.component_reads as |cr|}}
+			{{/if}}
+			{{#each benchmark.component_reads as |cr|}}
 			.saturating_add(T::DbWeight::get().reads(({{cr.slope}} as Weight).saturating_mul({{cr.name}} as Weight)))
-			{{~/each}}
-			{{~#if (ne benchmark.base_writes "0")}}
+			{{/each}}
+			{{#if (ne benchmark.base_writes "0")}}
 			.saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}} as Weight))
-			{{~/if}}
-			{{~#each benchmark.component_writes as |cw|}}
+			{{/if}}
+			{{#each benchmark.component_writes as |cw|}}
 			.saturating_add(T::DbWeight::get().writes(({{cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight)))
-			{{~/each}}
+			{{/each}}
 	}
-	{{~/each}}
+	{{/each}}
 }
 
 // For backwards compatibility and tests
 impl WeightInfo for () {
-	{{~#each benchmarks as |benchmark|}}
-	{{~#each benchmark.comments as |comment|}}
+	{{#each benchmarks as |benchmark|}}
+	{{#each benchmark.comments as |comment|}}
 	// {{comment}}
-	{{~/each}}
+	{{/each}}
 	fn {{benchmark.name~}}
 	(
 		{{~#each benchmark.components as |c| ~}}
 		{{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}}
 	) -> Weight {
 		({{underscore benchmark.base_weight}} as Weight)
-			{{~#each benchmark.component_weight as |cw|}}
+			{{#each benchmark.component_weight as |cw|}}
 			// Standard Error: {{underscore cw.error}}
 			.saturating_add(({{underscore cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight))
-			{{~/each}}
-			{{~#if (ne benchmark.base_reads "0")}}
+			{{/each}}
+			{{#if (ne benchmark.base_reads "0")}}
 			.saturating_add(RocksDbWeight::get().reads({{benchmark.base_reads}} as Weight))
-			{{~/if}}
-			{{~#each benchmark.component_reads as |cr|}}
+			{{/if}}
+			{{#each benchmark.component_reads as |cr|}}
 			.saturating_add(RocksDbWeight::get().reads(({{cr.slope}} as Weight).saturating_mul({{cr.name}} as Weight)))
-			{{~/each}}
-			{{~#if (ne benchmark.base_writes "0")}}
+			{{/each}}
+			{{#if (ne benchmark.base_writes "0")}}
 			.saturating_add(RocksDbWeight::get().writes({{benchmark.base_writes}} as Weight))
-			{{~/if}}
-			{{~#each benchmark.component_writes as |cw|}}
+			{{/if}}
+			{{#each benchmark.component_writes as |cw|}}
 			.saturating_add(RocksDbWeight::get().writes(({{cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight)))
-			{{~/each}}
+			{{/each}}
 	}
-	{{~/each}}
+	{{/each}}
 }
modifiedpallets/common/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/common/src/benchmarking.rs
+++ b/pallets/common/src/benchmarking.rs
@@ -1,64 +1,87 @@
 use sp_std::vec::Vec;
 use crate::{Config, CollectionHandle};
 use up_data_structs::{
-	CollectionMode, Collection, CollectionId, MAX_COLLECTION_NAME_LENGTH,
+	CollectionMode, CreateCollectionData, CollectionId, MAX_COLLECTION_NAME_LENGTH,
 	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, OFFCHAIN_SCHEMA_LIMIT,
 	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT,
 };
-use frame_support::traits::{Currency, Get};
+use frame_support::{
+	traits::{Currency, Get},
+	pallet_prelude::ConstU32,
+	BoundedVec,
+};
 use core::convert::TryInto;
 use sp_runtime::DispatchError;
 
-pub fn create_data(size: usize) -> Vec<u8> {
-	(0..size).map(|v| (v & 0xff) as u8).collect()
+pub fn create_data<const S: u32>() -> BoundedVec<u8, ConstU32<S>> {
+	create_var_data::<S>(S)
 }
-pub fn create_u16_data(size: usize) -> Vec<u16> {
-	(0..size).map(|v| (v & 0xffff) as u16).collect()
+pub fn create_u16_data<const S: u32>() -> BoundedVec<u16, ConstU32<S>> {
+	(0..S)
+		.map(|v| (v & 0xffff) as u16)
+		.collect::<Vec<_>>()
+		.try_into()
+		.unwrap()
+}
+pub fn create_var_data<const S: u32>(size: u32) -> BoundedVec<u8, ConstU32<S>> {
+	assert!(
+		size <= S,
+		"size ({}) should be less within bound ({})",
+		size,
+		S
+	);
+	(0..size)
+		.map(|v| (v & 0xff) as u8)
+		.collect::<Vec<_>>()
+		.try_into()
+		.unwrap()
 }
 
 pub fn create_collection_raw<T: Config, R>(
 	owner: T::AccountId,
 	mode: CollectionMode,
-	handler: impl FnOnce(Collection<T::AccountId>) -> Result<CollectionId, DispatchError>,
+	handler: impl FnOnce(
+		T::AccountId,
+		CreateCollectionData<T::AccountId>,
+	) -> Result<CollectionId, DispatchError>,
 	cast: impl FnOnce(CollectionHandle<T>) -> R,
 ) -> Result<R, DispatchError> {
 	T::Currency::deposit_creating(&owner, T::CollectionCreationPrice::get());
-	let name = create_u16_data(MAX_COLLECTION_NAME_LENGTH)
-		.try_into()
-		.unwrap();
-	let description = create_u16_data(MAX_COLLECTION_DESCRIPTION_LENGTH)
-		.try_into()
-		.unwrap();
-	let token_prefix = create_data(MAX_TOKEN_PREFIX_LENGTH).try_into().unwrap();
-	let offchain_schema = create_data(OFFCHAIN_SCHEMA_LIMIT as usize)
-		.try_into()
-		.unwrap();
-	let variable_on_chain_schema = create_data(VARIABLE_ON_CHAIN_SCHEMA_LIMIT as usize)
-		.try_into()
-		.unwrap();
-	let const_on_chain_schema = create_data(CONST_ON_CHAIN_SCHEMA_LIMIT as usize)
-		.try_into()
-		.unwrap();
-	handler(Collection {
+	let name = create_u16_data::<MAX_COLLECTION_NAME_LENGTH>();
+	let description = create_u16_data::<MAX_COLLECTION_DESCRIPTION_LENGTH>();
+	let token_prefix = create_data::<MAX_TOKEN_PREFIX_LENGTH>();
+	let offchain_schema = create_data::<OFFCHAIN_SCHEMA_LIMIT>();
+	let variable_on_chain_schema = create_data::<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>();
+	let const_on_chain_schema = create_data::<CONST_ON_CHAIN_SCHEMA_LIMIT>();
+	handler(
 		owner,
-		mode,
-		access: Default::default(),
-		name,
-		description,
-		token_prefix,
-		mint_mode: true,
-		offchain_schema,
-		schema_version: Default::default(),
-		sponsorship: Default::default(),
-		limits: Default::default(),
-		variable_on_chain_schema,
-		const_on_chain_schema,
-		meta_update_permission: Default::default(),
-	})
+		CreateCollectionData {
+			mode,
+			name,
+			description,
+			token_prefix,
+			offchain_schema,
+			variable_on_chain_schema,
+			const_on_chain_schema,
+			..Default::default()
+		},
+	)
 	.and_then(CollectionHandle::try_get)
 	.map(cast)
 }
 
+/// Helper macros, which handles all benchmarking preparation in semi-declarative way
+///
+/// `name` is a substrate account
+/// - name: sub[(id)]
+/// `name` is a collection with owner `owner`
+/// - name: collection(owner)
+/// `name` is a cross account based on substrate
+/// - name: cross_sub[(id)]
+/// `name` is a cross account, which maps to substrate account `name`
+/// - name: cross_from_sub
+/// `name` is a cross account, which maps to substrate account `other_name`
+/// - name: cross_from_sub(other_name)
 #[macro_export]
 macro_rules! bench_init {
 	($name:ident: sub $(($id:expr))?; $($rest:tt)*) => {
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -17,7 +17,7 @@
 	TokenId, Weight, WithdrawReasons, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode,
 	NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
 	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,
-	CustomDataLimit, CreateCollectionData, SponsorshipState,
+	CustomDataLimit, CreateCollectionData, SponsorshipState, CreateItemExData,
 };
 pub use pallet::*;
 use sp_core::H160;
@@ -624,9 +624,10 @@
 }
 
 /// Worst cases
-pub trait CommonWeightInfo {
+pub trait CommonWeightInfo<CrossAccountId> {
 	fn create_item() -> Weight;
 	fn create_multiple_items(amount: u32) -> Weight;
+	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;
 	fn burn_item() -> Weight;
 	fn transfer() -> Weight;
 	fn approve() -> Weight;
@@ -648,6 +649,11 @@
 		to: T::CrossAccountId,
 		data: Vec<CreateItemData>,
 	) -> DispatchResultWithPostInfo;
+	fn create_multiple_items_ex(
+		&self,
+		sender: T::CrossAccountId,
+		data: CreateItemExData<T::CrossAccountId>,
+	) -> DispatchResultWithPostInfo;
 	fn burn_item(
 		&self,
 		sender: T::CrossAccountId,
modifiedpallets/fungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/fungible/src/benchmarking.rs
+++ b/pallets/fungible/src/benchmarking.rs
@@ -4,7 +4,7 @@
 use sp_std::prelude::*;
 use pallet_common::benchmarking::create_collection_raw;
 use frame_benchmarking::{benchmarks, account};
-use up_data_structs::{CollectionMode};
+use up_data_structs::{CollectionMode, MAX_ITEMS_PER_BATCH};
 use pallet_common::bench_init;
 
 const SEED: u32 = 1;
@@ -26,6 +26,18 @@
 		};
 	}: {<Pallet<T>>::create_item(&collection, &sender, (to, 200))?}
 
+	create_multiple_items_ex {
+		let b in 0..MAX_ITEMS_PER_BATCH;
+		bench_init!{
+			owner: sub; collection: collection(owner);
+			sender: cross_from_sub(owner);
+		};
+		let data = (0..b).map(|i| {
+			bench_init!(to: cross_sub(i););
+			(to, 200)
+		}).collect::<BTreeMap<_, _>>().try_into().unwrap();
+	}: {<Pallet<T>>::create_multiple_items(&collection, &sender, data)}
+
 	burn_item {
 		bench_init!{
 			owner: sub; collection: collection(owner);
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -1,7 +1,7 @@
 use core::marker::PhantomData;
 
 use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, BoundedVec};
-use up_data_structs::TokenId;
+use up_data_structs::{TokenId, CreateItemExData};
 use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
 use sp_runtime::ArithmeticError;
 use sp_std::{vec::Vec, vec};
@@ -12,7 +12,7 @@
 };
 
 pub struct CommonWeights<T: Config>(PhantomData<T>);
-impl<T: Config> CommonWeightInfo for CommonWeights<T> {
+impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {
 	fn create_item() -> Weight {
 		<SelfWeightOf<T>>::create_item()
 	}
@@ -21,6 +21,15 @@
 		Self::create_item()
 	}
 
+	fn create_multiple_items_ex(data: &CreateItemExData<T::CrossAccountId>) -> Weight {
+		match data {
+			CreateItemExData::Fungible(f) => {
+				<SelfWeightOf<T>>::create_multiple_items_ex(f.len() as u32)
+			}
+			_ => 0,
+		}
+	}
+
 	fn burn_item() -> Weight {
 		<SelfWeightOf<T>>::burn_item()
 	}
@@ -87,6 +96,23 @@
 		)
 	}
 
+	fn create_multiple_items_ex(
+		&self,
+		sender: <T>::CrossAccountId,
+		data: up_data_structs::CreateItemExData<<T>::CrossAccountId>,
+	) -> DispatchResultWithPostInfo {
+		let weight = <CommonWeights<T>>::create_multiple_items_ex(&data);
+		let data = match data {
+			up_data_structs::CreateItemExData::Fungible(f) => f,
+			_ => fail!(<Error<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
+		};
+
+		with_weight(
+			<Pallet<T>>::create_multiple_items(self, &sender, data.into_inner()),
+			weight,
+		)
+	}
+
 	fn burn_item(
 		&self,
 		sender: T::CrossAccountId,
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -9,7 +9,7 @@
 use pallet_evm_coder_substrate::WithRecorder;
 use sp_core::H160;
 use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
-use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};
+use sp_std::collections::btree_map::BTreeMap;
 
 pub use pallet::*;
 
@@ -222,7 +222,7 @@
 	pub fn create_multiple_items(
 		collection: &FungibleHandle<T>,
 		sender: &T::CrossAccountId,
-		data: Vec<CreateItemData<T>>,
+		data: BTreeMap<T::CrossAccountId, u128>,
 	) -> DispatchResult {
 		if !collection.is_owner_or_admin(sender) {
 			ensure!(
@@ -235,23 +235,19 @@
 				collection.check_allowlist(owner)?;
 			}
 		}
-
-		let mut balances = BTreeMap::new();
 
 		let total_supply = data
 			.iter()
-			.map(|u| u.1)
+			.map(|(_, v)| *v)
 			.try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {
 				acc.checked_add(v)
 			})
 			.ok_or(ArithmeticError::Overflow)?;
 
-		for (user, amount) in data.into_iter() {
-			let balance = balances
-				.entry(user.clone())
-				.or_insert_with(|| <Balance<T>>::get((collection.id, user)));
-			*balance = (*balance)
-				.checked_add(amount)
+		let mut balances = data;
+		for (k, v) in balances.iter_mut() {
+			*v = <Balance<T>>::get((collection.id, &k))
+				.checked_add(*v)
 				.ok_or(ArithmeticError::Overflow)?;
 		}
 
@@ -396,6 +392,6 @@
 		sender: &T::CrossAccountId,
 		data: CreateItemData<T>,
 	) -> DispatchResult {
-		Self::create_multiple_items(collection, sender, vec![data])
+		Self::create_multiple_items(collection, sender, [(data.0, data.1)].into_iter().collect())
 	}
 }
modifiedpallets/fungible/src/weights.rsdiffbeforeafterboth
--- a/pallets/fungible/src/weights.rs
+++ b/pallets/fungible/src/weights.rs
@@ -33,6 +33,7 @@
 /// Weight functions needed for pallet_fungible.
 pub trait WeightInfo {
 	fn create_item() -> Weight;
+	fn create_multiple_items_ex(b: u32, ) -> Weight;
 	fn burn_item() -> Weight;
 	fn transfer() -> Weight;
 	fn approve() -> Weight;
@@ -51,6 +52,17 @@
 			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 	}
 	// Storage: Fungible TotalSupply (r:1 w:1)
+	// Storage: Fungible Balance (r:4 w:4)
+	fn create_multiple_items_ex(b: u32, ) -> Weight {
+		(1_055_000 as Weight)
+			// Standard Error: 22_000
+			.saturating_add((5_273_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(b as Weight)))
+	}
+	// Storage: Fungible TotalSupply (r:1 w:1)
 	// Storage: Fungible Balance (r:1 w:1)
 	fn burn_item() -> Weight {
 		(14_096_000 as Weight)
@@ -97,6 +109,17 @@
 			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 	}
 	// Storage: Fungible TotalSupply (r:1 w:1)
+	// Storage: Fungible Balance (r:4 w:4)
+	fn create_multiple_items_ex(b: u32, ) -> Weight {
+		(1_055_000 as Weight)
+			// Standard Error: 22_000
+			.saturating_add((5_273_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+			.saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(b as Weight)))
+	}
+	// Storage: Fungible TotalSupply (r:1 w:1)
 	// Storage: Fungible Balance (r:1 w:1)
 	fn burn_item() -> Weight {
 		(14_096_000 as Weight)
modifiedpallets/inflation/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/inflation/src/benchmarking.rs
+++ b/pallets/inflation/src/benchmarking.rs
@@ -1,7 +1,7 @@
 #![cfg(feature = "runtime-benchmarks")]
 
 use super::*;
-use crate::Module as Inflation;
+use crate::Pallet as Inflation;
 
 use frame_benchmarking::{benchmarks};
 use frame_support::traits::OnInitialize;
modifiedpallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -2,18 +2,18 @@
 use crate::{Pallet, Config, NonfungibleHandle};
 
 use sp_std::prelude::*;
-use pallet_common::benchmarking::{create_collection_raw, create_data};
+use pallet_common::benchmarking::{create_collection_raw, create_data, create_var_data};
 use frame_benchmarking::{benchmarks, account};
-use up_data_structs::{CollectionMode, MAX_ITEMS_PER_BATCH};
+use up_data_structs::{CollectionMode, MAX_ITEMS_PER_BATCH, CUSTOM_DATA_LIMIT};
 use pallet_common::bench_init;
 use core::convert::TryInto;
 
 const SEED: u32 = 1;
 
 fn create_max_item_data<T: Config>(owner: T::CrossAccountId) -> CreateItemData<T> {
-	let const_data = create_data(CUSTOM_DATA_LIMIT as usize).try_into().unwrap();
-	let variable_data = create_data(CUSTOM_DATA_LIMIT as usize).try_into().unwrap();
-	CreateItemData {
+	let const_data = create_data::<CUSTOM_DATA_LIMIT>();
+	let variable_data = create_data::<CUSTOM_DATA_LIMIT>();
+	CreateItemData::<T> {
 		const_data,
 		variable_data,
 		owner,
@@ -24,7 +24,7 @@
 	sender: &T::CrossAccountId,
 	owner: T::CrossAccountId,
 ) -> Result<TokenId, DispatchError> {
-	<Pallet<T>>::create_item(&collection, sender, create_max_item_data(owner))?;
+	<Pallet<T>>::create_item(&collection, sender, create_max_item_data::<T>(owner))?;
 	Ok(TokenId(<TokensMinted<T>>::get(&collection.id)))
 }
 
@@ -53,7 +53,19 @@
 			owner: sub; collection: collection(owner);
 			sender: cross_from_sub(owner); to: cross_sub;
 		};
-		let data = (0..b).map(|_| create_max_item_data(to.clone())).collect();
+		let data = (0..b).map(|_| create_max_item_data::<T>(to.clone())).collect();
+	}: {<Pallet<T>>::create_multiple_items(&collection, &sender, data)?}
+
+	create_multiple_items_ex {
+		let b in 0..MAX_ITEMS_PER_BATCH;
+		bench_init!{
+			owner: sub; collection: collection(owner);
+			sender: cross_from_sub(owner);
+		};
+		let data = (0..b).map(|i| {
+			bench_init!(to: cross_sub(i););
+			create_max_item_data::<T>(to)
+		}).collect();
 	}: {<Pallet<T>>::create_multiple_items(&collection, &sender, data)?}
 
 	burn_item {
@@ -105,6 +117,6 @@
 			owner: cross_from_sub; sender: cross_sub;
 		};
 		let item = create_max_item(&collection, &owner, sender.clone())?;
-		let data = create_data(b as usize);
+		let data = create_var_data(b).try_into().unwrap();
 	}: {<Pallet<T>>::set_variable_metadata(&collection, &sender, item, data)?}
 }
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -1,7 +1,7 @@
 use core::marker::PhantomData;
 
 use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, BoundedVec};
-use up_data_structs::{TokenId, CustomDataLimit};
+use up_data_structs::{TokenId, CustomDataLimit, CreateItemExData};
 use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
 use sp_runtime::DispatchError;
 use sp_std::vec::Vec;
@@ -12,11 +12,18 @@
 };
 
 pub struct CommonWeights<T: Config>(PhantomData<T>);
-impl<T: Config> CommonWeightInfo for CommonWeights<T> {
+impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {
 	fn create_item() -> Weight {
 		<SelfWeightOf<T>>::create_item()
 	}
 
+	fn create_multiple_items_ex(data: &CreateItemExData<T::CrossAccountId>) -> Weight {
+		match data {
+			CreateItemExData::NFT(t) => <SelfWeightOf<T>>::create_multiple_items_ex(t.len() as u32),
+			_ => 0,
+		}
+	}
+
 	fn create_multiple_items(amount: u32) -> Weight {
 		<SelfWeightOf<T>>::create_multiple_items(amount)
 	}
@@ -51,7 +58,7 @@
 	to: &T::CrossAccountId,
 ) -> Result<CreateItemData<T>, DispatchError> {
 	match data {
-		up_data_structs::CreateItemData::NFT(data) => Ok(CreateItemData {
+		up_data_structs::CreateItemData::NFT(data) => Ok(CreateItemData::<T> {
 			const_data: data.const_data,
 			variable_data: data.variable_data,
 			owner: to.clone(),
@@ -68,7 +75,7 @@
 		data: up_data_structs::CreateItemData,
 	) -> DispatchResultWithPostInfo {
 		with_weight(
-			<Pallet<T>>::create_item(self, &sender, map_create_data(data, &to)?),
+			<Pallet<T>>::create_item(self, &sender, map_create_data::<T>(data, &to)?),
 			<CommonWeights<T>>::create_item(),
 		)
 	}
@@ -91,6 +98,23 @@
 		)
 	}
 
+	fn create_multiple_items_ex(
+		&self,
+		sender: <T>::CrossAccountId,
+		data: up_data_structs::CreateItemExData<<T>::CrossAccountId>,
+	) -> DispatchResultWithPostInfo {
+		let weight = <CommonWeights<T>>::create_multiple_items_ex(&data);
+		let data = match data {
+			up_data_structs::CreateItemExData::NFT(nft) => nft,
+			_ => fail!(Error::<T>::NotNonfungibleDataUsedToMintFungibleCollectionToken),
+		};
+
+		with_weight(
+			<Pallet<T>>::create_multiple_items(self, &sender, data.into_inner()),
+			weight,
+		)
+	}
+
 	fn burn_item(
 		&self,
 		sender: T::CrossAccountId,
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -232,7 +232,7 @@
 		<Pallet<T>>::create_item(
 			self,
 			&caller,
-			CreateItemData {
+			CreateItemData::<T> {
 				const_data: BoundedVec::default(),
 				variable_data: BoundedVec::default(),
 				owner: to,
@@ -268,7 +268,7 @@
 		<Pallet<T>>::create_item(
 			self,
 			&caller,
-			CreateItemData {
+			CreateItemData::<T> {
 				const_data: Vec::<u8>::from(token_uri)
 					.try_into()
 					.map_err(|_| "token uri is too long")?,
@@ -376,7 +376,7 @@
 			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;
 		}
 		let data = (0..total_tokens)
-			.map(|_| CreateItemData {
+			.map(|_| CreateItemData::<T> {
 				const_data: BoundedVec::default(),
 				variable_data: BoundedVec::default(),
 				owner: to.clone(),
@@ -409,7 +409,7 @@
 			}
 			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;
 
-			data.push(CreateItemData {
+			data.push(CreateItemData::<T> {
 				const_data: Vec::<u8>::from(token_uri)
 					.try_into()
 					.map_err(|_| "token uri is too long")?,
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -2,7 +2,9 @@
 
 use erc::ERC721Events;
 use frame_support::{BoundedVec, ensure};
-use up_data_structs::{AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData};
+use up_data_structs::{
+	AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,
+};
 use pallet_common::{
 	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId,
 };
@@ -22,11 +24,7 @@
 pub mod erc;
 pub mod weights;
 
-pub struct CreateItemData<T: Config> {
-	pub const_data: BoundedVec<u8, CustomDataLimit>,
-	pub variable_data: BoundedVec<u8, CustomDataLimit>,
-	pub owner: T::CrossAccountId,
-}
+pub type CreateItemData<T> = CreateNftExData<<T as pallet_common::Config>::CrossAccountId>;
 pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
 
 #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
modifiedpallets/nonfungible/src/weights.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/weights.rs
+++ b/pallets/nonfungible/src/weights.rs
@@ -34,6 +34,7 @@
 pub trait WeightInfo {
 	fn create_item() -> Weight;
 	fn create_multiple_items(b: u32, ) -> Weight;
+	fn create_multiple_items_ex(b: u32, ) -> Weight;
 	fn burn_item() -> Weight;
 	fn transfer() -> Weight;
 	fn approve() -> Weight;
@@ -66,6 +67,19 @@
 			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(b as Weight)))
 	}
+	// Storage: Nonfungible TokensMinted (r:1 w:1)
+	// Storage: Nonfungible AccountBalance (r:4 w:4)
+	// Storage: Nonfungible TokenData (r:0 w:4)
+	// Storage: Nonfungible Owned (r:0 w:4)
+	fn create_multiple_items_ex(b: u32, ) -> Weight {
+		(2_090_000 as Weight)
+			// Standard Error: 10_000
+			.saturating_add((9_230_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(b as Weight)))
+	}
 	// Storage: Nonfungible TokenData (r:1 w:1)
 	// Storage: Nonfungible TokensBurnt (r:1 w:1)
 	// Storage: Nonfungible Allowance (r:1 w:0)
@@ -140,6 +154,19 @@
 			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(b as Weight)))
 	}
+	// Storage: Nonfungible TokensMinted (r:1 w:1)
+	// Storage: Nonfungible AccountBalance (r:4 w:4)
+	// Storage: Nonfungible TokenData (r:0 w:4)
+	// Storage: Nonfungible Owned (r:0 w:4)
+	fn create_multiple_items_ex(b: u32, ) -> Weight {
+		(2_090_000 as Weight)
+			// Standard Error: 10_000
+			.saturating_add((9_230_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+			.saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(b as Weight)))
+	}
 	// Storage: Nonfungible TokenData (r:1 w:1)
 	// Storage: Nonfungible TokensBurnt (r:1 w:1)
 	// Storage: Nonfungible Allowance (r:1 w:0)
modifiedpallets/refungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/refungible/src/benchmarking.rs
+++ b/pallets/refungible/src/benchmarking.rs
@@ -2,24 +2,28 @@
 use crate::{Pallet, Config, RefungibleHandle};
 
 use sp_std::prelude::*;
-use pallet_common::benchmarking::{create_collection_raw, create_data};
+use pallet_common::benchmarking::{create_collection_raw, create_data, create_var_data};
 use frame_benchmarking::{benchmarks, account};
-use up_data_structs::{CollectionMode, MAX_ITEMS_PER_BATCH};
+use up_data_structs::{CollectionMode, MAX_ITEMS_PER_BATCH, CUSTOM_DATA_LIMIT};
 use pallet_common::bench_init;
 use core::convert::TryInto;
 use core::iter::IntoIterator;
 
 const SEED: u32 = 1;
 
-fn create_max_item_data<T: Config>(
-	users: impl IntoIterator<Item = (T::CrossAccountId, u128)>,
-) -> CreateItemData<T> {
-	let const_data = create_data(CUSTOM_DATA_LIMIT as usize).try_into().unwrap();
-	let variable_data = create_data(CUSTOM_DATA_LIMIT as usize).try_into().unwrap();
-	CreateItemData {
+fn create_max_item_data<CrossAccountId: Ord>(
+	users: impl IntoIterator<Item = (CrossAccountId, u128)>,
+) -> CreateRefungibleExData<CrossAccountId> {
+	let const_data = create_data::<CUSTOM_DATA_LIMIT>();
+	let variable_data = create_data::<CUSTOM_DATA_LIMIT>();
+	CreateRefungibleExData {
 		const_data,
 		variable_data,
-		users: users.into_iter().collect(),
+		users: users
+			.into_iter()
+			.collect::<BTreeMap<_, _>>()
+			.try_into()
+			.unwrap(),
 	}
 }
 fn create_max_item<T: Config>(
@@ -27,7 +31,8 @@
 	sender: &T::CrossAccountId,
 	users: impl IntoIterator<Item = (T::CrossAccountId, u128)>,
 ) -> Result<TokenId, DispatchError> {
-	<Pallet<T>>::create_item(&collection, sender, create_max_item_data(users))?;
+	let data: CreateRefungibleExData<T::CrossAccountId> = create_max_item_data(users);
+	<Pallet<T>>::create_item(&collection, sender, data)?;
 	Ok(TokenId(<TokensMinted<T>>::get(&collection.id)))
 }
 
@@ -56,6 +61,30 @@
 		let data = (0..b).map(|_| create_max_item_data([(to.clone(), 200)])).collect();
 	}: {<Pallet<T>>::create_multiple_items(&collection, &sender, data)?}
 
+	create_multiple_items_ex_multiple_items {
+		let b in 0..MAX_ITEMS_PER_BATCH;
+		bench_init!{
+			owner: sub; collection: collection(owner);
+			sender: cross_from_sub(owner);
+		};
+		let data = (0..b).map(|t| {
+			bench_init!(to: cross_sub(t););
+			create_max_item_data([(to, 200)])
+		}).collect();
+	}: {<Pallet<T>>::create_multiple_items(&collection, &sender, data)?}
+
+	create_multiple_items_ex_multiple_owners {
+		let b in 0..MAX_ITEMS_PER_BATCH;
+		bench_init!{
+			owner: sub; collection: collection(owner);
+			sender: cross_from_sub(owner);
+		};
+		let data = vec![create_max_item_data((0..b).map(|u| {
+			bench_init!(to: cross_sub(u););
+			(to, 200)
+		}))].try_into().unwrap();
+	}: {<Pallet<T>>::create_multiple_items(&collection, &sender, data)?}
+
 	// Other user left, token data is kept
 	burn_item_partial {
 		bench_init!{
@@ -166,6 +195,6 @@
 			sender: cross_from_sub(owner);
 		};
 		let item = create_max_item(&collection, &sender, [(sender.clone(), 200)])?;
-		let data = create_data(b as usize);
+		let data = create_var_data(b).try_into().unwrap();
 	}: {<Pallet<T>>::set_variable_metadata(&collection, &sender, item, data)?}
 }
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -2,14 +2,14 @@
 
 use sp_std::collections::btree_map::BTreeMap;
 use frame_support::{dispatch::DispatchResultWithPostInfo, fail, weights::Weight, BoundedVec};
-use up_data_structs::{TokenId, CustomDataLimit};
+use up_data_structs::{TokenId, CustomDataLimit, CreateItemExData, CreateRefungibleExData};
 use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
 use sp_runtime::DispatchError;
-use sp_std::vec::Vec;
+use sp_std::{vec::Vec, vec};
 
 use crate::{
-	AccountBalance, Allowance, Balance, Config, CreateItemData, Error, Owned, Pallet,
-	RefungibleHandle, SelfWeightOf, TokenData, weights::WeightInfo, TokensMinted,
+	AccountBalance, Allowance, Balance, Config, Error, Owned, Pallet, RefungibleHandle,
+	SelfWeightOf, TokenData, weights::WeightInfo, TokensMinted,
 };
 
 macro_rules! max_weight_of {
@@ -22,7 +22,7 @@
 }
 
 pub struct CommonWeights<T: Config>(PhantomData<T>);
-impl<T: Config> CommonWeightInfo for CommonWeights<T> {
+impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {
 	fn create_item() -> Weight {
 		<SelfWeightOf<T>>::create_item()
 	}
@@ -31,6 +31,18 @@
 		<SelfWeightOf<T>>::create_multiple_items(amount)
 	}
 
+	fn create_multiple_items_ex(call: &CreateItemExData<T::CrossAccountId>) -> Weight {
+		match call {
+			CreateItemExData::RefungibleMultipleOwners(i) => {
+				<SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(i.users.len() as u32)
+			}
+			CreateItemExData::RefungibleMultipleItems(i) => {
+				<SelfWeightOf<T>>::create_multiple_items_ex_multiple_items(i.len() as u32)
+			}
+			_ => 0,
+		}
+	}
+
 	fn burn_item() -> Weight {
 		max_weight_of!(burn_item_partial(), burn_item_fully())
 	}
@@ -69,15 +81,15 @@
 fn map_create_data<T: Config>(
 	data: up_data_structs::CreateItemData,
 	to: &T::CrossAccountId,
-) -> Result<CreateItemData<T>, DispatchError> {
+) -> Result<CreateRefungibleExData<T::CrossAccountId>, DispatchError> {
 	match data {
-		up_data_structs::CreateItemData::ReFungible(data) => Ok(CreateItemData {
+		up_data_structs::CreateItemData::ReFungible(data) => Ok(CreateRefungibleExData {
 			const_data: data.const_data,
 			variable_data: data.variable_data,
 			users: {
 				let mut out = BTreeMap::new();
 				out.insert(to.clone(), data.pieces);
-				out
+				out.try_into().expect("limit > 0")
 			},
 		}),
 		_ => fail!(<Error<T>>::NotRefungibleDataUsedToMintFungibleCollectionToken),
@@ -92,7 +104,7 @@
 		data: up_data_structs::CreateItemData,
 	) -> DispatchResultWithPostInfo {
 		with_weight(
-			<Pallet<T>>::create_item(self, &sender, map_create_data(data, &to)?),
+			<Pallet<T>>::create_item(self, &sender, map_create_data::<T>(data, &to)?),
 			<CommonWeights<T>>::create_item(),
 		)
 	}
@@ -115,6 +127,28 @@
 		)
 	}
 
+	fn create_multiple_items_ex(
+		&self,
+		sender: <T>::CrossAccountId,
+		data: CreateItemExData<T::CrossAccountId>,
+	) -> DispatchResultWithPostInfo {
+		let weight = <CommonWeights<T>>::create_multiple_items_ex(&data);
+		let data = match data {
+			CreateItemExData::RefungibleMultipleOwners(r) => vec![r],
+			CreateItemExData::RefungibleMultipleItems(r)
+				if r.iter().all(|i| i.users.len() == 1) =>
+			{
+				r.into_inner()
+			}
+			_ => fail!(<Error<T>>::NotRefungibleDataUsedToMintFungibleCollectionToken),
+		};
+
+		with_weight(
+			<Pallet<T>>::create_multiple_items(self, &sender, data),
+			weight,
+		)
+	}
+
 	fn burn_item(
 		&self,
 		sender: T::CrossAccountId,
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
before · pallets/refungible/src/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]23use frame_support::{ensure, BoundedVec};4use up_data_structs::{5	AccessMode, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId, CreateCollectionData,6};7use pallet_common::{8	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,9};10use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};11use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};12use core::ops::Deref;13use codec::{Encode, Decode, MaxEncodedLen};14use scale_info::TypeInfo;1516pub use pallet::*;17#[cfg(feature = "runtime-benchmarks")]18pub mod benchmarking;19pub mod common;20pub mod erc;21pub mod weights;22pub struct CreateItemData<T: Config> {23	pub const_data: BoundedVec<u8, CustomDataLimit>,24	pub variable_data: BoundedVec<u8, CustomDataLimit>,25	pub users: BTreeMap<T::CrossAccountId, u128>,26}27pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;2829#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]30pub struct ItemData {31	pub const_data: BoundedVec<u8, CustomDataLimit>,32	pub variable_data: BoundedVec<u8, CustomDataLimit>,33}3435#[frame_support::pallet]36pub mod pallet {37	use super::*;38	use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};39	use up_data_structs::{CollectionId, TokenId};40	use super::weights::WeightInfo;4142	#[pallet::error]43	pub enum Error<T> {44		/// Not Refungible item data used to mint in Refungible collection.45		NotRefungibleDataUsedToMintFungibleCollectionToken,46		/// Maximum refungibility exceeded47		WrongRefungiblePieces,48	}4950	#[pallet::config]51	pub trait Config: frame_system::Config + pallet_common::Config {52		type WeightInfo: WeightInfo;53	}5455	#[pallet::pallet]56	#[pallet::generate_store(pub(super) trait Store)]57	pub struct Pallet<T>(_);5859	#[pallet::storage]60	pub type TokensMinted<T: Config> =61		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;62	#[pallet::storage]63	pub type TokensBurnt<T: Config> =64		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;6566	#[pallet::storage]67	pub type TokenData<T: Config> = StorageNMap<68		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),69		Value = ItemData,70		QueryKind = ValueQuery,71	>;7273	#[pallet::storage]74	pub type TotalSupply<T: Config> = StorageNMap<75		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),76		Value = u128,77		QueryKind = ValueQuery,78	>;7980	/// Used to enumerate tokens owned by account81	#[pallet::storage]82	pub type Owned<T: Config> = StorageNMap<83		Key = (84			Key<Twox64Concat, CollectionId>,85			Key<Blake2_128Concat, T::CrossAccountId>,86			Key<Twox64Concat, TokenId>,87		),88		Value = bool,89		QueryKind = ValueQuery,90	>;9192	#[pallet::storage]93	pub type AccountBalance<T: Config> = StorageNMap<94		Key = (95			Key<Twox64Concat, CollectionId>,96			// Owner97			Key<Blake2_128Concat, T::CrossAccountId>,98		),99		Value = u32,100		QueryKind = ValueQuery,101	>;102103	#[pallet::storage]104	pub type Balance<T: Config> = StorageNMap<105		Key = (106			Key<Twox64Concat, CollectionId>,107			Key<Twox64Concat, TokenId>,108			// Owner109			Key<Blake2_128Concat, T::CrossAccountId>,110		),111		Value = u128,112		QueryKind = ValueQuery,113	>;114115	#[pallet::storage]116	pub type Allowance<T: Config> = StorageNMap<117		Key = (118			Key<Twox64Concat, CollectionId>,119			Key<Twox64Concat, TokenId>,120			// Owner121			Key<Blake2_128, T::CrossAccountId>,122			// Spender123			Key<Blake2_128Concat, T::CrossAccountId>,124		),125		Value = u128,126		QueryKind = ValueQuery,127	>;128}129130pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);131impl<T: Config> RefungibleHandle<T> {132	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {133		Self(inner)134	}135	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {136		self.0137	}138}139impl<T: Config> Deref for RefungibleHandle<T> {140	type Target = pallet_common::CollectionHandle<T>;141142	fn deref(&self) -> &Self::Target {143		&self.0144	}145}146147impl<T: Config> Pallet<T> {148	pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {149		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)150	}151	pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {152		<TotalSupply<T>>::contains_key((collection.id, token))153	}154}155156// unchecked calls skips any permission checks157impl<T: Config> Pallet<T> {158	pub fn init_collection(159		owner: T::AccountId,160		data: CreateCollectionData<T::AccountId>,161	) -> Result<CollectionId, DispatchError> {162		<PalletCommon<T>>::init_collection(owner, data)163	}164	pub fn destroy_collection(165		collection: RefungibleHandle<T>,166		sender: &T::CrossAccountId,167	) -> DispatchResult {168		let id = collection.id;169170		// =========171172		PalletCommon::destroy_collection(collection.0, sender)?;173174		<TokensMinted<T>>::remove(id);175		<TokensBurnt<T>>::remove(id);176		<TokenData<T>>::remove_prefix((id,), None);177		<TotalSupply<T>>::remove_prefix((id,), None);178		<Balance<T>>::remove_prefix((id,), None);179		<Allowance<T>>::remove_prefix((id,), None);180		<Owned<T>>::remove_prefix((id,), None);181		<AccountBalance<T>>::remove_prefix((id,), None);182		Ok(())183	}184185	pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {186		let burnt = <TokensBurnt<T>>::get(collection.id)187			.checked_add(1)188			.ok_or(ArithmeticError::Overflow)?;189190		<TokensBurnt<T>>::insert(collection.id, burnt);191		<TokenData<T>>::remove((collection.id, token_id));192		<TotalSupply<T>>::remove((collection.id, token_id));193		<Balance<T>>::remove_prefix((collection.id, token_id), None);194		<Allowance<T>>::remove_prefix((collection.id, token_id), None);195		// TODO: ERC721 transfer event196		Ok(())197	}198199	pub fn burn(200		collection: &RefungibleHandle<T>,201		owner: &T::CrossAccountId,202		token: TokenId,203		amount: u128,204	) -> DispatchResult {205		let total_supply = <TotalSupply<T>>::get((collection.id, token))206			.checked_sub(amount)207			.ok_or(<CommonError<T>>::TokenValueTooLow)?;208209		// This was probally last owner of this token?210		if total_supply == 0 {211			// Ensure user actually owns this amount212			ensure!(213				<Balance<T>>::get((collection.id, token, owner)) == amount,214				<CommonError<T>>::TokenValueTooLow215			);216			let account_balance = <AccountBalance<T>>::get((collection.id, owner))217				.checked_sub(1)218				// Should not occur219				.ok_or(ArithmeticError::Underflow)?;220221			// =========222223			<Owned<T>>::remove((collection.id, owner, token));224			<AccountBalance<T>>::insert((collection.id, owner), account_balance);225			Self::burn_token(collection, token)?;226			<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(227				collection.id,228				token,229				owner.clone(),230				amount,231			));232			return Ok(());233		}234235		let balance = <Balance<T>>::get((collection.id, token, owner))236			.checked_sub(amount)237			.ok_or(<CommonError<T>>::TokenValueTooLow)?;238		let account_balance = if balance == 0 {239			<AccountBalance<T>>::get((collection.id, owner))240				.checked_sub(1)241				// Should not occur242				.ok_or(ArithmeticError::Underflow)?243		} else {244			0245		};246247		// =========248249		if balance == 0 {250			<Owned<T>>::remove((collection.id, owner, token));251			<Balance<T>>::remove((collection.id, token, owner));252			<AccountBalance<T>>::insert((collection.id, owner), account_balance);253		} else {254			<Balance<T>>::insert((collection.id, token, owner), balance);255		}256		<TotalSupply<T>>::insert((collection.id, token), total_supply);257		// TODO: ERC20 transfer event258		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(259			collection.id,260			token,261			owner.clone(),262			amount,263		));264		Ok(())265	}266267	pub fn transfer(268		collection: &RefungibleHandle<T>,269		from: &T::CrossAccountId,270		to: &T::CrossAccountId,271		token: TokenId,272		amount: u128,273	) -> DispatchResult {274		ensure!(275			collection.limits.transfers_enabled(),276			<CommonError<T>>::TransferNotAllowed277		);278279		if collection.access == AccessMode::AllowList {280			collection.check_allowlist(from)?;281			collection.check_allowlist(to)?;282		}283		<PalletCommon<T>>::ensure_correct_receiver(to)?;284285		let balance_from = <Balance<T>>::get((collection.id, token, from))286			.checked_sub(amount)287			.ok_or(<CommonError<T>>::TokenValueTooLow)?;288		let mut create_target = false;289		let from_to_differ = from != to;290		let balance_to = if from != to {291			let old_balance = <Balance<T>>::get((collection.id, token, to));292			if old_balance == 0 {293				create_target = true;294			}295			Some(296				old_balance297					.checked_add(amount)298					.ok_or(ArithmeticError::Overflow)?,299			)300		} else {301			None302		};303304		let account_balance_from = if balance_from == 0 {305			Some(306				<AccountBalance<T>>::get((collection.id, from))307					.checked_sub(1)308					// Should not occur309					.ok_or(ArithmeticError::Underflow)?,310			)311		} else {312			None313		};314		// Account data is created in token, AccountBalance should be increased315		// But only if from != to as we shouldn't check overflow in this case316		let account_balance_to = if create_target && from_to_differ {317			let account_balance_to = <AccountBalance<T>>::get((collection.id, to))318				.checked_add(1)319				.ok_or(ArithmeticError::Overflow)?;320			ensure!(321				account_balance_to < collection.limits.account_token_ownership_limit(),322				<CommonError<T>>::AccountTokenLimitExceeded,323			);324325			Some(account_balance_to)326		} else {327			None328		};329330		// =========331332		if let Some(balance_to) = balance_to {333			// from != to334			if balance_from == 0 {335				<Balance<T>>::remove((collection.id, token, from));336			} else {337				<Balance<T>>::insert((collection.id, token, from), balance_from);338			}339			<Balance<T>>::insert((collection.id, token, to), balance_to);340			if let Some(account_balance_from) = account_balance_from {341				<AccountBalance<T>>::insert((collection.id, from), account_balance_from);342				<Owned<T>>::remove((collection.id, from, token));343			}344			if let Some(account_balance_to) = account_balance_to {345				<AccountBalance<T>>::insert((collection.id, to), account_balance_to);346				<Owned<T>>::insert((collection.id, to, token), true);347			}348		}349350		// TODO: ERC20 transfer event351		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(352			collection.id,353			token,354			from.clone(),355			to.clone(),356			amount,357		));358		Ok(())359	}360361	pub fn create_multiple_items(362		collection: &RefungibleHandle<T>,363		sender: &T::CrossAccountId,364		data: Vec<CreateItemData<T>>,365	) -> DispatchResult {366		if !collection.is_owner_or_admin(sender) {367			ensure!(368				collection.mint_mode,369				<CommonError<T>>::PublicMintingNotAllowed370			);371			collection.check_allowlist(sender)?;372373			for item in data.iter() {374				for user in item.users.keys() {375					collection.check_allowlist(user)?;376				}377			}378		}379380		for item in data.iter() {381			for (owner, _) in item.users.iter() {382				<PalletCommon<T>>::ensure_correct_receiver(owner)?;383			}384		}385386		// Total pieces per tokens387		let totals = data388			.iter()389			.map(|data| {390				Ok(data391					.users392					.iter()393					.map(|u| u.1)394					.try_fold(0u128, |acc, v| acc.checked_add(*v))395					.ok_or(ArithmeticError::Overflow)?)396			})397			.collect::<Result<Vec<_>, DispatchError>>()?;398		for total in &totals {399			ensure!(400				*total <= MAX_REFUNGIBLE_PIECES,401				<Error<T>>::WrongRefungiblePieces402			);403		}404405		let first_token_id = <TokensMinted<T>>::get(collection.id);406		let tokens_minted = first_token_id407			.checked_add(data.len() as u32)408			.ok_or(ArithmeticError::Overflow)?;409		ensure!(410			tokens_minted < collection.limits.token_limit(),411			<CommonError<T>>::CollectionTokenLimitExceeded412		);413414		let mut balances = BTreeMap::new();415		for data in &data {416			for owner in data.users.keys() {417				let balance = balances418					.entry(owner)419					.or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));420				*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;421422				ensure!(423					*balance <= collection.limits.account_token_ownership_limit(),424					<CommonError<T>>::AccountTokenLimitExceeded,425				);426			}427		}428429		// =========430431		<TokensMinted<T>>::insert(collection.id, tokens_minted);432		for (account, balance) in balances {433			<AccountBalance<T>>::insert((collection.id, account), balance);434		}435		for (i, token) in data.into_iter().enumerate() {436			let token_id = first_token_id + i as u32 + 1;437			<TotalSupply<T>>::insert((collection.id, token_id), totals[i]);438439			<TokenData<T>>::insert(440				(collection.id, token_id),441				ItemData {442					const_data: token.const_data,443					variable_data: token.variable_data,444				},445			);446			for (user, amount) in token.users.into_iter() {447				if amount == 0 {448					continue;449				}450				<Balance<T>>::insert((collection.id, token_id, &user), amount);451				<Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);452				// TODO: ERC20 transfer event453				<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(454					collection.id,455					TokenId(token_id),456					user,457					amount,458				));459			}460		}461		Ok(())462	}463464	pub fn set_allowance_unchecked(465		collection: &RefungibleHandle<T>,466		sender: &T::CrossAccountId,467		spender: &T::CrossAccountId,468		token: TokenId,469		amount: u128,470	) {471		if amount == 0 {472			<Allowance<T>>::remove((collection.id, token, sender, spender));473		} else {474			<Allowance<T>>::insert((collection.id, token, sender, spender), amount);475		}476		// TODO: ERC20 approval event477		<PalletCommon<T>>::deposit_event(CommonEvent::Approved(478			collection.id,479			token,480			sender.clone(),481			spender.clone(),482			amount,483		))484	}485486	pub fn set_allowance(487		collection: &RefungibleHandle<T>,488		sender: &T::CrossAccountId,489		spender: &T::CrossAccountId,490		token: TokenId,491		amount: u128,492	) -> DispatchResult {493		if collection.access == AccessMode::AllowList {494			collection.check_allowlist(sender)?;495			collection.check_allowlist(spender)?;496		}497498		<PalletCommon<T>>::ensure_correct_receiver(spender)?;499500		if <Balance<T>>::get((collection.id, token, sender)) < amount {501			ensure!(502				collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),503				<CommonError<T>>::CantApproveMoreThanOwned504			);505		}506507		// =========508509		Self::set_allowance_unchecked(collection, sender, spender, token, amount);510		Ok(())511	}512513	pub fn transfer_from(514		collection: &RefungibleHandle<T>,515		spender: &T::CrossAccountId,516		from: &T::CrossAccountId,517		to: &T::CrossAccountId,518		token: TokenId,519		amount: u128,520	) -> DispatchResult {521		if spender.conv_eq(from) {522			return Self::transfer(collection, from, to, token, amount);523		}524		if collection.access == AccessMode::AllowList {525			// `from`, `to` checked in [`transfer`]526			collection.check_allowlist(spender)?;527		}528529		let allowance =530			<Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);531		if allowance.is_none() {532			ensure!(533				collection.ignores_allowance(spender),534				<CommonError<T>>::ApprovedValueTooLow535			);536		}537538		// =========539540		Self::transfer(collection, from, to, token, amount)?;541		if let Some(allowance) = allowance {542			Self::set_allowance_unchecked(collection, from, spender, token, allowance);543		}544		Ok(())545	}546547	pub fn burn_from(548		collection: &RefungibleHandle<T>,549		spender: &T::CrossAccountId,550		from: &T::CrossAccountId,551		token: TokenId,552		amount: u128,553	) -> DispatchResult {554		if spender.conv_eq(from) {555			return Self::burn(collection, from, token, amount);556		}557		if collection.access == AccessMode::AllowList {558			// `from` checked in [`burn`]559			collection.check_allowlist(spender)?;560		}561562		let allowance =563			<Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);564		if allowance.is_none() {565			ensure!(566				collection.ignores_allowance(spender),567				<CommonError<T>>::ApprovedValueTooLow568			);569		}570571		// =========572573		Self::burn(collection, from, token, amount)?;574		if let Some(allowance) = allowance {575			Self::set_allowance_unchecked(collection, from, spender, token, allowance);576		}577		Ok(())578	}579580	pub fn set_variable_metadata(581		collection: &RefungibleHandle<T>,582		sender: &T::CrossAccountId,583		token: TokenId,584		data: BoundedVec<u8, CustomDataLimit>,585	) -> DispatchResult {586		collection.check_can_update_meta(587			sender,588			&T::CrossAccountId::from_sub(collection.owner.clone()),589		)?;590591		let token_data = <TokenData<T>>::get((collection.id, token));592593		// =========594595		<TokenData<T>>::insert(596			(collection.id, token),597			ItemData {598				variable_data: data,599				..token_data600			},601		);602		Ok(())603	}604605	/// Delegated to `create_multiple_items`606	pub fn create_item(607		collection: &RefungibleHandle<T>,608		sender: &T::CrossAccountId,609		data: CreateItemData<T>,610	) -> DispatchResult {611		Self::create_multiple_items(collection, sender, vec![data])612	}613}
after · pallets/refungible/src/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]23use frame_support::{ensure, BoundedVec};4use up_data_structs::{5	AccessMode, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId,6	CreateCollectionData, CreateRefungibleExData,7};8use pallet_common::{9	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,10};11use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};12use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};13use core::ops::Deref;14use codec::{Encode, Decode, MaxEncodedLen};15use scale_info::TypeInfo;1617pub use pallet::*;18#[cfg(feature = "runtime-benchmarks")]19pub mod benchmarking;20pub mod common;21pub mod erc;22pub mod weights;23pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;2425#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]26pub struct ItemData {27	pub const_data: BoundedVec<u8, CustomDataLimit>,28	pub variable_data: BoundedVec<u8, CustomDataLimit>,29}3031#[frame_support::pallet]32pub mod pallet {33	use super::*;34	use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};35	use up_data_structs::{CollectionId, TokenId};36	use super::weights::WeightInfo;3738	#[pallet::error]39	pub enum Error<T> {40		/// Not Refungible item data used to mint in Refungible collection.41		NotRefungibleDataUsedToMintFungibleCollectionToken,42		/// Maximum refungibility exceeded43		WrongRefungiblePieces,44	}4546	#[pallet::config]47	pub trait Config: frame_system::Config + pallet_common::Config {48		type WeightInfo: WeightInfo;49	}5051	#[pallet::pallet]52	#[pallet::generate_store(pub(super) trait Store)]53	pub struct Pallet<T>(_);5455	#[pallet::storage]56	pub type TokensMinted<T: Config> =57		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;58	#[pallet::storage]59	pub type TokensBurnt<T: Config> =60		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;6162	#[pallet::storage]63	pub type TokenData<T: Config> = StorageNMap<64		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),65		Value = ItemData,66		QueryKind = ValueQuery,67	>;6869	#[pallet::storage]70	pub type TotalSupply<T: Config> = StorageNMap<71		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),72		Value = u128,73		QueryKind = ValueQuery,74	>;7576	/// Used to enumerate tokens owned by account77	#[pallet::storage]78	pub type Owned<T: Config> = StorageNMap<79		Key = (80			Key<Twox64Concat, CollectionId>,81			Key<Blake2_128Concat, T::CrossAccountId>,82			Key<Twox64Concat, TokenId>,83		),84		Value = bool,85		QueryKind = ValueQuery,86	>;8788	#[pallet::storage]89	pub type AccountBalance<T: Config> = StorageNMap<90		Key = (91			Key<Twox64Concat, CollectionId>,92			// Owner93			Key<Blake2_128Concat, T::CrossAccountId>,94		),95		Value = u32,96		QueryKind = ValueQuery,97	>;9899	#[pallet::storage]100	pub type Balance<T: Config> = StorageNMap<101		Key = (102			Key<Twox64Concat, CollectionId>,103			Key<Twox64Concat, TokenId>,104			// Owner105			Key<Blake2_128Concat, T::CrossAccountId>,106		),107		Value = u128,108		QueryKind = ValueQuery,109	>;110111	#[pallet::storage]112	pub type Allowance<T: Config> = StorageNMap<113		Key = (114			Key<Twox64Concat, CollectionId>,115			Key<Twox64Concat, TokenId>,116			// Owner117			Key<Blake2_128, T::CrossAccountId>,118			// Spender119			Key<Blake2_128Concat, T::CrossAccountId>,120		),121		Value = u128,122		QueryKind = ValueQuery,123	>;124}125126pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);127impl<T: Config> RefungibleHandle<T> {128	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {129		Self(inner)130	}131	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {132		self.0133	}134}135impl<T: Config> Deref for RefungibleHandle<T> {136	type Target = pallet_common::CollectionHandle<T>;137138	fn deref(&self) -> &Self::Target {139		&self.0140	}141}142143impl<T: Config> Pallet<T> {144	pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {145		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)146	}147	pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {148		<TotalSupply<T>>::contains_key((collection.id, token))149	}150}151152// unchecked calls skips any permission checks153impl<T: Config> Pallet<T> {154	pub fn init_collection(155		owner: T::AccountId,156		data: CreateCollectionData<T::AccountId>,157	) -> Result<CollectionId, DispatchError> {158		<PalletCommon<T>>::init_collection(owner, data)159	}160	pub fn destroy_collection(161		collection: RefungibleHandle<T>,162		sender: &T::CrossAccountId,163	) -> DispatchResult {164		let id = collection.id;165166		// =========167168		PalletCommon::destroy_collection(collection.0, sender)?;169170		<TokensMinted<T>>::remove(id);171		<TokensBurnt<T>>::remove(id);172		<TokenData<T>>::remove_prefix((id,), None);173		<TotalSupply<T>>::remove_prefix((id,), None);174		<Balance<T>>::remove_prefix((id,), None);175		<Allowance<T>>::remove_prefix((id,), None);176		<Owned<T>>::remove_prefix((id,), None);177		<AccountBalance<T>>::remove_prefix((id,), None);178		Ok(())179	}180181	pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {182		let burnt = <TokensBurnt<T>>::get(collection.id)183			.checked_add(1)184			.ok_or(ArithmeticError::Overflow)?;185186		<TokensBurnt<T>>::insert(collection.id, burnt);187		<TokenData<T>>::remove((collection.id, token_id));188		<TotalSupply<T>>::remove((collection.id, token_id));189		<Balance<T>>::remove_prefix((collection.id, token_id), None);190		<Allowance<T>>::remove_prefix((collection.id, token_id), None);191		// TODO: ERC721 transfer event192		Ok(())193	}194195	pub fn burn(196		collection: &RefungibleHandle<T>,197		owner: &T::CrossAccountId,198		token: TokenId,199		amount: u128,200	) -> DispatchResult {201		let total_supply = <TotalSupply<T>>::get((collection.id, token))202			.checked_sub(amount)203			.ok_or(<CommonError<T>>::TokenValueTooLow)?;204205		// This was probally last owner of this token?206		if total_supply == 0 {207			// Ensure user actually owns this amount208			ensure!(209				<Balance<T>>::get((collection.id, token, owner)) == amount,210				<CommonError<T>>::TokenValueTooLow211			);212			let account_balance = <AccountBalance<T>>::get((collection.id, owner))213				.checked_sub(1)214				// Should not occur215				.ok_or(ArithmeticError::Underflow)?;216217			// =========218219			<Owned<T>>::remove((collection.id, owner, token));220			<AccountBalance<T>>::insert((collection.id, owner), account_balance);221			Self::burn_token(collection, token)?;222			<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(223				collection.id,224				token,225				owner.clone(),226				amount,227			));228			return Ok(());229		}230231		let balance = <Balance<T>>::get((collection.id, token, owner))232			.checked_sub(amount)233			.ok_or(<CommonError<T>>::TokenValueTooLow)?;234		let account_balance = if balance == 0 {235			<AccountBalance<T>>::get((collection.id, owner))236				.checked_sub(1)237				// Should not occur238				.ok_or(ArithmeticError::Underflow)?239		} else {240			0241		};242243		// =========244245		if balance == 0 {246			<Owned<T>>::remove((collection.id, owner, token));247			<Balance<T>>::remove((collection.id, token, owner));248			<AccountBalance<T>>::insert((collection.id, owner), account_balance);249		} else {250			<Balance<T>>::insert((collection.id, token, owner), balance);251		}252		<TotalSupply<T>>::insert((collection.id, token), total_supply);253		// TODO: ERC20 transfer event254		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(255			collection.id,256			token,257			owner.clone(),258			amount,259		));260		Ok(())261	}262263	pub fn transfer(264		collection: &RefungibleHandle<T>,265		from: &T::CrossAccountId,266		to: &T::CrossAccountId,267		token: TokenId,268		amount: u128,269	) -> DispatchResult {270		ensure!(271			collection.limits.transfers_enabled(),272			<CommonError<T>>::TransferNotAllowed273		);274275		if collection.access == AccessMode::AllowList {276			collection.check_allowlist(from)?;277			collection.check_allowlist(to)?;278		}279		<PalletCommon<T>>::ensure_correct_receiver(to)?;280281		let balance_from = <Balance<T>>::get((collection.id, token, from))282			.checked_sub(amount)283			.ok_or(<CommonError<T>>::TokenValueTooLow)?;284		let mut create_target = false;285		let from_to_differ = from != to;286		let balance_to = if from != to {287			let old_balance = <Balance<T>>::get((collection.id, token, to));288			if old_balance == 0 {289				create_target = true;290			}291			Some(292				old_balance293					.checked_add(amount)294					.ok_or(ArithmeticError::Overflow)?,295			)296		} else {297			None298		};299300		let account_balance_from = if balance_from == 0 {301			Some(302				<AccountBalance<T>>::get((collection.id, from))303					.checked_sub(1)304					// Should not occur305					.ok_or(ArithmeticError::Underflow)?,306			)307		} else {308			None309		};310		// Account data is created in token, AccountBalance should be increased311		// But only if from != to as we shouldn't check overflow in this case312		let account_balance_to = if create_target && from_to_differ {313			let account_balance_to = <AccountBalance<T>>::get((collection.id, to))314				.checked_add(1)315				.ok_or(ArithmeticError::Overflow)?;316			ensure!(317				account_balance_to < collection.limits.account_token_ownership_limit(),318				<CommonError<T>>::AccountTokenLimitExceeded,319			);320321			Some(account_balance_to)322		} else {323			None324		};325326		// =========327328		if let Some(balance_to) = balance_to {329			// from != to330			if balance_from == 0 {331				<Balance<T>>::remove((collection.id, token, from));332			} else {333				<Balance<T>>::insert((collection.id, token, from), balance_from);334			}335			<Balance<T>>::insert((collection.id, token, to), balance_to);336			if let Some(account_balance_from) = account_balance_from {337				<AccountBalance<T>>::insert((collection.id, from), account_balance_from);338				<Owned<T>>::remove((collection.id, from, token));339			}340			if let Some(account_balance_to) = account_balance_to {341				<AccountBalance<T>>::insert((collection.id, to), account_balance_to);342				<Owned<T>>::insert((collection.id, to, token), true);343			}344		}345346		// TODO: ERC20 transfer event347		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(348			collection.id,349			token,350			from.clone(),351			to.clone(),352			amount,353		));354		Ok(())355	}356357	pub fn create_multiple_items(358		collection: &RefungibleHandle<T>,359		sender: &T::CrossAccountId,360		data: Vec<CreateRefungibleExData<T::CrossAccountId>>,361	) -> DispatchResult {362		if !collection.is_owner_or_admin(sender) {363			ensure!(364				collection.mint_mode,365				<CommonError<T>>::PublicMintingNotAllowed366			);367			collection.check_allowlist(sender)?;368369			for item in data.iter() {370				for user in item.users.keys() {371					collection.check_allowlist(user)?;372				}373			}374		}375376		for item in data.iter() {377			for (owner, _) in item.users.iter() {378				<PalletCommon<T>>::ensure_correct_receiver(owner)?;379			}380		}381382		// Total pieces per tokens383		let totals = data384			.iter()385			.map(|data| {386				Ok(data387					.users388					.iter()389					.map(|u| u.1)390					.try_fold(0u128, |acc, v| acc.checked_add(*v))391					.ok_or(ArithmeticError::Overflow)?)392			})393			.collect::<Result<Vec<_>, DispatchError>>()?;394		for total in &totals {395			ensure!(396				*total <= MAX_REFUNGIBLE_PIECES,397				<Error<T>>::WrongRefungiblePieces398			);399		}400401		let first_token_id = <TokensMinted<T>>::get(collection.id);402		let tokens_minted = first_token_id403			.checked_add(data.len() as u32)404			.ok_or(ArithmeticError::Overflow)?;405		ensure!(406			tokens_minted < collection.limits.token_limit(),407			<CommonError<T>>::CollectionTokenLimitExceeded408		);409410		let mut balances = BTreeMap::new();411		for data in &data {412			for owner in data.users.keys() {413				let balance = balances414					.entry(owner)415					.or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));416				*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;417418				ensure!(419					*balance <= collection.limits.account_token_ownership_limit(),420					<CommonError<T>>::AccountTokenLimitExceeded,421				);422			}423		}424425		// =========426427		<TokensMinted<T>>::insert(collection.id, tokens_minted);428		for (account, balance) in balances {429			<AccountBalance<T>>::insert((collection.id, account), balance);430		}431		for (i, token) in data.into_iter().enumerate() {432			let token_id = first_token_id + i as u32 + 1;433			<TotalSupply<T>>::insert((collection.id, token_id), totals[i]);434435			<TokenData<T>>::insert(436				(collection.id, token_id),437				ItemData {438					const_data: token.const_data,439					variable_data: token.variable_data,440				},441			);442			for (user, amount) in token.users.into_iter() {443				if amount == 0 {444					continue;445				}446				<Balance<T>>::insert((collection.id, token_id, &user), amount);447				<Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);448				// TODO: ERC20 transfer event449				<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(450					collection.id,451					TokenId(token_id),452					user,453					amount,454				));455			}456		}457		Ok(())458	}459460	pub fn set_allowance_unchecked(461		collection: &RefungibleHandle<T>,462		sender: &T::CrossAccountId,463		spender: &T::CrossAccountId,464		token: TokenId,465		amount: u128,466	) {467		if amount == 0 {468			<Allowance<T>>::remove((collection.id, token, sender, spender));469		} else {470			<Allowance<T>>::insert((collection.id, token, sender, spender), amount);471		}472		// TODO: ERC20 approval event473		<PalletCommon<T>>::deposit_event(CommonEvent::Approved(474			collection.id,475			token,476			sender.clone(),477			spender.clone(),478			amount,479		))480	}481482	pub fn set_allowance(483		collection: &RefungibleHandle<T>,484		sender: &T::CrossAccountId,485		spender: &T::CrossAccountId,486		token: TokenId,487		amount: u128,488	) -> DispatchResult {489		if collection.access == AccessMode::AllowList {490			collection.check_allowlist(sender)?;491			collection.check_allowlist(spender)?;492		}493494		<PalletCommon<T>>::ensure_correct_receiver(spender)?;495496		if <Balance<T>>::get((collection.id, token, sender)) < amount {497			ensure!(498				collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),499				<CommonError<T>>::CantApproveMoreThanOwned500			);501		}502503		// =========504505		Self::set_allowance_unchecked(collection, sender, spender, token, amount);506		Ok(())507	}508509	pub fn transfer_from(510		collection: &RefungibleHandle<T>,511		spender: &T::CrossAccountId,512		from: &T::CrossAccountId,513		to: &T::CrossAccountId,514		token: TokenId,515		amount: u128,516	) -> DispatchResult {517		if spender.conv_eq(from) {518			return Self::transfer(collection, from, to, token, amount);519		}520		if collection.access == AccessMode::AllowList {521			// `from`, `to` checked in [`transfer`]522			collection.check_allowlist(spender)?;523		}524525		let allowance =526			<Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);527		if allowance.is_none() {528			ensure!(529				collection.ignores_allowance(spender),530				<CommonError<T>>::ApprovedValueTooLow531			);532		}533534		// =========535536		Self::transfer(collection, from, to, token, amount)?;537		if let Some(allowance) = allowance {538			Self::set_allowance_unchecked(collection, from, spender, token, allowance);539		}540		Ok(())541	}542543	pub fn burn_from(544		collection: &RefungibleHandle<T>,545		spender: &T::CrossAccountId,546		from: &T::CrossAccountId,547		token: TokenId,548		amount: u128,549	) -> DispatchResult {550		if spender.conv_eq(from) {551			return Self::burn(collection, from, token, amount);552		}553		if collection.access == AccessMode::AllowList {554			// `from` checked in [`burn`]555			collection.check_allowlist(spender)?;556		}557558		let allowance =559			<Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);560		if allowance.is_none() {561			ensure!(562				collection.ignores_allowance(spender),563				<CommonError<T>>::ApprovedValueTooLow564			);565		}566567		// =========568569		Self::burn(collection, from, token, amount)?;570		if let Some(allowance) = allowance {571			Self::set_allowance_unchecked(collection, from, spender, token, allowance);572		}573		Ok(())574	}575576	pub fn set_variable_metadata(577		collection: &RefungibleHandle<T>,578		sender: &T::CrossAccountId,579		token: TokenId,580		data: BoundedVec<u8, CustomDataLimit>,581	) -> DispatchResult {582		collection.check_can_update_meta(583			sender,584			&T::CrossAccountId::from_sub(collection.owner.clone()),585		)?;586587		let token_data = <TokenData<T>>::get((collection.id, token));588589		// =========590591		<TokenData<T>>::insert(592			(collection.id, token),593			ItemData {594				variable_data: data,595				..token_data596			},597		);598		Ok(())599	}600601	/// Delegated to `create_multiple_items`602	pub fn create_item(603		collection: &RefungibleHandle<T>,604		sender: &T::CrossAccountId,605		data: CreateRefungibleExData<T::CrossAccountId>,606	) -> DispatchResult {607		Self::create_multiple_items(collection, sender, vec![data])608	}609}
modifiedpallets/refungible/src/weights.rsdiffbeforeafterboth
--- a/pallets/refungible/src/weights.rs
+++ b/pallets/refungible/src/weights.rs
@@ -34,6 +34,8 @@
 pub trait WeightInfo {
 	fn create_item() -> Weight;
 	fn create_multiple_items(b: u32, ) -> Weight;
+	fn create_multiple_items_ex_multiple_items(b: u32, ) -> Weight;
+	fn create_multiple_items_ex_multiple_owners(b: u32, ) -> Weight;
 	fn burn_item_partial() -> Weight;
 	fn burn_item_fully() -> Weight;
 	fn transfer_normal() -> Weight;
@@ -77,6 +79,36 @@
 			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes((4 as Weight).saturating_mul(b as Weight)))
 	}
+	// Storage: Refungible TokensMinted (r:1 w:1)
+	// Storage: Refungible AccountBalance (r:4 w:4)
+	// Storage: Refungible Balance (r:0 w:4)
+	// Storage: Refungible TotalSupply (r:0 w:4)
+	// Storage: Refungible TokenData (r:0 w:4)
+	// Storage: Refungible Owned (r:0 w:4)
+	fn create_multiple_items_ex_multiple_items(b: u32, ) -> Weight {
+		(11_953_000 as Weight)
+			// Standard Error: 27_000
+			.saturating_add((10_775_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes((5 as Weight).saturating_mul(b as Weight)))
+	}
+	// Storage: Refungible TokensMinted (r:1 w:1)
+	// Storage: Refungible TotalSupply (r:0 w:1)
+	// Storage: Refungible TokenData (r:0 w:1)
+	// Storage: Refungible AccountBalance (r:4 w:4)
+	// Storage: Refungible Balance (r:0 w:4)
+	// Storage: Refungible Owned (r:0 w:4)
+	fn create_multiple_items_ex_multiple_owners(b: u32, ) -> Weight {
+		(0 as Weight)
+			// Standard Error: 13_000
+			.saturating_add((8_528_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
+			.saturating_add(T::DbWeight::get().writes(3 as Weight))
+			.saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(b as Weight)))
+	}
 	// Storage: Refungible TotalSupply (r:1 w:1)
 	// Storage: Refungible Balance (r:1 w:1)
 	// Storage: Refungible AccountBalance (r:1 w:1)
@@ -215,6 +247,36 @@
 			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes((4 as Weight).saturating_mul(b as Weight)))
 	}
+	// Storage: Refungible TokensMinted (r:1 w:1)
+	// Storage: Refungible AccountBalance (r:4 w:4)
+	// Storage: Refungible Balance (r:0 w:4)
+	// Storage: Refungible TotalSupply (r:0 w:4)
+	// Storage: Refungible TokenData (r:0 w:4)
+	// Storage: Refungible Owned (r:0 w:4)
+	fn create_multiple_items_ex_multiple_items(b: u32, ) -> Weight {
+		(11_953_000 as Weight)
+			// Standard Error: 27_000
+			.saturating_add((10_775_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+			.saturating_add(RocksDbWeight::get().writes((5 as Weight).saturating_mul(b as Weight)))
+	}
+	// Storage: Refungible TokensMinted (r:1 w:1)
+	// Storage: Refungible TotalSupply (r:0 w:1)
+	// Storage: Refungible TokenData (r:0 w:1)
+	// Storage: Refungible AccountBalance (r:4 w:4)
+	// Storage: Refungible Balance (r:0 w:4)
+	// Storage: Refungible Owned (r:0 w:4)
+	fn create_multiple_items_ex_multiple_owners(b: u32, ) -> Weight {
+		(0 as Weight)
+			// Standard Error: 13_000
+			.saturating_add((8_528_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
+			.saturating_add(RocksDbWeight::get().writes(3 as Weight))
+			.saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(b as Weight)))
+	}
 	// Storage: Refungible TotalSupply (r:1 w:1)
 	// Storage: Refungible Balance (r:1 w:1)
 	// Storage: Refungible AccountBalance (r:1 w:1)
modifiedpallets/unique/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/unique/src/benchmarking.rs
+++ b/pallets/unique/src/benchmarking.rs
@@ -5,9 +5,8 @@
 use frame_system::RawOrigin;
 use frame_benchmarking::{benchmarks, account};
 use up_data_structs::*;
-use core::convert::TryInto;
 use sp_runtime::DispatchError;
-use pallet_common::benchmarking::{create_data, create_u16_data};
+use pallet_common::benchmarking::{create_data, create_var_data, create_u16_data};
 
 const SEED: u32 = 1;
 
@@ -16,13 +15,9 @@
 	mode: CollectionMode,
 ) -> Result<CollectionId, DispatchError> {
 	T::Currency::deposit_creating(&owner, T::CollectionCreationPrice::get());
-	let col_name = create_u16_data(MAX_COLLECTION_NAME_LENGTH)
-		.try_into()
-		.unwrap();
-	let col_desc = create_u16_data(MAX_COLLECTION_DESCRIPTION_LENGTH)
-		.try_into()
-		.unwrap();
-	let token_prefix = create_data(MAX_TOKEN_PREFIX_LENGTH).try_into().unwrap();
+	let col_name = create_u16_data::<MAX_COLLECTION_NAME_LENGTH>();
+	let col_desc = create_u16_data::<MAX_COLLECTION_DESCRIPTION_LENGTH>();
+	let token_prefix = create_data::<MAX_TOKEN_PREFIX_LENGTH>();
 	<Pallet<T>>::create_collection(
 		RawOrigin::Signed(owner).into(),
 		col_name,
@@ -37,11 +32,10 @@
 }
 
 benchmarks! {
-
 	create_collection {
-		let col_name: Vec<u16> = create_u16_data(MAX_COLLECTION_NAME_LENGTH);
-		let col_desc: Vec<u16> = create_u16_data(MAX_COLLECTION_DESCRIPTION_LENGTH);
-		let token_prefix: Vec<u8> = create_data(MAX_TOKEN_PREFIX_LENGTH);
+		let col_name = create_u16_data::<MAX_COLLECTION_NAME_LENGTH>();
+		let col_desc = create_u16_data::<MAX_COLLECTION_DESCRIPTION_LENGTH>();
+		let token_prefix = create_data::<MAX_TOKEN_PREFIX_LENGTH>();
 		let mode: CollectionMode = CollectionMode::NFT;
 		let caller: T::AccountId = account("caller", 0, SEED);
 		T::Currency::deposit_creating(&caller, T::CollectionCreationPrice::get());
@@ -125,7 +119,7 @@
 
 		let caller: T::AccountId = account("caller", 0, SEED);
 		let collection = create_nft_collection::<T>(caller.clone())?;
-		let data = create_data(b as usize);
+		let data = create_var_data(b);
 	}: set_offchain_schema(RawOrigin::Signed(caller.clone()), collection, data)
 
 	set_const_on_chain_schema {
@@ -133,7 +127,7 @@
 
 		let caller: T::AccountId = account("caller", 0, SEED);
 		let collection = create_nft_collection::<T>(caller.clone())?;
-		let data = create_data(b as usize);
+		let data = create_var_data(b);
 	}: set_const_on_chain_schema(RawOrigin::Signed(caller.clone()), collection, data)
 
 	set_variable_on_chain_schema {
@@ -141,7 +135,7 @@
 
 		let caller: T::AccountId = account("caller", 0, SEED);
 		let collection = create_nft_collection::<T>(caller.clone())?;
-		let data = create_data(b as usize);
+		let data = create_var_data(b);
 	}: set_variable_on_chain_schema(RawOrigin::Signed(caller.clone()), collection, data)
 
 	set_schema_version {
modifiedpallets/unique/src/common.rsdiffbeforeafterboth
--- a/pallets/unique/src/common.rs
+++ b/pallets/unique/src/common.rs
@@ -5,6 +5,7 @@
 use pallet_fungible::{common::CommonWeights as FungibleWeights};
 use pallet_nonfungible::{common::CommonWeights as NonfungibleWeights};
 use pallet_refungible::{common::CommonWeights as RefungibleWeights};
+use up_data_structs::CreateItemExData;
 
 use crate::{Config, dispatch::dispatch_weight};
 
@@ -17,7 +18,7 @@
 }
 
 pub struct CommonWeights<T: Config>(PhantomData<T>);
-impl<T: Config> CommonWeightInfo for CommonWeights<T> {
+impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {
 	fn create_item() -> up_data_structs::Weight {
 		dispatch_weight::<T>() + max_weight_of!(create_item())
 	}
@@ -26,6 +27,10 @@
 		dispatch_weight::<T>() + max_weight_of!(create_multiple_items(amount))
 	}
 
+	fn create_multiple_items_ex(data: &CreateItemExData<T::CrossAccountId>) -> Weight {
+		dispatch_weight::<T>() + max_weight_of!(create_multiple_items_ex(data))
+	}
+
 	fn burn_item() -> Weight {
 		dispatch_weight::<T>() + max_weight_of!(burn_item())
 	}
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -40,7 +40,7 @@
 	OFFCHAIN_SCHEMA_LIMIT, MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH,
 	MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData, CollectionLimits, CollectionId,
 	CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,
-	CreateCollectionData, CustomDataLimit,
+	CreateCollectionData, CustomDataLimit, CreateItemExData,
 };
 use pallet_common::{
 	account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,
@@ -735,6 +735,14 @@
 			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))
 		}
 
+		#[weight = <CommonWeights<T>>::create_multiple_items_ex(&data)]
+		#[transactional]
+		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {
+			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
+
+			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data))
+		}
+
 		// TODO! transaction weight
 
 		/// Set transfers_enabled value for particular collection
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -1,6 +1,11 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
-use core::convert::{TryFrom, TryInto};
+use core::{
+	convert::{TryFrom, TryInto},
+	fmt,
+};
+use frame_support::storage::bounded_btree_map::BoundedBTreeMap;
+use sp_std::collections::btree_map::BTreeMap;
 
 #[cfg(feature = "serde")]
 pub use serde::{Serialize, Deserialize};
@@ -76,9 +81,7 @@
 /// create_many call
 pub const MAX_ITEMS_PER_BATCH: u32 = 200;
 
-parameter_types! {
-	pub const CustomDataLimit: u32 = CUSTOM_DATA_LIMIT;
-}
+pub type CustomDataLimit = ConstU32<CUSTOM_DATA_LIMIT>;
 
 #[derive(
 	Encode,
@@ -417,15 +420,72 @@
 	}
 }
 
+fn bounded_debug<V, S>(v: &BoundedVec<V, S>, f: &mut fmt::Formatter) -> Result<(), fmt::Error>
+where
+	V: fmt::Debug,
+{
+	use core::fmt::Debug;
+	(&v as &Vec<V>).fmt(f)
+}
+
+#[cfg(feature = "serde1")]
+#[allow(dead_code)]
+mod bounded_map_serde {
+	use core::convert::TryFrom;
+	use sp_std::collections::btree_map::BTreeMap;
+	use frame_support::{traits::Get, storage::bounded_btree_map::BoundedBTreeMap};
+	use serde::{
+		ser::{self, Serialize},
+		de::{self, Deserialize, Error},
+	};
+	pub fn serialize<D, K, V, S>(
+		value: &BoundedBTreeMap<K, V, S>,
+		serializer: D,
+	) -> Result<D::Ok, D::Error>
+	where
+		D: ser::Serializer,
+		K: Serialize + Ord,
+		V: Serialize,
+	{
+		(value as &BTreeMap<_, _>).serialize(serializer)
+	}
+
+	pub fn deserialize<'de, D, K, V, S>(
+		deserializer: D,
+	) -> Result<BoundedBTreeMap<K, V, S>, D::Error>
+	where
+		D: de::Deserializer<'de>,
+		K: de::Deserialize<'de> + Ord,
+		V: de::Deserialize<'de>,
+		S: Get<u32>,
+	{
+		let map = <BTreeMap<K, V>>::deserialize(deserializer)?;
+		let len = map.len();
+		TryFrom::try_from(map).map_err(|_| D::Error::invalid_length(len, &"lesser size"))
+	}
+}
+
+fn bounded_map_debug<K, V, S>(
+	v: &BoundedBTreeMap<K, V, S>,
+	f: &mut fmt::Formatter,
+) -> Result<(), fmt::Error>
+where
+	K: fmt::Debug + Ord,
+	V: fmt::Debug,
+{
+	use core::fmt::Debug;
+	(&v as &BTreeMap<K, V>).fmt(f)
+}
+
 #[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 #[derivative(Debug)]
 pub struct CreateNftData {
 	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
-	#[derivative(Debug = "ignore")]
+	#[derivative(Debug(format_with = "bounded_debug"))]
 	pub const_data: BoundedVec<u8, CustomDataLimit>,
 	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
-	#[derivative(Debug = "ignore")]
+	#[derivative(Debug(format_with = "bounded_debug"))]
 	pub variable_data: BoundedVec<u8, CustomDataLimit>,
 }
 
@@ -440,10 +500,10 @@
 #[derivative(Debug)]
 pub struct CreateReFungibleData {
 	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
-	#[derivative(Debug = "ignore")]
+	#[derivative(Debug(format_with = "bounded_debug"))]
 	pub const_data: BoundedVec<u8, CustomDataLimit>,
 	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
-	#[derivative(Debug = "ignore")]
+	#[derivative(Debug(format_with = "bounded_debug"))]
 	pub variable_data: BoundedVec<u8, CustomDataLimit>,
 	pub pieces: u128,
 }
@@ -470,6 +530,47 @@
 	ReFungible(CreateReFungibleData),
 }
 
+#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
+#[derivative(Debug)]
+pub struct CreateNftExData<CrossAccountId> {
+	#[derivative(Debug(format_with = "bounded_debug"))]
+	pub const_data: BoundedVec<u8, CustomDataLimit>,
+	#[derivative(Debug(format_with = "bounded_debug"))]
+	pub variable_data: BoundedVec<u8, CustomDataLimit>,
+	pub owner: CrossAccountId,
+}
+
+#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
+#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]
+pub struct CreateRefungibleExData<CrossAccountId> {
+	#[derivative(Debug(format_with = "bounded_debug"))]
+	pub const_data: BoundedVec<u8, CustomDataLimit>,
+	#[derivative(Debug(format_with = "bounded_debug"))]
+	pub variable_data: BoundedVec<u8, CustomDataLimit>,
+	#[derivative(Debug(format_with = "bounded_map_debug"))]
+	pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,
+}
+
+#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
+#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]
+pub enum CreateItemExData<CrossAccountId> {
+	NFT(
+		#[derivative(Debug(format_with = "bounded_debug"))]
+		BoundedVec<CreateNftExData<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,
+	),
+	Fungible(
+		#[derivative(Debug(format_with = "bounded_map_debug"))]
+		BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,
+	),
+	/// Many tokens, each may have only one owner
+	RefungibleMultipleItems(
+		#[derivative(Debug(format_with = "bounded_debug"))]
+		BoundedVec<CreateRefungibleExData<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,
+	),
+	/// Single token, which may have many owners
+	RefungibleMultipleOwners(CreateRefungibleExData<CrossAccountId>),
+}
+
 impl CreateItemData {
 	pub fn data_size(&self) -> usize {
 		match self {
addedtests/src/createMultipleItemsEx.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/createMultipleItemsEx.test.ts
@@ -0,0 +1,58 @@
+import {expect} from 'chai';
+import privateKey from './substrate/privateKey';
+import usingApi, {executeTransaction} from './substrate/substrate-api';
+import {createCollectionExpectSuccess} from './util/helpers';
+
+describe('createMultipleItemsEx', () => {
+  it('can initialize multiple NFT with different owners', async () => {
+    const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+    const alice = privateKey('//Alice');
+    const bob = privateKey('//Bob');
+    const charlie = privateKey('//Charlie');
+    await usingApi(async (api) => {
+      const data = [
+        {
+          owner: {substrate: alice.address},
+          constData: '0x0000',
+          variableData: '0x1111',
+        }, {
+          owner: {substrate: bob.address},
+          constData: '0x2222',
+          variableData: '0x3333',
+        }, {
+          owner: {substrate: charlie.address},
+          constData: '0x4444',
+          variableData: '0x5555',
+        },
+      ];
+
+      await executeTransaction(api, alice, api.tx.unique.createMultipleItemsEx(collection, {
+        NFT: data,
+      }));
+      const tokens = await api.query.nonfungible.tokenData.entries(collection);
+      const json = tokens.map(([, token]) => token.toJSON());
+      expect(json).to.be.deep.equal(data);
+    });
+  });
+
+  it('fails when trying to set multiple owners when creating multiple refungibles', async () => {
+    const collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+    const alice = privateKey('//Alice');
+    const bob = privateKey('//Bob');
+
+    await usingApi(async (api) => {
+      // Polkadot requires map, and yet requires keys to be JSON encoded
+      const users = new Map();
+      users.set(JSON.stringify({substrate: alice.address}), 1);
+      users.set(JSON.stringify({substrate: bob.address}), 1);
+
+      // TODO: better error message?
+      await expect(executeTransaction(api, alice, api.tx.unique.createMultipleItemsEx(collection, {
+        RefungibleMultipleItems: [
+          {users},
+          {users},
+        ],
+      }))).to.be.rejectedWith(/^refungible\.NotRefungibleDataUsedToMintFungibleCollectionToken$/);
+    });
+  });
+});
modifiedtests/src/interfaces/augment-api-tx.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-tx.ts
+++ b/tests/src/interfaces/augment-api-tx.ts
@@ -5,7 +5,7 @@
 import type { Bytes, Compact, Option, U256, Vec, bool, u128, u16, u32, u64 } from '@polkadot/types-codec';
 import type { AnyNumber, ITuple } from '@polkadot/types-codec/types';
 import type { AccountId32, Call, H160, H256, MultiAddress, Perbill } from '@polkadot/types/interfaces/runtime';
-import type { CumulusPrimitivesParachainInherentParachainInherentData, EthereumTransactionTransactionV2, OrmlVestingVestingSchedule, PalletCommonAccountBasicCrossAccountIdRepr, UpDataStructsAccessMode, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCreateCollectionData, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion, XcmV1MultiLocation, XcmV2WeightLimit, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from '@polkadot/types/lookup';
+import type { CumulusPrimitivesParachainInherentParachainInherentData, EthereumTransactionTransactionV2, OrmlVestingVestingSchedule, PalletCommonAccountBasicCrossAccountIdRepr, UpDataStructsAccessMode, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCreateCollectionData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion, XcmV1MultiLocation, XcmV2WeightLimit, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from '@polkadot/types/lookup';
 
 declare module '@polkadot/api-base/types/submittable' {
   export interface AugmentedSubmittables<ApiType extends ApiTypes> {
@@ -710,6 +710,7 @@
        * * owner: Address, initial owner of the NFT.
        **/
       createMultipleItems: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, owner: PalletCommonAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, itemsData: Vec<UpDataStructsCreateItemData> | (UpDataStructsCreateItemData | { NFT: any } | { Fungible: any } | { ReFungible: any } | string | Uint8Array)[]) => SubmittableExtrinsic<ApiType>, [u32, PalletCommonAccountBasicCrossAccountIdRepr, Vec<UpDataStructsCreateItemData>]>;
+      createMultipleItemsEx: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, data: UpDataStructsCreateItemExData | { NFT: any } | { Fungible: any } | { RefungibleMultipleItems: any } | { RefungibleMultipleOwners: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, UpDataStructsCreateItemExData]>;
       /**
        * **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.
        * 
modifiedtests/src/interfaces/augment-types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-types.ts
+++ b/tests/src/interfaces/augment-types.ts
@@ -1,7 +1,7 @@
 // Auto-generated via `yarn polkadot-types-from-defs`, do not edit
 /* eslint-disable */
 
-import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportPalletId, FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchClass, FrameSupportWeightsDispatchInfo, FrameSupportWeightsPays, FrameSupportWeightsPerDispatchClassU32, FrameSupportWeightsPerDispatchClassU64, FrameSupportWeightsPerDispatchClassWeightsPerClass, FrameSupportWeightsRuntimeDbWeight, FrameSupportWeightsWeightToFeeCoefficient, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReleases, PalletBalancesReserveData, PalletCommonAccountBasicCrossAccountIdRepr, PalletCommonError, PalletCommonEvent, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletFungibleError, PalletInflationCall, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRefungibleItemData, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTimestampCall, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletUniqueRawEvent, PalletXcmCall, PalletXcmError, PalletXcmEvent, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV1AbridgedHostConfiguration, PolkadotPrimitivesV1AbridgedHrmpChannel, PolkadotPrimitivesV1PersistedValidationData, PolkadotPrimitivesV1UpgradeRestriction, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpTrieStorageProof, SpVersionRuntimeVersion, UniqueRuntimeRuntime, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateNftData, UpDataStructsCreateReFungibleData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipState, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from './unique';
+import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportPalletId, FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchClass, FrameSupportWeightsDispatchInfo, FrameSupportWeightsPays, FrameSupportWeightsPerDispatchClassU32, FrameSupportWeightsPerDispatchClassU64, FrameSupportWeightsPerDispatchClassWeightsPerClass, FrameSupportWeightsRuntimeDbWeight, FrameSupportWeightsWeightToFeeCoefficient, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReleases, PalletBalancesReserveData, PalletCommonAccountBasicCrossAccountIdRepr, PalletCommonError, PalletCommonEvent, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletFungibleError, PalletInflationCall, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRefungibleItemData, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTimestampCall, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletUniqueRawEvent, PalletXcmCall, PalletXcmError, PalletXcmEvent, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV1AbridgedHostConfiguration, PolkadotPrimitivesV1AbridgedHrmpChannel, PolkadotPrimitivesV1PersistedValidationData, PolkadotPrimitivesV1UpgradeRestriction, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpTrieStorageProof, SpVersionRuntimeVersion, UniqueRuntimeRuntime, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsCreateNftData, UpDataStructsCreateNftExData, UpDataStructsCreateReFungibleData, UpDataStructsCreateRefungibleExData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipState, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from './unique';
 import type { Data, StorageKey } from '@polkadot/types';
 import type { BitVec, Bool, Bytes, I128, I16, I256, I32, I64, I8, Json, Null, Raw, Text, Type, U128, U16, U256, U32, U64, U8, USize, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types-codec';
 import type { AssetApproval, AssetApprovalKey, AssetBalance, AssetDestroyWitness, AssetDetails, AssetMetadata, TAssetBalance, TAssetDepositBalance } from '@polkadot/types/interfaces/assets';
@@ -1159,8 +1159,11 @@
     UpDataStructsCreateCollectionData: UpDataStructsCreateCollectionData;
     UpDataStructsCreateFungibleData: UpDataStructsCreateFungibleData;
     UpDataStructsCreateItemData: UpDataStructsCreateItemData;
+    UpDataStructsCreateItemExData: UpDataStructsCreateItemExData;
     UpDataStructsCreateNftData: UpDataStructsCreateNftData;
+    UpDataStructsCreateNftExData: UpDataStructsCreateNftExData;
     UpDataStructsCreateReFungibleData: UpDataStructsCreateReFungibleData;
+    UpDataStructsCreateRefungibleExData: UpDataStructsCreateRefungibleExData;
     UpDataStructsMetaUpdatePermission: UpDataStructsMetaUpdatePermission;
     UpDataStructsSchemaVersion: UpDataStructsSchemaVersion;
     UpDataStructsSponsoringRateLimit: UpDataStructsSponsoringRateLimit;
modifiedtests/src/interfaces/lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/lookup.ts
+++ b/tests/src/interfaces/lookup.ts
@@ -1300,6 +1300,10 @@
         owner: 'PalletCommonAccountBasicCrossAccountIdRepr',
         itemsData: 'Vec<UpDataStructsCreateItemData>',
       },
+      create_multiple_items_ex: {
+        collectionId: 'u32',
+        data: 'UpDataStructsCreateItemExData',
+      },
       set_transfers_enabled_flag: {
         collectionId: 'u32',
         value: 'bool',
@@ -1474,11 +1478,38 @@
     pieces: 'u128'
   },
   /**
-   * Lookup181: pallet_template_transaction_payment::Call<T>
+   * Lookup180: up_data_structs::CreateItemExData<pallet_common::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
    **/
+  UpDataStructsCreateItemExData: {
+    _enum: {
+      NFT: 'Vec<UpDataStructsCreateNftExData>',
+      Fungible: 'BTreeMap<PalletCommonAccountBasicCrossAccountIdRepr, u128>',
+      RefungibleMultipleItems: 'Vec<UpDataStructsCreateRefungibleExData>',
+      RefungibleMultipleOwners: 'UpDataStructsCreateRefungibleExData'
+    }
+  },
+  /**
+   * Lookup182: up_data_structs::CreateNftExData<pallet_common::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
+   **/
+  UpDataStructsCreateNftExData: {
+    constData: 'Bytes',
+    variableData: 'Bytes',
+    owner: 'PalletCommonAccountBasicCrossAccountIdRepr'
+  },
+  /**
+   * Lookup189: up_data_structs::CreateRefungibleExData<pallet_common::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
+   **/
+  UpDataStructsCreateRefungibleExData: {
+    constData: 'Bytes',
+    variableData: 'Bytes',
+    users: 'BTreeMap<PalletCommonAccountBasicCrossAccountIdRepr, u128>'
+  },
+  /**
+   * Lookup192: pallet_template_transaction_payment::Call<T>
+   **/
   PalletTemplateTransactionPaymentCall: 'Null',
   /**
-   * Lookup182: pallet_evm::pallet::Call<T>
+   * Lookup193: pallet_evm::pallet::Call<T>
    **/
   PalletEvmCall: {
     _enum: {
@@ -1521,7 +1552,7 @@
     }
   },
   /**
-   * Lookup188: pallet_ethereum::pallet::Call<T>
+   * Lookup199: pallet_ethereum::pallet::Call<T>
    **/
   PalletEthereumCall: {
     _enum: {
@@ -1531,7 +1562,7 @@
     }
   },
   /**
-   * Lookup189: ethereum::transaction::TransactionV2
+   * Lookup200: ethereum::transaction::TransactionV2
    **/
   EthereumTransactionTransactionV2: {
     _enum: {
@@ -1541,7 +1572,7 @@
     }
   },
   /**
-   * Lookup190: ethereum::transaction::LegacyTransaction
+   * Lookup201: ethereum::transaction::LegacyTransaction
    **/
   EthereumTransactionLegacyTransaction: {
     nonce: 'U256',
@@ -1553,7 +1584,7 @@
     signature: 'EthereumTransactionTransactionSignature'
   },
   /**
-   * Lookup191: ethereum::transaction::TransactionAction
+   * Lookup202: ethereum::transaction::TransactionAction
    **/
   EthereumTransactionTransactionAction: {
     _enum: {
@@ -1562,7 +1593,7 @@
     }
   },
   /**
-   * Lookup192: ethereum::transaction::TransactionSignature
+   * Lookup203: ethereum::transaction::TransactionSignature
    **/
   EthereumTransactionTransactionSignature: {
     v: 'u64',
@@ -1570,7 +1601,7 @@
     s: 'H256'
   },
   /**
-   * Lookup194: ethereum::transaction::EIP2930Transaction
+   * Lookup205: ethereum::transaction::EIP2930Transaction
    **/
   EthereumTransactionEip2930Transaction: {
     chainId: 'u64',
@@ -1586,14 +1617,14 @@
     s: 'H256'
   },
   /**
-   * Lookup196: ethereum::transaction::AccessListItem
+   * Lookup207: ethereum::transaction::AccessListItem
    **/
   EthereumTransactionAccessListItem: {
     address: 'H160',
     slots: 'Vec<H256>'
   },
   /**
-   * Lookup197: ethereum::transaction::EIP1559Transaction
+   * Lookup208: ethereum::transaction::EIP1559Transaction
    **/
   EthereumTransactionEip1559Transaction: {
     chainId: 'u64',
@@ -1610,7 +1641,7 @@
     s: 'H256'
   },
   /**
-   * Lookup198: pallet_evm_migration::pallet::Call<T>
+   * Lookup209: pallet_evm_migration::pallet::Call<T>
    **/
   PalletEvmMigrationCall: {
     _enum: {
@@ -1628,7 +1659,7 @@
     }
   },
   /**
-   * Lookup201: pallet_sudo::pallet::Event<T>
+   * Lookup212: pallet_sudo::pallet::Event<T>
    **/
   PalletSudoEvent: {
     _enum: {
@@ -1644,7 +1675,7 @@
     }
   },
   /**
-   * Lookup203: sp_runtime::DispatchError
+   * Lookup214: sp_runtime::DispatchError
    **/
   SpRuntimeDispatchError: {
     _enum: {
@@ -1660,32 +1691,32 @@
     }
   },
   /**
-   * Lookup204: sp_runtime::ModuleError
+   * Lookup215: sp_runtime::ModuleError
    **/
   SpRuntimeModuleError: {
     index: 'u8',
     error: 'u8'
   },
   /**
-   * Lookup205: sp_runtime::TokenError
+   * Lookup216: sp_runtime::TokenError
    **/
   SpRuntimeTokenError: {
     _enum: ['NoFunds', 'WouldDie', 'BelowMinimum', 'CannotCreate', 'UnknownAsset', 'Frozen', 'Unsupported']
   },
   /**
-   * Lookup206: sp_runtime::ArithmeticError
+   * Lookup217: sp_runtime::ArithmeticError
    **/
   SpRuntimeArithmeticError: {
     _enum: ['Underflow', 'Overflow', 'DivisionByZero']
   },
   /**
-   * Lookup207: pallet_sudo::pallet::Error<T>
+   * Lookup218: pallet_sudo::pallet::Error<T>
    **/
   PalletSudoError: {
     _enum: ['RequireSudo']
   },
   /**
-   * Lookup208: frame_system::AccountInfo<Index, pallet_balances::AccountData<Balance>>
+   * Lookup219: frame_system::AccountInfo<Index, pallet_balances::AccountData<Balance>>
    **/
   FrameSystemAccountInfo: {
     nonce: 'u32',
@@ -1695,7 +1726,7 @@
     data: 'PalletBalancesAccountData'
   },
   /**
-   * Lookup209: frame_support::weights::PerDispatchClass<T>
+   * Lookup220: frame_support::weights::PerDispatchClass<T>
    **/
   FrameSupportWeightsPerDispatchClassU64: {
     normal: 'u64',
@@ -1703,13 +1734,13 @@
     mandatory: 'u64'
   },
   /**
-   * Lookup210: sp_runtime::generic::digest::Digest
+   * Lookup221: sp_runtime::generic::digest::Digest
    **/
   SpRuntimeDigest: {
     logs: 'Vec<SpRuntimeDigestDigestItem>'
   },
   /**
-   * Lookup212: sp_runtime::generic::digest::DigestItem
+   * Lookup223: sp_runtime::generic::digest::DigestItem
    **/
   SpRuntimeDigestDigestItem: {
     _enum: {
@@ -1725,7 +1756,7 @@
     }
   },
   /**
-   * Lookup214: frame_system::EventRecord<unique_runtime::Event, primitive_types::H256>
+   * Lookup225: frame_system::EventRecord<unique_runtime::Event, primitive_types::H256>
    **/
   FrameSystemEventRecord: {
     phase: 'FrameSystemPhase',
@@ -1733,7 +1764,7 @@
     topics: 'Vec<H256>'
   },
   /**
-   * Lookup216: frame_system::pallet::Event<T>
+   * Lookup227: frame_system::pallet::Event<T>
    **/
   FrameSystemEvent: {
     _enum: {
@@ -1761,7 +1792,7 @@
     }
   },
   /**
-   * Lookup217: frame_support::weights::DispatchInfo
+   * Lookup228: frame_support::weights::DispatchInfo
    **/
   FrameSupportWeightsDispatchInfo: {
     weight: 'u64',
@@ -1769,19 +1800,19 @@
     paysFee: 'FrameSupportWeightsPays'
   },
   /**
-   * Lookup218: frame_support::weights::DispatchClass
+   * Lookup229: frame_support::weights::DispatchClass
    **/
   FrameSupportWeightsDispatchClass: {
     _enum: ['Normal', 'Operational', 'Mandatory']
   },
   /**
-   * Lookup219: frame_support::weights::Pays
+   * Lookup230: frame_support::weights::Pays
    **/
   FrameSupportWeightsPays: {
     _enum: ['Yes', 'No']
   },
   /**
-   * Lookup220: orml_vesting::module::Event<T>
+   * Lookup231: orml_vesting::module::Event<T>
    **/
   OrmlVestingModuleEvent: {
     _enum: {
@@ -1800,7 +1831,7 @@
     }
   },
   /**
-   * Lookup221: cumulus_pallet_xcmp_queue::pallet::Event<T>
+   * Lookup232: cumulus_pallet_xcmp_queue::pallet::Event<T>
    **/
   CumulusPalletXcmpQueueEvent: {
     _enum: {
@@ -1815,7 +1846,7 @@
     }
   },
   /**
-   * Lookup222: pallet_xcm::pallet::Event<T>
+   * Lookup233: pallet_xcm::pallet::Event<T>
    **/
   PalletXcmEvent: {
     _enum: {
@@ -1838,7 +1869,7 @@
     }
   },
   /**
-   * Lookup223: xcm::v2::traits::Outcome
+   * Lookup234: xcm::v2::traits::Outcome
    **/
   XcmV2TraitsOutcome: {
     _enum: {
@@ -1848,7 +1879,7 @@
     }
   },
   /**
-   * Lookup225: cumulus_pallet_xcm::pallet::Event<T>
+   * Lookup236: cumulus_pallet_xcm::pallet::Event<T>
    **/
   CumulusPalletXcmEvent: {
     _enum: {
@@ -1858,7 +1889,7 @@
     }
   },
   /**
-   * Lookup226: cumulus_pallet_dmp_queue::pallet::Event<T>
+   * Lookup237: cumulus_pallet_dmp_queue::pallet::Event<T>
    **/
   CumulusPalletDmpQueueEvent: {
     _enum: {
@@ -1871,7 +1902,7 @@
     }
   },
   /**
-   * Lookup227: pallet_unique::RawEvent<sp_core::crypto::AccountId32, pallet_common::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
+   * Lookup238: pallet_unique::RawEvent<sp_core::crypto::AccountId32, pallet_common::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
    **/
   PalletUniqueRawEvent: {
     _enum: {
@@ -1893,7 +1924,7 @@
     }
   },
   /**
-   * Lookup228: pallet_common::pallet::Event<T>
+   * Lookup239: pallet_common::pallet::Event<T>
    **/
   PalletCommonEvent: {
     _enum: {
@@ -1906,7 +1937,7 @@
     }
   },
   /**
-   * Lookup229: pallet_evm::pallet::Event<T>
+   * Lookup240: pallet_evm::pallet::Event<T>
    **/
   PalletEvmEvent: {
     _enum: {
@@ -1920,7 +1951,7 @@
     }
   },
   /**
-   * Lookup230: ethereum::log::Log
+   * Lookup241: ethereum::log::Log
    **/
   EthereumLog: {
     address: 'H160',
@@ -1928,7 +1959,7 @@
     data: 'Bytes'
   },
   /**
-   * Lookup231: pallet_ethereum::pallet::Event
+   * Lookup242: pallet_ethereum::pallet::Event
    **/
   PalletEthereumEvent: {
     _enum: {
@@ -1936,7 +1967,7 @@
     }
   },
   /**
-   * Lookup232: evm_core::error::ExitReason
+   * Lookup243: evm_core::error::ExitReason
    **/
   EvmCoreErrorExitReason: {
     _enum: {
@@ -1947,13 +1978,13 @@
     }
   },
   /**
-   * Lookup233: evm_core::error::ExitSucceed
+   * Lookup244: evm_core::error::ExitSucceed
    **/
   EvmCoreErrorExitSucceed: {
     _enum: ['Stopped', 'Returned', 'Suicided']
   },
   /**
-   * Lookup234: evm_core::error::ExitError
+   * Lookup245: evm_core::error::ExitError
    **/
   EvmCoreErrorExitError: {
     _enum: {
@@ -1975,13 +2006,13 @@
     }
   },
   /**
-   * Lookup237: evm_core::error::ExitRevert
+   * Lookup248: evm_core::error::ExitRevert
    **/
   EvmCoreErrorExitRevert: {
     _enum: ['Reverted']
   },
   /**
-   * Lookup238: evm_core::error::ExitFatal
+   * Lookup249: evm_core::error::ExitFatal
    **/
   EvmCoreErrorExitFatal: {
     _enum: {
@@ -1992,7 +2023,7 @@
     }
   },
   /**
-   * Lookup239: frame_system::Phase
+   * Lookup250: frame_system::Phase
    **/
   FrameSystemPhase: {
     _enum: {
@@ -2002,14 +2033,14 @@
     }
   },
   /**
-   * Lookup241: frame_system::LastRuntimeUpgradeInfo
+   * Lookup252: frame_system::LastRuntimeUpgradeInfo
    **/
   FrameSystemLastRuntimeUpgradeInfo: {
     specVersion: 'Compact<u32>',
     specName: 'Text'
   },
   /**
-   * Lookup242: frame_system::limits::BlockWeights
+   * Lookup253: frame_system::limits::BlockWeights
    **/
   FrameSystemLimitsBlockWeights: {
     baseBlock: 'u64',
@@ -2017,7 +2048,7 @@
     perClass: 'FrameSupportWeightsPerDispatchClassWeightsPerClass'
   },
   /**
-   * Lookup243: frame_support::weights::PerDispatchClass<frame_system::limits::WeightsPerClass>
+   * Lookup254: frame_support::weights::PerDispatchClass<frame_system::limits::WeightsPerClass>
    **/
   FrameSupportWeightsPerDispatchClassWeightsPerClass: {
     normal: 'FrameSystemLimitsWeightsPerClass',
@@ -2025,7 +2056,7 @@
     mandatory: 'FrameSystemLimitsWeightsPerClass'
   },
   /**
-   * Lookup244: frame_system::limits::WeightsPerClass
+   * Lookup255: frame_system::limits::WeightsPerClass
    **/
   FrameSystemLimitsWeightsPerClass: {
     baseExtrinsic: 'u64',
@@ -2034,13 +2065,13 @@
     reserved: 'Option<u64>'
   },
   /**
-   * Lookup246: frame_system::limits::BlockLength
+   * Lookup257: frame_system::limits::BlockLength
    **/
   FrameSystemLimitsBlockLength: {
     max: 'FrameSupportWeightsPerDispatchClassU32'
   },
   /**
-   * Lookup247: frame_support::weights::PerDispatchClass<T>
+   * Lookup258: frame_support::weights::PerDispatchClass<T>
    **/
   FrameSupportWeightsPerDispatchClassU32: {
     normal: 'u32',
@@ -2048,14 +2079,14 @@
     mandatory: 'u32'
   },
   /**
-   * Lookup248: frame_support::weights::RuntimeDbWeight
+   * Lookup259: frame_support::weights::RuntimeDbWeight
    **/
   FrameSupportWeightsRuntimeDbWeight: {
     read: 'u64',
     write: 'u64'
   },
   /**
-   * Lookup249: sp_version::RuntimeVersion
+   * Lookup260: sp_version::RuntimeVersion
    **/
   SpVersionRuntimeVersion: {
     specName: 'Text',
@@ -2068,19 +2099,19 @@
     stateVersion: 'u8'
   },
   /**
-   * Lookup253: frame_system::pallet::Error<T>
+   * Lookup264: frame_system::pallet::Error<T>
    **/
   FrameSystemError: {
     _enum: ['InvalidSpecName', 'SpecVersionNeedsToIncrease', 'FailedToExtractRuntimeVersion', 'NonDefaultComposite', 'NonZeroRefCount', 'CallFiltered']
   },
   /**
-   * Lookup255: orml_vesting::module::Error<T>
+   * Lookup266: orml_vesting::module::Error<T>
    **/
   OrmlVestingModuleError: {
     _enum: ['ZeroVestingPeriod', 'ZeroVestingPeriodCount', 'InsufficientBalanceToLock', 'TooManyVestingSchedules', 'AmountLow', 'MaxVestingSchedulesExceeded']
   },
   /**
-   * Lookup257: cumulus_pallet_xcmp_queue::InboundChannelDetails
+   * Lookup268: cumulus_pallet_xcmp_queue::InboundChannelDetails
    **/
   CumulusPalletXcmpQueueInboundChannelDetails: {
     sender: 'u32',
@@ -2088,19 +2119,19 @@
     messageMetadata: 'Vec<(u32,PolkadotParachainPrimitivesXcmpMessageFormat)>'
   },
   /**
-   * Lookup258: cumulus_pallet_xcmp_queue::InboundState
+   * Lookup269: cumulus_pallet_xcmp_queue::InboundState
    **/
   CumulusPalletXcmpQueueInboundState: {
     _enum: ['Ok', 'Suspended']
   },
   /**
-   * Lookup261: polkadot_parachain::primitives::XcmpMessageFormat
+   * Lookup272: polkadot_parachain::primitives::XcmpMessageFormat
    **/
   PolkadotParachainPrimitivesXcmpMessageFormat: {
     _enum: ['ConcatenatedVersionedXcm', 'ConcatenatedEncodedBlob', 'Signals']
   },
   /**
-   * Lookup264: cumulus_pallet_xcmp_queue::OutboundChannelDetails
+   * Lookup275: cumulus_pallet_xcmp_queue::OutboundChannelDetails
    **/
   CumulusPalletXcmpQueueOutboundChannelDetails: {
     recipient: 'u32',
@@ -2110,13 +2141,13 @@
     lastIndex: 'u16'
   },
   /**
-   * Lookup265: cumulus_pallet_xcmp_queue::OutboundState
+   * Lookup276: cumulus_pallet_xcmp_queue::OutboundState
    **/
   CumulusPalletXcmpQueueOutboundState: {
     _enum: ['Ok', 'Suspended']
   },
   /**
-   * Lookup267: cumulus_pallet_xcmp_queue::QueueConfigData
+   * Lookup278: cumulus_pallet_xcmp_queue::QueueConfigData
    **/
   CumulusPalletXcmpQueueQueueConfigData: {
     suspendThreshold: 'u32',
@@ -2127,29 +2158,29 @@
     xcmpMaxIndividualWeight: 'u64'
   },
   /**
-   * Lookup269: cumulus_pallet_xcmp_queue::pallet::Error<T>
+   * Lookup280: cumulus_pallet_xcmp_queue::pallet::Error<T>
    **/
   CumulusPalletXcmpQueueError: {
     _enum: ['FailedToSend', 'BadXcmOrigin', 'BadXcm', 'BadOverweightIndex', 'WeightOverLimit']
   },
   /**
-   * Lookup270: pallet_xcm::pallet::Error<T>
+   * Lookup281: pallet_xcm::pallet::Error<T>
    **/
   PalletXcmError: {
     _enum: ['Unreachable', 'SendFailure', 'Filtered', 'UnweighableMessage', 'DestinationNotInvertible', 'Empty', 'CannotReanchor', 'TooManyAssets', 'InvalidOrigin', 'BadVersion', 'BadLocation', 'NoSubscription', 'AlreadySubscribed']
   },
   /**
-   * Lookup271: cumulus_pallet_xcm::pallet::Error<T>
+   * Lookup282: cumulus_pallet_xcm::pallet::Error<T>
    **/
   CumulusPalletXcmError: 'Null',
   /**
-   * Lookup272: cumulus_pallet_dmp_queue::ConfigData
+   * Lookup283: cumulus_pallet_dmp_queue::ConfigData
    **/
   CumulusPalletDmpQueueConfigData: {
     maxIndividual: 'u64'
   },
   /**
-   * Lookup273: cumulus_pallet_dmp_queue::PageIndexData
+   * Lookup284: cumulus_pallet_dmp_queue::PageIndexData
    **/
   CumulusPalletDmpQueuePageIndexData: {
     beginUsed: 'u32',
@@ -2157,19 +2188,19 @@
     overweightCount: 'u64'
   },
   /**
-   * Lookup276: cumulus_pallet_dmp_queue::pallet::Error<T>
+   * Lookup287: cumulus_pallet_dmp_queue::pallet::Error<T>
    **/
   CumulusPalletDmpQueueError: {
     _enum: ['Unknown', 'OverLimit']
   },
   /**
-   * Lookup280: pallet_unique::Error<T>
+   * Lookup291: pallet_unique::Error<T>
    **/
   PalletUniqueError: {
     _enum: ['CollectionDecimalPointLimitExceeded', 'ConfirmUnsetSponsorFail', 'EmptyArgument']
   },
   /**
-   * Lookup281: up_data_structs::Collection<sp_core::crypto::AccountId32>
+   * Lookup292: up_data_structs::Collection<sp_core::crypto::AccountId32>
    **/
   UpDataStructsCollection: {
     owner: 'AccountId32',
@@ -2188,7 +2219,7 @@
     metaUpdatePermission: 'UpDataStructsMetaUpdatePermission'
   },
   /**
-   * Lookup282: up_data_structs::SponsorshipState<sp_core::crypto::AccountId32>
+   * Lookup293: up_data_structs::SponsorshipState<sp_core::crypto::AccountId32>
    **/
   UpDataStructsSponsorshipState: {
     _enum: {
@@ -2198,7 +2229,7 @@
     }
   },
   /**
-   * Lookup285: up_data_structs::CollectionStats
+   * Lookup296: up_data_structs::CollectionStats
    **/
   UpDataStructsCollectionStats: {
     created: 'u32',
@@ -2206,32 +2237,32 @@
     alive: 'u32'
   },
   /**
-   * Lookup286: pallet_common::pallet::Error<T>
+   * Lookup297: pallet_common::pallet::Error<T>
    **/
   PalletCommonError: {
     _enum: ['CollectionNotFound', 'MustBeTokenOwner', 'NoPermission', 'PublicMintingNotAllowed', 'AddressNotInAllowlist', 'CollectionNameLimitExceeded', 'CollectionDescriptionLimitExceeded', 'CollectionTokenPrefixLimitExceeded', 'TotalCollectionsLimitExceeded', 'TokenVariableDataLimitExceeded', 'CollectionAdminCountExceeded', 'CollectionLimitBoundsExceeded', 'OwnerPermissionsCantBeReverted', 'TransferNotAllowed', 'AccountTokenLimitExceeded', 'CollectionTokenLimitExceeded', 'MetadataFlagFrozen', 'TokenNotFound', 'TokenValueTooLow', 'ApprovedValueTooLow', 'CantApproveMoreThanOwned', 'AddressIsZero', 'UnsupportedOperation']
   },
   /**
-   * Lookup288: pallet_fungible::pallet::Error<T>
+   * Lookup299: pallet_fungible::pallet::Error<T>
    **/
   PalletFungibleError: {
     _enum: ['NotFungibleDataUsedToMintFungibleCollectionToken', 'FungibleItemsHaveNoId', 'FungibleItemsDontHaveData']
   },
   /**
-   * Lookup289: pallet_refungible::ItemData
+   * Lookup300: pallet_refungible::ItemData
    **/
   PalletRefungibleItemData: {
     constData: 'Bytes',
     variableData: 'Bytes'
   },
   /**
-   * Lookup293: pallet_refungible::pallet::Error<T>
+   * Lookup304: pallet_refungible::pallet::Error<T>
    **/
   PalletRefungibleError: {
     _enum: ['NotRefungibleDataUsedToMintFungibleCollectionToken', 'WrongRefungiblePieces']
   },
   /**
-   * Lookup294: pallet_nonfungible::ItemData<pallet_common::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
+   * Lookup305: pallet_nonfungible::ItemData<pallet_common::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>
    **/
   PalletNonfungibleItemData: {
     constData: 'Bytes',
@@ -2239,19 +2270,19 @@
     owner: 'PalletCommonAccountBasicCrossAccountIdRepr'
   },
   /**
-   * Lookup295: pallet_nonfungible::pallet::Error<T>
+   * Lookup306: pallet_nonfungible::pallet::Error<T>
    **/
   PalletNonfungibleError: {
     _enum: ['NotNonfungibleDataUsedToMintFungibleCollectionToken', 'NonfungibleItemsHaveNoAmount']
   },
   /**
-   * Lookup297: pallet_evm::pallet::Error<T>
+   * Lookup308: pallet_evm::pallet::Error<T>
    **/
   PalletEvmError: {
     _enum: ['BalanceLow', 'FeeOverflow', 'PaymentOverflow', 'WithdrawFailed', 'GasPriceTooLow', 'InvalidNonce']
   },
   /**
-   * Lookup300: fp_rpc::TransactionStatus
+   * Lookup311: fp_rpc::TransactionStatus
    **/
   FpRpcTransactionStatus: {
     transactionHash: 'H256',
@@ -2263,11 +2294,11 @@
     logsBloom: 'EthbloomBloom'
   },
   /**
-   * Lookup303: ethbloom::Bloom
+   * Lookup314: ethbloom::Bloom
    **/
   EthbloomBloom: '[u8;256]',
   /**
-   * Lookup305: ethereum::receipt::ReceiptV3
+   * Lookup316: ethereum::receipt::ReceiptV3
    **/
   EthereumReceiptReceiptV3: {
     _enum: {
@@ -2277,7 +2308,7 @@
     }
   },
   /**
-   * Lookup306: ethereum::receipt::EIP658ReceiptData
+   * Lookup317: ethereum::receipt::EIP658ReceiptData
    **/
   EthereumReceiptEip658ReceiptData: {
     statusCode: 'u8',
@@ -2286,7 +2317,7 @@
     logs: 'Vec<EthereumLog>'
   },
   /**
-   * Lookup307: ethereum::block::Block<ethereum::transaction::TransactionV2>
+   * Lookup318: ethereum::block::Block<ethereum::transaction::TransactionV2>
    **/
   EthereumBlock: {
     header: 'EthereumHeader',
@@ -2294,7 +2325,7 @@
     ommers: 'Vec<EthereumHeader>'
   },
   /**
-   * Lookup308: ethereum::header::Header
+   * Lookup319: ethereum::header::Header
    **/
   EthereumHeader: {
     parentHash: 'H256',
@@ -2314,41 +2345,41 @@
     nonce: 'EthereumTypesHashH64'
   },
   /**
-   * Lookup309: ethereum_types::hash::H64
+   * Lookup320: ethereum_types::hash::H64
    **/
   EthereumTypesHashH64: '[u8;8]',
   /**
-   * Lookup314: pallet_ethereum::pallet::Error<T>
+   * Lookup325: pallet_ethereum::pallet::Error<T>
    **/
   PalletEthereumError: {
     _enum: ['InvalidSignature', 'PreLogExists']
   },
   /**
-   * Lookup315: pallet_evm_coder_substrate::pallet::Error<T>
+   * Lookup326: pallet_evm_coder_substrate::pallet::Error<T>
    **/
   PalletEvmCoderSubstrateError: {
     _enum: ['OutOfGas', 'OutOfFund']
   },
   /**
-   * Lookup316: pallet_evm_contract_helpers::SponsoringModeT
+   * Lookup327: pallet_evm_contract_helpers::SponsoringModeT
    **/
   PalletEvmContractHelpersSponsoringModeT: {
     _enum: ['Disabled', 'Allowlisted', 'Generous']
   },
   /**
-   * Lookup318: pallet_evm_contract_helpers::pallet::Error<T>
+   * Lookup329: pallet_evm_contract_helpers::pallet::Error<T>
    **/
   PalletEvmContractHelpersError: {
     _enum: ['NoPermission']
   },
   /**
-   * Lookup319: pallet_evm_migration::pallet::Error<T>
+   * Lookup330: pallet_evm_migration::pallet::Error<T>
    **/
   PalletEvmMigrationError: {
     _enum: ['AccountNotEmpty', 'AccountIsNotMigrating']
   },
   /**
-   * Lookup321: sp_runtime::MultiSignature
+   * Lookup332: sp_runtime::MultiSignature
    **/
   SpRuntimeMultiSignature: {
     _enum: {
@@ -2358,39 +2389,39 @@
     }
   },
   /**
-   * Lookup322: sp_core::ed25519::Signature
+   * Lookup333: sp_core::ed25519::Signature
    **/
   SpCoreEd25519Signature: '[u8;64]',
   /**
-   * Lookup324: sp_core::sr25519::Signature
+   * Lookup335: sp_core::sr25519::Signature
    **/
   SpCoreSr25519Signature: '[u8;64]',
   /**
-   * Lookup325: sp_core::ecdsa::Signature
+   * Lookup336: sp_core::ecdsa::Signature
    **/
   SpCoreEcdsaSignature: '[u8;65]',
   /**
-   * Lookup328: frame_system::extensions::check_spec_version::CheckSpecVersion<T>
+   * Lookup339: frame_system::extensions::check_spec_version::CheckSpecVersion<T>
    **/
   FrameSystemExtensionsCheckSpecVersion: 'Null',
   /**
-   * Lookup329: frame_system::extensions::check_genesis::CheckGenesis<T>
+   * Lookup340: frame_system::extensions::check_genesis::CheckGenesis<T>
    **/
   FrameSystemExtensionsCheckGenesis: 'Null',
   /**
-   * Lookup332: frame_system::extensions::check_nonce::CheckNonce<T>
+   * Lookup343: frame_system::extensions::check_nonce::CheckNonce<T>
    **/
   FrameSystemExtensionsCheckNonce: 'Compact<u32>',
   /**
-   * Lookup333: frame_system::extensions::check_weight::CheckWeight<T>
+   * Lookup344: frame_system::extensions::check_weight::CheckWeight<T>
    **/
   FrameSystemExtensionsCheckWeight: 'Null',
   /**
-   * Lookup334: pallet_template_transaction_payment::ChargeTransactionPayment<unique_runtime::Runtime>
+   * Lookup345: pallet_template_transaction_payment::ChargeTransactionPayment<unique_runtime::Runtime>
    **/
   PalletTemplateTransactionPaymentChargeTransactionPayment: 'Compact<u128>',
   /**
-   * Lookup335: unique_runtime::Runtime
+   * Lookup346: unique_runtime::Runtime
    **/
   UniqueRuntimeRuntime: 'Null'
 };
modifiedtests/src/interfaces/types-lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/types-lookup.ts
+++ b/tests/src/interfaces/types-lookup.ts
@@ -1421,6 +1421,11 @@
       readonly owner: PalletCommonAccountBasicCrossAccountIdRepr;
       readonly itemsData: Vec<UpDataStructsCreateItemData>;
     } & Struct;
+    readonly isCreateMultipleItemsEx: boolean;
+    readonly asCreateMultipleItemsEx: {
+      readonly collectionId: u32;
+      readonly data: UpDataStructsCreateItemExData;
+    } & Struct;
     readonly isSetTransfersEnabledFlag: boolean;
     readonly asSetTransfersEnabledFlag: {
       readonly collectionId: u32;
@@ -1497,7 +1502,7 @@
       readonly collectionId: u32;
       readonly newLimit: UpDataStructsCollectionLimits;
     } & Struct;
-    readonly type: 'CreateCollection' | 'CreateCollectionEx' | 'DestroyCollection' | 'AddToAllowList' | 'RemoveFromAllowList' | 'SetPublicAccessMode' | 'SetMintPermission' | 'ChangeCollectionOwner' | 'AddCollectionAdmin' | 'RemoveCollectionAdmin' | 'SetCollectionSponsor' | 'ConfirmSponsorship' | 'RemoveCollectionSponsor' | 'CreateItem' | 'CreateMultipleItems' | 'SetTransfersEnabledFlag' | 'BurnItem' | 'BurnFrom' | 'Transfer' | 'Approve' | 'TransferFrom' | 'SetVariableMetaData' | 'SetMetaUpdatePermissionFlag' | 'SetSchemaVersion' | 'SetOffchainSchema' | 'SetConstOnChainSchema' | 'SetVariableOnChainSchema' | 'SetCollectionLimits';
+    readonly type: 'CreateCollection' | 'CreateCollectionEx' | 'DestroyCollection' | 'AddToAllowList' | 'RemoveFromAllowList' | 'SetPublicAccessMode' | 'SetMintPermission' | 'ChangeCollectionOwner' | 'AddCollectionAdmin' | 'RemoveCollectionAdmin' | 'SetCollectionSponsor' | 'ConfirmSponsorship' | 'RemoveCollectionSponsor' | 'CreateItem' | 'CreateMultipleItems' | 'CreateMultipleItemsEx' | 'SetTransfersEnabledFlag' | 'BurnItem' | 'BurnFrom' | 'Transfer' | 'Approve' | 'TransferFrom' | 'SetVariableMetaData' | 'SetMetaUpdatePermissionFlag' | 'SetSchemaVersion' | 'SetOffchainSchema' | 'SetConstOnChainSchema' | 'SetVariableOnChainSchema' | 'SetCollectionLimits';
   }
 
   /** @name UpDataStructsCollectionMode (155) */
@@ -1606,10 +1611,37 @@
     readonly pieces: u128;
   }
 
-  /** @name PalletTemplateTransactionPaymentCall (181) */
+  /** @name UpDataStructsCreateItemExData (180) */
+  export interface UpDataStructsCreateItemExData extends Enum {
+    readonly isNft: boolean;
+    readonly asNft: Vec<UpDataStructsCreateNftExData>;
+    readonly isFungible: boolean;
+    readonly asFungible: BTreeMap<PalletCommonAccountBasicCrossAccountIdRepr, u128>;
+    readonly isRefungibleMultipleItems: boolean;
+    readonly asRefungibleMultipleItems: Vec<UpDataStructsCreateRefungibleExData>;
+    readonly isRefungibleMultipleOwners: boolean;
+    readonly asRefungibleMultipleOwners: UpDataStructsCreateRefungibleExData;
+    readonly type: 'Nft' | 'Fungible' | 'RefungibleMultipleItems' | 'RefungibleMultipleOwners';
+  }
+
+  /** @name UpDataStructsCreateNftExData (182) */
+  export interface UpDataStructsCreateNftExData extends Struct {
+    readonly constData: Bytes;
+    readonly variableData: Bytes;
+    readonly owner: PalletCommonAccountBasicCrossAccountIdRepr;
+  }
+
+  /** @name UpDataStructsCreateRefungibleExData (189) */
+  export interface UpDataStructsCreateRefungibleExData extends Struct {
+    readonly constData: Bytes;
+    readonly variableData: Bytes;
+    readonly users: BTreeMap<PalletCommonAccountBasicCrossAccountIdRepr, u128>;
+  }
+
+  /** @name PalletTemplateTransactionPaymentCall (192) */
   export type PalletTemplateTransactionPaymentCall = Null;
 
-  /** @name PalletEvmCall (182) */
+  /** @name PalletEvmCall (193) */
   export interface PalletEvmCall extends Enum {
     readonly isWithdraw: boolean;
     readonly asWithdraw: {
@@ -1654,7 +1686,7 @@
     readonly type: 'Withdraw' | 'Call' | 'Create' | 'Create2';
   }
 
-  /** @name PalletEthereumCall (188) */
+  /** @name PalletEthereumCall (199) */
   export interface PalletEthereumCall extends Enum {
     readonly isTransact: boolean;
     readonly asTransact: {
@@ -1663,7 +1695,7 @@
     readonly type: 'Transact';
   }
 
-  /** @name EthereumTransactionTransactionV2 (189) */
+  /** @name EthereumTransactionTransactionV2 (200) */
   export interface EthereumTransactionTransactionV2 extends Enum {
     readonly isLegacy: boolean;
     readonly asLegacy: EthereumTransactionLegacyTransaction;
@@ -1674,7 +1706,7 @@
     readonly type: 'Legacy' | 'Eip2930' | 'Eip1559';
   }
 
-  /** @name EthereumTransactionLegacyTransaction (190) */
+  /** @name EthereumTransactionLegacyTransaction (201) */
   export interface EthereumTransactionLegacyTransaction extends Struct {
     readonly nonce: U256;
     readonly gasPrice: U256;
@@ -1685,7 +1717,7 @@
     readonly signature: EthereumTransactionTransactionSignature;
   }
 
-  /** @name EthereumTransactionTransactionAction (191) */
+  /** @name EthereumTransactionTransactionAction (202) */
   export interface EthereumTransactionTransactionAction extends Enum {
     readonly isCall: boolean;
     readonly asCall: H160;
@@ -1693,14 +1725,14 @@
     readonly type: 'Call' | 'Create';
   }
 
-  /** @name EthereumTransactionTransactionSignature (192) */
+  /** @name EthereumTransactionTransactionSignature (203) */
   export interface EthereumTransactionTransactionSignature extends Struct {
     readonly v: u64;
     readonly r: H256;
     readonly s: H256;
   }
 
-  /** @name EthereumTransactionEip2930Transaction (194) */
+  /** @name EthereumTransactionEip2930Transaction (205) */
   export interface EthereumTransactionEip2930Transaction extends Struct {
     readonly chainId: u64;
     readonly nonce: U256;
@@ -1715,13 +1747,13 @@
     readonly s: H256;
   }
 
-  /** @name EthereumTransactionAccessListItem (196) */
+  /** @name EthereumTransactionAccessListItem (207) */
   export interface EthereumTransactionAccessListItem extends Struct {
     readonly address: H160;
     readonly slots: Vec<H256>;
   }
 
-  /** @name EthereumTransactionEip1559Transaction (197) */
+  /** @name EthereumTransactionEip1559Transaction (208) */
   export interface EthereumTransactionEip1559Transaction extends Struct {
     readonly chainId: u64;
     readonly nonce: U256;
@@ -1737,7 +1769,7 @@
     readonly s: H256;
   }
 
-  /** @name PalletEvmMigrationCall (198) */
+  /** @name PalletEvmMigrationCall (209) */
   export interface PalletEvmMigrationCall extends Enum {
     readonly isBegin: boolean;
     readonly asBegin: {
@@ -1756,7 +1788,7 @@
     readonly type: 'Begin' | 'SetData' | 'Finish';
   }
 
-  /** @name PalletSudoEvent (201) */
+  /** @name PalletSudoEvent (212) */
   export interface PalletSudoEvent extends Enum {
     readonly isSudid: boolean;
     readonly asSudid: {
@@ -1773,7 +1805,7 @@
     readonly type: 'Sudid' | 'KeyChanged' | 'SudoAsDone';
   }
 
-  /** @name SpRuntimeDispatchError (203) */
+  /** @name SpRuntimeDispatchError (214) */
   export interface SpRuntimeDispatchError extends Enum {
     readonly isOther: boolean;
     readonly isCannotLookup: boolean;
@@ -1790,13 +1822,13 @@
     readonly type: 'Other' | 'CannotLookup' | 'BadOrigin' | 'Module' | 'ConsumerRemaining' | 'NoProviders' | 'TooManyConsumers' | 'Token' | 'Arithmetic';
   }
 
-  /** @name SpRuntimeModuleError (204) */
+  /** @name SpRuntimeModuleError (215) */
   export interface SpRuntimeModuleError extends Struct {
     readonly index: u8;
     readonly error: u8;
   }
 
-  /** @name SpRuntimeTokenError (205) */
+  /** @name SpRuntimeTokenError (216) */
   export interface SpRuntimeTokenError extends Enum {
     readonly isNoFunds: boolean;
     readonly isWouldDie: boolean;
@@ -1808,7 +1840,7 @@
     readonly type: 'NoFunds' | 'WouldDie' | 'BelowMinimum' | 'CannotCreate' | 'UnknownAsset' | 'Frozen' | 'Unsupported';
   }
 
-  /** @name SpRuntimeArithmeticError (206) */
+  /** @name SpRuntimeArithmeticError (217) */
   export interface SpRuntimeArithmeticError extends Enum {
     readonly isUnderflow: boolean;
     readonly isOverflow: boolean;
@@ -1816,13 +1848,13 @@
     readonly type: 'Underflow' | 'Overflow' | 'DivisionByZero';
   }
 
-  /** @name PalletSudoError (207) */
+  /** @name PalletSudoError (218) */
   export interface PalletSudoError extends Enum {
     readonly isRequireSudo: boolean;
     readonly type: 'RequireSudo';
   }
 
-  /** @name FrameSystemAccountInfo (208) */
+  /** @name FrameSystemAccountInfo (219) */
   export interface FrameSystemAccountInfo extends Struct {
     readonly nonce: u32;
     readonly consumers: u32;
@@ -1831,19 +1863,19 @@
     readonly data: PalletBalancesAccountData;
   }
 
-  /** @name FrameSupportWeightsPerDispatchClassU64 (209) */
+  /** @name FrameSupportWeightsPerDispatchClassU64 (220) */
   export interface FrameSupportWeightsPerDispatchClassU64 extends Struct {
     readonly normal: u64;
     readonly operational: u64;
     readonly mandatory: u64;
   }
 
-  /** @name SpRuntimeDigest (210) */
+  /** @name SpRuntimeDigest (221) */
   export interface SpRuntimeDigest extends Struct {
     readonly logs: Vec<SpRuntimeDigestDigestItem>;
   }
 
-  /** @name SpRuntimeDigestDigestItem (212) */
+  /** @name SpRuntimeDigestDigestItem (223) */
   export interface SpRuntimeDigestDigestItem extends Enum {
     readonly isOther: boolean;
     readonly asOther: Bytes;
@@ -1857,14 +1889,14 @@
     readonly type: 'Other' | 'Consensus' | 'Seal' | 'PreRuntime' | 'RuntimeEnvironmentUpdated';
   }
 
-  /** @name FrameSystemEventRecord (214) */
+  /** @name FrameSystemEventRecord (225) */
   export interface FrameSystemEventRecord extends Struct {
     readonly phase: FrameSystemPhase;
     readonly event: Event;
     readonly topics: Vec<H256>;
   }
 
-  /** @name FrameSystemEvent (216) */
+  /** @name FrameSystemEvent (227) */
   export interface FrameSystemEvent extends Enum {
     readonly isExtrinsicSuccess: boolean;
     readonly asExtrinsicSuccess: {
@@ -1892,14 +1924,14 @@
     readonly type: 'ExtrinsicSuccess' | 'ExtrinsicFailed' | 'CodeUpdated' | 'NewAccount' | 'KilledAccount' | 'Remarked';
   }
 
-  /** @name FrameSupportWeightsDispatchInfo (217) */
+  /** @name FrameSupportWeightsDispatchInfo (228) */
   export interface FrameSupportWeightsDispatchInfo extends Struct {
     readonly weight: u64;
     readonly class: FrameSupportWeightsDispatchClass;
     readonly paysFee: FrameSupportWeightsPays;
   }
 
-  /** @name FrameSupportWeightsDispatchClass (218) */
+  /** @name FrameSupportWeightsDispatchClass (229) */
   export interface FrameSupportWeightsDispatchClass extends Enum {
     readonly isNormal: boolean;
     readonly isOperational: boolean;
@@ -1907,14 +1939,14 @@
     readonly type: 'Normal' | 'Operational' | 'Mandatory';
   }
 
-  /** @name FrameSupportWeightsPays (219) */
+  /** @name FrameSupportWeightsPays (230) */
   export interface FrameSupportWeightsPays extends Enum {
     readonly isYes: boolean;
     readonly isNo: boolean;
     readonly type: 'Yes' | 'No';
   }
 
-  /** @name OrmlVestingModuleEvent (220) */
+  /** @name OrmlVestingModuleEvent (231) */
   export interface OrmlVestingModuleEvent extends Enum {
     readonly isVestingScheduleAdded: boolean;
     readonly asVestingScheduleAdded: {
@@ -1934,7 +1966,7 @@
     readonly type: 'VestingScheduleAdded' | 'Claimed' | 'VestingSchedulesUpdated';
   }
 
-  /** @name CumulusPalletXcmpQueueEvent (221) */
+  /** @name CumulusPalletXcmpQueueEvent (232) */
   export interface CumulusPalletXcmpQueueEvent extends Enum {
     readonly isSuccess: boolean;
     readonly asSuccess: Option<H256>;
@@ -1955,7 +1987,7 @@
     readonly type: 'Success' | 'Fail' | 'BadVersion' | 'BadFormat' | 'UpwardMessageSent' | 'XcmpMessageSent' | 'OverweightEnqueued' | 'OverweightServiced';
   }
 
-  /** @name PalletXcmEvent (222) */
+  /** @name PalletXcmEvent (233) */
   export interface PalletXcmEvent extends Enum {
     readonly isAttempted: boolean;
     readonly asAttempted: XcmV2TraitsOutcome;
@@ -1992,7 +2024,7 @@
     readonly type: 'Attempted' | 'Sent' | 'UnexpectedResponse' | 'ResponseReady' | 'Notified' | 'NotifyOverweight' | 'NotifyDispatchError' | 'NotifyDecodeFailed' | 'InvalidResponder' | 'InvalidResponderVersion' | 'ResponseTaken' | 'AssetsTrapped' | 'VersionChangeNotified' | 'SupportedVersionChanged' | 'NotifyTargetSendFail' | 'NotifyTargetMigrationFail';
   }
 
-  /** @name XcmV2TraitsOutcome (223) */
+  /** @name XcmV2TraitsOutcome (234) */
   export interface XcmV2TraitsOutcome extends Enum {
     readonly isComplete: boolean;
     readonly asComplete: u64;
@@ -2003,7 +2035,7 @@
     readonly type: 'Complete' | 'Incomplete' | 'Error';
   }
 
-  /** @name CumulusPalletXcmEvent (225) */
+  /** @name CumulusPalletXcmEvent (236) */
   export interface CumulusPalletXcmEvent extends Enum {
     readonly isInvalidFormat: boolean;
     readonly asInvalidFormat: U8aFixed;
@@ -2014,7 +2046,7 @@
     readonly type: 'InvalidFormat' | 'UnsupportedVersion' | 'ExecutedDownward';
   }
 
-  /** @name CumulusPalletDmpQueueEvent (226) */
+  /** @name CumulusPalletDmpQueueEvent (237) */
   export interface CumulusPalletDmpQueueEvent extends Enum {
     readonly isInvalidFormat: boolean;
     readonly asInvalidFormat: U8aFixed;
@@ -2031,7 +2063,7 @@
     readonly type: 'InvalidFormat' | 'UnsupportedVersion' | 'ExecutedDownward' | 'WeightExhausted' | 'OverweightEnqueued' | 'OverweightServiced';
   }
 
-  /** @name PalletUniqueRawEvent (227) */
+  /** @name PalletUniqueRawEvent (238) */
   export interface PalletUniqueRawEvent extends Enum {
     readonly isCollectionSponsorRemoved: boolean;
     readonly asCollectionSponsorRemoved: u32;
@@ -2066,7 +2098,7 @@
     readonly type: 'CollectionSponsorRemoved' | 'CollectionAdminAdded' | 'CollectionOwnedChanged' | 'CollectionSponsorSet' | 'ConstOnChainSchemaSet' | 'SponsorshipConfirmed' | 'CollectionAdminRemoved' | 'AllowListAddressRemoved' | 'AllowListAddressAdded' | 'CollectionLimitSet' | 'MintPermissionSet' | 'OffchainSchemaSet' | 'PublicAccessModeSet' | 'SchemaVersionSet' | 'VariableOnChainSchemaSet';
   }
 
-  /** @name PalletCommonEvent (228) */
+  /** @name PalletCommonEvent (239) */
   export interface PalletCommonEvent extends Enum {
     readonly isCollectionCreated: boolean;
     readonly asCollectionCreated: ITuple<[u32, u8, AccountId32]>;
@@ -2083,7 +2115,7 @@
     readonly type: 'CollectionCreated' | 'CollectionDestroyed' | 'ItemCreated' | 'ItemDestroyed' | 'Transfer' | 'Approved';
   }
 
-  /** @name PalletEvmEvent (229) */
+  /** @name PalletEvmEvent (240) */
   export interface PalletEvmEvent extends Enum {
     readonly isLog: boolean;
     readonly asLog: EthereumLog;
@@ -2102,21 +2134,21 @@
     readonly type: 'Log' | 'Created' | 'CreatedFailed' | 'Executed' | 'ExecutedFailed' | 'BalanceDeposit' | 'BalanceWithdraw';
   }
 
-  /** @name EthereumLog (230) */
+  /** @name EthereumLog (241) */
   export interface EthereumLog extends Struct {
     readonly address: H160;
     readonly topics: Vec<H256>;
     readonly data: Bytes;
   }
 
-  /** @name PalletEthereumEvent (231) */
+  /** @name PalletEthereumEvent (242) */
   export interface PalletEthereumEvent extends Enum {
     readonly isExecuted: boolean;
     readonly asExecuted: ITuple<[H160, H160, H256, EvmCoreErrorExitReason]>;
     readonly type: 'Executed';
   }
 
-  /** @name EvmCoreErrorExitReason (232) */
+  /** @name EvmCoreErrorExitReason (243) */
   export interface EvmCoreErrorExitReason extends Enum {
     readonly isSucceed: boolean;
     readonly asSucceed: EvmCoreErrorExitSucceed;
@@ -2129,7 +2161,7 @@
     readonly type: 'Succeed' | 'Error' | 'Revert' | 'Fatal';
   }
 
-  /** @name EvmCoreErrorExitSucceed (233) */
+  /** @name EvmCoreErrorExitSucceed (244) */
   export interface EvmCoreErrorExitSucceed extends Enum {
     readonly isStopped: boolean;
     readonly isReturned: boolean;
@@ -2137,7 +2169,7 @@
     readonly type: 'Stopped' | 'Returned' | 'Suicided';
   }
 
-  /** @name EvmCoreErrorExitError (234) */
+  /** @name EvmCoreErrorExitError (245) */
   export interface EvmCoreErrorExitError extends Enum {
     readonly isStackUnderflow: boolean;
     readonly isStackOverflow: boolean;
@@ -2158,13 +2190,13 @@
     readonly type: 'StackUnderflow' | 'StackOverflow' | 'InvalidJump' | 'InvalidRange' | 'DesignatedInvalid' | 'CallTooDeep' | 'CreateCollision' | 'CreateContractLimit' | 'InvalidCode' | 'OutOfOffset' | 'OutOfGas' | 'OutOfFund' | 'PcUnderflow' | 'CreateEmpty' | 'Other';
   }
 
-  /** @name EvmCoreErrorExitRevert (237) */
+  /** @name EvmCoreErrorExitRevert (248) */
   export interface EvmCoreErrorExitRevert extends Enum {
     readonly isReverted: boolean;
     readonly type: 'Reverted';
   }
 
-  /** @name EvmCoreErrorExitFatal (238) */
+  /** @name EvmCoreErrorExitFatal (249) */
   export interface EvmCoreErrorExitFatal extends Enum {
     readonly isNotSupported: boolean;
     readonly isUnhandledInterrupt: boolean;
@@ -2175,7 +2207,7 @@
     readonly type: 'NotSupported' | 'UnhandledInterrupt' | 'CallErrorAsFatal' | 'Other';
   }
 
-  /** @name FrameSystemPhase (239) */
+  /** @name FrameSystemPhase (250) */
   export interface FrameSystemPhase extends Enum {
     readonly isApplyExtrinsic: boolean;
     readonly asApplyExtrinsic: u32;
@@ -2184,27 +2216,27 @@
     readonly type: 'ApplyExtrinsic' | 'Finalization' | 'Initialization';
   }
 
-  /** @name FrameSystemLastRuntimeUpgradeInfo (241) */
+  /** @name FrameSystemLastRuntimeUpgradeInfo (252) */
   export interface FrameSystemLastRuntimeUpgradeInfo extends Struct {
     readonly specVersion: Compact<u32>;
     readonly specName: Text;
   }
 
-  /** @name FrameSystemLimitsBlockWeights (242) */
+  /** @name FrameSystemLimitsBlockWeights (253) */
   export interface FrameSystemLimitsBlockWeights extends Struct {
     readonly baseBlock: u64;
     readonly maxBlock: u64;
     readonly perClass: FrameSupportWeightsPerDispatchClassWeightsPerClass;
   }
 
-  /** @name FrameSupportWeightsPerDispatchClassWeightsPerClass (243) */
+  /** @name FrameSupportWeightsPerDispatchClassWeightsPerClass (254) */
   export interface FrameSupportWeightsPerDispatchClassWeightsPerClass extends Struct {
     readonly normal: FrameSystemLimitsWeightsPerClass;
     readonly operational: FrameSystemLimitsWeightsPerClass;
     readonly mandatory: FrameSystemLimitsWeightsPerClass;
   }
 
-  /** @name FrameSystemLimitsWeightsPerClass (244) */
+  /** @name FrameSystemLimitsWeightsPerClass (255) */
   export interface FrameSystemLimitsWeightsPerClass extends Struct {
     readonly baseExtrinsic: u64;
     readonly maxExtrinsic: Option<u64>;
@@ -2212,25 +2244,25 @@
     readonly reserved: Option<u64>;
   }
 
-  /** @name FrameSystemLimitsBlockLength (246) */
+  /** @name FrameSystemLimitsBlockLength (257) */
   export interface FrameSystemLimitsBlockLength extends Struct {
     readonly max: FrameSupportWeightsPerDispatchClassU32;
   }
 
-  /** @name FrameSupportWeightsPerDispatchClassU32 (247) */
+  /** @name FrameSupportWeightsPerDispatchClassU32 (258) */
   export interface FrameSupportWeightsPerDispatchClassU32 extends Struct {
     readonly normal: u32;
     readonly operational: u32;
     readonly mandatory: u32;
   }
 
-  /** @name FrameSupportWeightsRuntimeDbWeight (248) */
+  /** @name FrameSupportWeightsRuntimeDbWeight (259) */
   export interface FrameSupportWeightsRuntimeDbWeight extends Struct {
     readonly read: u64;
     readonly write: u64;
   }
 
-  /** @name SpVersionRuntimeVersion (249) */
+  /** @name SpVersionRuntimeVersion (260) */
   export interface SpVersionRuntimeVersion extends Struct {
     readonly specName: Text;
     readonly implName: Text;
@@ -2242,7 +2274,7 @@
     readonly stateVersion: u8;
   }
 
-  /** @name FrameSystemError (253) */
+  /** @name FrameSystemError (264) */
   export interface FrameSystemError extends Enum {
     readonly isInvalidSpecName: boolean;
     readonly isSpecVersionNeedsToIncrease: boolean;
@@ -2253,7 +2285,7 @@
     readonly type: 'InvalidSpecName' | 'SpecVersionNeedsToIncrease' | 'FailedToExtractRuntimeVersion' | 'NonDefaultComposite' | 'NonZeroRefCount' | 'CallFiltered';
   }
 
-  /** @name OrmlVestingModuleError (255) */
+  /** @name OrmlVestingModuleError (266) */
   export interface OrmlVestingModuleError extends Enum {
     readonly isZeroVestingPeriod: boolean;
     readonly isZeroVestingPeriodCount: boolean;
@@ -2264,21 +2296,21 @@
     readonly type: 'ZeroVestingPeriod' | 'ZeroVestingPeriodCount' | 'InsufficientBalanceToLock' | 'TooManyVestingSchedules' | 'AmountLow' | 'MaxVestingSchedulesExceeded';
   }
 
-  /** @name CumulusPalletXcmpQueueInboundChannelDetails (257) */
+  /** @name CumulusPalletXcmpQueueInboundChannelDetails (268) */
   export interface CumulusPalletXcmpQueueInboundChannelDetails extends Struct {
     readonly sender: u32;
     readonly state: CumulusPalletXcmpQueueInboundState;
     readonly messageMetadata: Vec<ITuple<[u32, PolkadotParachainPrimitivesXcmpMessageFormat]>>;
   }
 
-  /** @name CumulusPalletXcmpQueueInboundState (258) */
+  /** @name CumulusPalletXcmpQueueInboundState (269) */
   export interface CumulusPalletXcmpQueueInboundState extends Enum {
     readonly isOk: boolean;
     readonly isSuspended: boolean;
     readonly type: 'Ok' | 'Suspended';
   }
 
-  /** @name PolkadotParachainPrimitivesXcmpMessageFormat (261) */
+  /** @name PolkadotParachainPrimitivesXcmpMessageFormat (272) */
   export interface PolkadotParachainPrimitivesXcmpMessageFormat extends Enum {
     readonly isConcatenatedVersionedXcm: boolean;
     readonly isConcatenatedEncodedBlob: boolean;
@@ -2286,7 +2318,7 @@
     readonly type: 'ConcatenatedVersionedXcm' | 'ConcatenatedEncodedBlob' | 'Signals';
   }
 
-  /** @name CumulusPalletXcmpQueueOutboundChannelDetails (264) */
+  /** @name CumulusPalletXcmpQueueOutboundChannelDetails (275) */
   export interface CumulusPalletXcmpQueueOutboundChannelDetails extends Struct {
     readonly recipient: u32;
     readonly state: CumulusPalletXcmpQueueOutboundState;
@@ -2295,14 +2327,14 @@
     readonly lastIndex: u16;
   }
 
-  /** @name CumulusPalletXcmpQueueOutboundState (265) */
+  /** @name CumulusPalletXcmpQueueOutboundState (276) */
   export interface CumulusPalletXcmpQueueOutboundState extends Enum {
     readonly isOk: boolean;
     readonly isSuspended: boolean;
     readonly type: 'Ok' | 'Suspended';
   }
 
-  /** @name CumulusPalletXcmpQueueQueueConfigData (267) */
+  /** @name CumulusPalletXcmpQueueQueueConfigData (278) */
   export interface CumulusPalletXcmpQueueQueueConfigData extends Struct {
     readonly suspendThreshold: u32;
     readonly dropThreshold: u32;
@@ -2312,7 +2344,7 @@
     readonly xcmpMaxIndividualWeight: u64;
   }
 
-  /** @name CumulusPalletXcmpQueueError (269) */
+  /** @name CumulusPalletXcmpQueueError (280) */
   export interface CumulusPalletXcmpQueueError extends Enum {
     readonly isFailedToSend: boolean;
     readonly isBadXcmOrigin: boolean;
@@ -2322,7 +2354,7 @@
     readonly type: 'FailedToSend' | 'BadXcmOrigin' | 'BadXcm' | 'BadOverweightIndex' | 'WeightOverLimit';
   }
 
-  /** @name PalletXcmError (270) */
+  /** @name PalletXcmError (281) */
   export interface PalletXcmError extends Enum {
     readonly isUnreachable: boolean;
     readonly isSendFailure: boolean;
@@ -2340,29 +2372,29 @@
     readonly type: 'Unreachable' | 'SendFailure' | 'Filtered' | 'UnweighableMessage' | 'DestinationNotInvertible' | 'Empty' | 'CannotReanchor' | 'TooManyAssets' | 'InvalidOrigin' | 'BadVersion' | 'BadLocation' | 'NoSubscription' | 'AlreadySubscribed';
   }
 
-  /** @name CumulusPalletXcmError (271) */
+  /** @name CumulusPalletXcmError (282) */
   export type CumulusPalletXcmError = Null;
 
-  /** @name CumulusPalletDmpQueueConfigData (272) */
+  /** @name CumulusPalletDmpQueueConfigData (283) */
   export interface CumulusPalletDmpQueueConfigData extends Struct {
     readonly maxIndividual: u64;
   }
 
-  /** @name CumulusPalletDmpQueuePageIndexData (273) */
+  /** @name CumulusPalletDmpQueuePageIndexData (284) */
   export interface CumulusPalletDmpQueuePageIndexData extends Struct {
     readonly beginUsed: u32;
     readonly endUsed: u32;
     readonly overweightCount: u64;
   }
 
-  /** @name CumulusPalletDmpQueueError (276) */
+  /** @name CumulusPalletDmpQueueError (287) */
   export interface CumulusPalletDmpQueueError extends Enum {
     readonly isUnknown: boolean;
     readonly isOverLimit: boolean;
     readonly type: 'Unknown' | 'OverLimit';
   }
 
-  /** @name PalletUniqueError (280) */
+  /** @name PalletUniqueError (291) */
   export interface PalletUniqueError extends Enum {
     readonly isCollectionDecimalPointLimitExceeded: boolean;
     readonly isConfirmUnsetSponsorFail: boolean;
@@ -2370,7 +2402,7 @@
     readonly type: 'CollectionDecimalPointLimitExceeded' | 'ConfirmUnsetSponsorFail' | 'EmptyArgument';
   }
 
-  /** @name UpDataStructsCollection (281) */
+  /** @name UpDataStructsCollection (292) */
   export interface UpDataStructsCollection extends Struct {
     readonly owner: AccountId32;
     readonly mode: UpDataStructsCollectionMode;
@@ -2388,7 +2420,7 @@
     readonly metaUpdatePermission: UpDataStructsMetaUpdatePermission;
   }
 
-  /** @name UpDataStructsSponsorshipState (282) */
+  /** @name UpDataStructsSponsorshipState (293) */
   export interface UpDataStructsSponsorshipState extends Enum {
     readonly isDisabled: boolean;
     readonly isUnconfirmed: boolean;
@@ -2398,14 +2430,14 @@
     readonly type: 'Disabled' | 'Unconfirmed' | 'Confirmed';
   }
 
-  /** @name UpDataStructsCollectionStats (285) */
+  /** @name UpDataStructsCollectionStats (296) */
   export interface UpDataStructsCollectionStats extends Struct {
     readonly created: u32;
     readonly destroyed: u32;
     readonly alive: u32;
   }
 
-  /** @name PalletCommonError (286) */
+  /** @name PalletCommonError (297) */
   export interface PalletCommonError extends Enum {
     readonly isCollectionNotFound: boolean;
     readonly isMustBeTokenOwner: boolean;
@@ -2433,7 +2465,7 @@
     readonly type: 'CollectionNotFound' | 'MustBeTokenOwner' | 'NoPermission' | 'PublicMintingNotAllowed' | 'AddressNotInAllowlist' | 'CollectionNameLimitExceeded' | 'CollectionDescriptionLimitExceeded' | 'CollectionTokenPrefixLimitExceeded' | 'TotalCollectionsLimitExceeded' | 'TokenVariableDataLimitExceeded' | 'CollectionAdminCountExceeded' | 'CollectionLimitBoundsExceeded' | 'OwnerPermissionsCantBeReverted' | 'TransferNotAllowed' | 'AccountTokenLimitExceeded' | 'CollectionTokenLimitExceeded' | 'MetadataFlagFrozen' | 'TokenNotFound' | 'TokenValueTooLow' | 'ApprovedValueTooLow' | 'CantApproveMoreThanOwned' | 'AddressIsZero' | 'UnsupportedOperation';
   }
 
-  /** @name PalletFungibleError (288) */
+  /** @name PalletFungibleError (299) */
   export interface PalletFungibleError extends Enum {
     readonly isNotFungibleDataUsedToMintFungibleCollectionToken: boolean;
     readonly isFungibleItemsHaveNoId: boolean;
@@ -2441,34 +2473,34 @@
     readonly type: 'NotFungibleDataUsedToMintFungibleCollectionToken' | 'FungibleItemsHaveNoId' | 'FungibleItemsDontHaveData';
   }
 
-  /** @name PalletRefungibleItemData (289) */
+  /** @name PalletRefungibleItemData (300) */
   export interface PalletRefungibleItemData extends Struct {
     readonly constData: Bytes;
     readonly variableData: Bytes;
   }
 
-  /** @name PalletRefungibleError (293) */
+  /** @name PalletRefungibleError (304) */
   export interface PalletRefungibleError extends Enum {
     readonly isNotRefungibleDataUsedToMintFungibleCollectionToken: boolean;
     readonly isWrongRefungiblePieces: boolean;
     readonly type: 'NotRefungibleDataUsedToMintFungibleCollectionToken' | 'WrongRefungiblePieces';
   }
 
-  /** @name PalletNonfungibleItemData (294) */
+  /** @name PalletNonfungibleItemData (305) */
   export interface PalletNonfungibleItemData extends Struct {
     readonly constData: Bytes;
     readonly variableData: Bytes;
     readonly owner: PalletCommonAccountBasicCrossAccountIdRepr;
   }
 
-  /** @name PalletNonfungibleError (295) */
+  /** @name PalletNonfungibleError (306) */
   export interface PalletNonfungibleError extends Enum {
     readonly isNotNonfungibleDataUsedToMintFungibleCollectionToken: boolean;
     readonly isNonfungibleItemsHaveNoAmount: boolean;
     readonly type: 'NotNonfungibleDataUsedToMintFungibleCollectionToken' | 'NonfungibleItemsHaveNoAmount';
   }
 
-  /** @name PalletEvmError (297) */
+  /** @name PalletEvmError (308) */
   export interface PalletEvmError extends Enum {
     readonly isBalanceLow: boolean;
     readonly isFeeOverflow: boolean;
@@ -2479,7 +2511,7 @@
     readonly type: 'BalanceLow' | 'FeeOverflow' | 'PaymentOverflow' | 'WithdrawFailed' | 'GasPriceTooLow' | 'InvalidNonce';
   }
 
-  /** @name FpRpcTransactionStatus (300) */
+  /** @name FpRpcTransactionStatus (311) */
   export interface FpRpcTransactionStatus extends Struct {
     readonly transactionHash: H256;
     readonly transactionIndex: u32;
@@ -2490,10 +2522,10 @@
     readonly logsBloom: EthbloomBloom;
   }
 
-  /** @name EthbloomBloom (303) */
+  /** @name EthbloomBloom (314) */
   export interface EthbloomBloom extends U8aFixed {}
 
-  /** @name EthereumReceiptReceiptV3 (305) */
+  /** @name EthereumReceiptReceiptV3 (316) */
   export interface EthereumReceiptReceiptV3 extends Enum {
     readonly isLegacy: boolean;
     readonly asLegacy: EthereumReceiptEip658ReceiptData;
@@ -2504,7 +2536,7 @@
     readonly type: 'Legacy' | 'Eip2930' | 'Eip1559';
   }
 
-  /** @name EthereumReceiptEip658ReceiptData (306) */
+  /** @name EthereumReceiptEip658ReceiptData (317) */
   export interface EthereumReceiptEip658ReceiptData extends Struct {
     readonly statusCode: u8;
     readonly usedGas: U256;
@@ -2512,14 +2544,14 @@
     readonly logs: Vec<EthereumLog>;
   }
 
-  /** @name EthereumBlock (307) */
+  /** @name EthereumBlock (318) */
   export interface EthereumBlock extends Struct {
     readonly header: EthereumHeader;
     readonly transactions: Vec<EthereumTransactionTransactionV2>;
     readonly ommers: Vec<EthereumHeader>;
   }
 
-  /** @name EthereumHeader (308) */
+  /** @name EthereumHeader (319) */
   export interface EthereumHeader extends Struct {
     readonly parentHash: H256;
     readonly ommersHash: H256;
@@ -2538,24 +2570,24 @@
     readonly nonce: EthereumTypesHashH64;
   }
 
-  /** @name EthereumTypesHashH64 (309) */
+  /** @name EthereumTypesHashH64 (320) */
   export interface EthereumTypesHashH64 extends U8aFixed {}
 
-  /** @name PalletEthereumError (314) */
+  /** @name PalletEthereumError (325) */
   export interface PalletEthereumError extends Enum {
     readonly isInvalidSignature: boolean;
     readonly isPreLogExists: boolean;
     readonly type: 'InvalidSignature' | 'PreLogExists';
   }
 
-  /** @name PalletEvmCoderSubstrateError (315) */
+  /** @name PalletEvmCoderSubstrateError (326) */
   export interface PalletEvmCoderSubstrateError extends Enum {
     readonly isOutOfGas: boolean;
     readonly isOutOfFund: boolean;
     readonly type: 'OutOfGas' | 'OutOfFund';
   }
 
-  /** @name PalletEvmContractHelpersSponsoringModeT (316) */
+  /** @name PalletEvmContractHelpersSponsoringModeT (327) */
   export interface PalletEvmContractHelpersSponsoringModeT extends Enum {
     readonly isDisabled: boolean;
     readonly isAllowlisted: boolean;
@@ -2563,20 +2595,20 @@
     readonly type: 'Disabled' | 'Allowlisted' | 'Generous';
   }
 
-  /** @name PalletEvmContractHelpersError (318) */
+  /** @name PalletEvmContractHelpersError (329) */
   export interface PalletEvmContractHelpersError extends Enum {
     readonly isNoPermission: boolean;
     readonly type: 'NoPermission';
   }
 
-  /** @name PalletEvmMigrationError (319) */
+  /** @name PalletEvmMigrationError (330) */
   export interface PalletEvmMigrationError extends Enum {
     readonly isAccountNotEmpty: boolean;
     readonly isAccountIsNotMigrating: boolean;
     readonly type: 'AccountNotEmpty' | 'AccountIsNotMigrating';
   }
 
-  /** @name SpRuntimeMultiSignature (321) */
+  /** @name SpRuntimeMultiSignature (332) */
   export interface SpRuntimeMultiSignature extends Enum {
     readonly isEd25519: boolean;
     readonly asEd25519: SpCoreEd25519Signature;
@@ -2587,31 +2619,31 @@
     readonly type: 'Ed25519' | 'Sr25519' | 'Ecdsa';
   }
 
-  /** @name SpCoreEd25519Signature (322) */
+  /** @name SpCoreEd25519Signature (333) */
   export interface SpCoreEd25519Signature extends U8aFixed {}
 
-  /** @name SpCoreSr25519Signature (324) */
+  /** @name SpCoreSr25519Signature (335) */
   export interface SpCoreSr25519Signature extends U8aFixed {}
 
-  /** @name SpCoreEcdsaSignature (325) */
+  /** @name SpCoreEcdsaSignature (336) */
   export interface SpCoreEcdsaSignature extends U8aFixed {}
 
-  /** @name FrameSystemExtensionsCheckSpecVersion (328) */
+  /** @name FrameSystemExtensionsCheckSpecVersion (339) */
   export type FrameSystemExtensionsCheckSpecVersion = Null;
 
-  /** @name FrameSystemExtensionsCheckGenesis (329) */
+  /** @name FrameSystemExtensionsCheckGenesis (340) */
   export type FrameSystemExtensionsCheckGenesis = Null;
 
-  /** @name FrameSystemExtensionsCheckNonce (332) */
+  /** @name FrameSystemExtensionsCheckNonce (343) */
   export interface FrameSystemExtensionsCheckNonce extends Compact<u32> {}
 
-  /** @name FrameSystemExtensionsCheckWeight (333) */
+  /** @name FrameSystemExtensionsCheckWeight (344) */
   export type FrameSystemExtensionsCheckWeight = Null;
 
-  /** @name PalletTemplateTransactionPaymentChargeTransactionPayment (334) */
+  /** @name PalletTemplateTransactionPaymentChargeTransactionPayment (345) */
   export interface PalletTemplateTransactionPaymentChargeTransactionPayment extends Compact<u128> {}
 
-  /** @name UniqueRuntimeRuntime (335) */
+  /** @name UniqueRuntimeRuntime (346) */
   export type UniqueRuntimeRuntime = Null;
 
 } // declare module
modifiedtests/src/interfaces/unique/types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/unique/types.ts
+++ b/tests/src/interfaces/unique/types.ts
@@ -1316,6 +1316,11 @@
     readonly owner: PalletCommonAccountBasicCrossAccountIdRepr;
     readonly itemsData: Vec<UpDataStructsCreateItemData>;
   } & Struct;
+  readonly isCreateMultipleItemsEx: boolean;
+  readonly asCreateMultipleItemsEx: {
+    readonly collectionId: u32;
+    readonly data: UpDataStructsCreateItemExData;
+  } & Struct;
   readonly isSetTransfersEnabledFlag: boolean;
   readonly asSetTransfersEnabledFlag: {
     readonly collectionId: u32;
@@ -1392,7 +1397,7 @@
     readonly collectionId: u32;
     readonly newLimit: UpDataStructsCollectionLimits;
   } & Struct;
-  readonly type: 'CreateCollection' | 'CreateCollectionEx' | 'DestroyCollection' | 'AddToAllowList' | 'RemoveFromAllowList' | 'SetPublicAccessMode' | 'SetMintPermission' | 'ChangeCollectionOwner' | 'AddCollectionAdmin' | 'RemoveCollectionAdmin' | 'SetCollectionSponsor' | 'ConfirmSponsorship' | 'RemoveCollectionSponsor' | 'CreateItem' | 'CreateMultipleItems' | 'SetTransfersEnabledFlag' | 'BurnItem' | 'BurnFrom' | 'Transfer' | 'Approve' | 'TransferFrom' | 'SetVariableMetaData' | 'SetMetaUpdatePermissionFlag' | 'SetSchemaVersion' | 'SetOffchainSchema' | 'SetConstOnChainSchema' | 'SetVariableOnChainSchema' | 'SetCollectionLimits';
+  readonly type: 'CreateCollection' | 'CreateCollectionEx' | 'DestroyCollection' | 'AddToAllowList' | 'RemoveFromAllowList' | 'SetPublicAccessMode' | 'SetMintPermission' | 'ChangeCollectionOwner' | 'AddCollectionAdmin' | 'RemoveCollectionAdmin' | 'SetCollectionSponsor' | 'ConfirmSponsorship' | 'RemoveCollectionSponsor' | 'CreateItem' | 'CreateMultipleItems' | 'CreateMultipleItemsEx' | 'SetTransfersEnabledFlag' | 'BurnItem' | 'BurnFrom' | 'Transfer' | 'Approve' | 'TransferFrom' | 'SetVariableMetaData' | 'SetMetaUpdatePermissionFlag' | 'SetSchemaVersion' | 'SetOffchainSchema' | 'SetConstOnChainSchema' | 'SetVariableOnChainSchema' | 'SetCollectionLimits';
 }
 
 /** @name PalletUniqueError */
@@ -1806,12 +1811,32 @@
   readonly type: 'Nft' | 'Fungible' | 'ReFungible';
 }
 
+/** @name UpDataStructsCreateItemExData */
+export interface UpDataStructsCreateItemExData extends Enum {
+  readonly isNft: boolean;
+  readonly asNft: Vec<UpDataStructsCreateNftExData>;
+  readonly isFungible: boolean;
+  readonly asFungible: BTreeMap<PalletCommonAccountBasicCrossAccountIdRepr,u128>;
+  readonly isRefungibleMultipleItems: boolean;
+  readonly asRefungibleMultipleItems: Vec<UpDataStructsCreateRefungibleExData>;
+  readonly isRefungibleMultipleOwners: boolean;
+  readonly asRefungibleMultipleOwners: UpDataStructsCreateRefungibleExData;
+  readonly type: 'Nft' | 'Fungible' | 'RefungibleMultipleItems' | 'RefungibleMultipleOwners';
+}
+
 /** @name UpDataStructsCreateNftData */
 export interface UpDataStructsCreateNftData extends Struct {
   readonly constData: Bytes;
   readonly variableData: Bytes;
 }
 
+/** @name UpDataStructsCreateNftExData */
+export interface UpDataStructsCreateNftExData extends Struct {
+  readonly constData: Bytes;
+  readonly variableData: Bytes;
+  readonly owner: PalletCommonAccountBasicCrossAccountIdRepr;
+}
+
 /** @name UpDataStructsCreateReFungibleData */
 export interface UpDataStructsCreateReFungibleData extends Struct {
   readonly constData: Bytes;
@@ -1819,6 +1844,13 @@
   readonly pieces: u128;
 }
 
+/** @name UpDataStructsCreateRefungibleExData */
+export interface UpDataStructsCreateRefungibleExData extends Struct {
+  readonly constData: Bytes;
+  readonly variableData: Bytes;
+  readonly users: BTreeMap<PalletCommonAccountBasicCrossAccountIdRepr, u128>;
+}
+
 /** @name UpDataStructsMetaUpdatePermission */
 export interface UpDataStructsMetaUpdatePermission extends Enum {
   readonly isItemOwner: boolean;