git.delta.rocks / unique-network / refs/commits / afa26fc3182d

difftreelog

refac: rename substrate methods to cross

Trubnikov Sergey2022-10-24parent: #7f98069.patch.diff
in: master

27 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1077,13 +1077,13 @@
 
 [[package]]
 name = "console"
-version = "0.15.1"
+version = "0.15.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847"
+checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c"
 dependencies = [
  "encode_unicode",
+ "lazy_static",
  "libc",
- "once_cell",
  "terminal_size",
  "winapi",
 ]
@@ -1871,9 +1871,9 @@
 
 [[package]]
 name = "cxx"
-version = "1.0.79"
+version = "1.0.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f83d0ebf42c6eafb8d7c52f7e5f2d3003b89c7aa4fd2b79229209459a849af8"
+checksum = "19f39818dcfc97d45b03953c1292efc4e80954e1583c4aa770bac1383e2310a4"
 dependencies = [
  "cc",
  "cxxbridge-flags",
@@ -1883,9 +1883,9 @@
 
 [[package]]
 name = "cxx-build"
-version = "1.0.79"
+version = "1.0.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07d050484b55975889284352b0ffc2ecbda25c0c55978017c132b29ba0818a86"
+checksum = "3e580d70777c116df50c390d1211993f62d40302881e54d4b79727acb83d0199"
 dependencies = [
  "cc",
  "codespan-reporting",
@@ -1898,15 +1898,15 @@
 
 [[package]]
 name = "cxxbridge-flags"
-version = "1.0.79"
+version = "1.0.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99d2199b00553eda8012dfec8d3b1c75fce747cf27c169a270b3b99e3448ab78"
+checksum = "56a46460b88d1cec95112c8c363f0e2c39afdb237f60583b0b36343bf627ea9c"
 
 [[package]]
 name = "cxxbridge-macro"
-version = "1.0.79"
+version = "1.0.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcb67a6de1f602736dd7eaead0080cf3435df806c61b24b13328db128c58868f"
+checksum = "747b608fecf06b0d72d440f27acc99288207324b793be2c17991839f3d4995ea"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3561,9 +3561,9 @@
 
 [[package]]
 name = "iana-time-zone-haiku"
-version = "0.1.1"
+version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
+checksum = "fde6edd6cef363e9359ed3c98ba64590ba9eecba2293eb5a723ab32aee8926aa"
 dependencies = [
  "cxx",
  "cxx-build",
@@ -10774,9 +10774,9 @@
 
 [[package]]
 name = "serde_json"
-version = "1.0.87"
+version = "1.0.86"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
+checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074"
 dependencies = [
  "itoa",
  "ryu",
@@ -12055,9 +12055,9 @@
 
 [[package]]
 name = "syn"
-version = "1.0.103"
+version = "1.0.102"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
+checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1"
 dependencies = [
  "proc-macro2",
  "quote",
modifiedpallets/common/src/erc.rsdiffbeforeafterboth
--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -34,8 +34,8 @@
 use crate::{
 	Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf,
 	eth::{
-		convert_cross_account_to_uint256, convert_uint256_to_cross_account,
-		convert_cross_account_to_tuple,
+		convert_cross_account_to_uint256, convert_cross_account_to_tuple,
+		convert_tuple_to_cross_account,
 	},
 	weights::WeightInfo,
 };
@@ -139,26 +139,25 @@
 		save(self)
 	}
 
-	// TODO: Temprorary off. Need refactor
-	// /// Set the substrate sponsor of the collection.
-	// ///
-	// /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
-	// ///
-	// /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract.
-	// fn set_collection_sponsor_substrate(
-	// 	&mut self,
-	// 	caller: caller,
-	// 	sponsor: uint256,
-	// ) -> Result<void> {
-	// 	self.consume_store_reads_and_writes(1, 1)?;
+	/// Set the sponsor of the collection.
+	///
+	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+	///
+	/// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.
+	fn set_collection_sponsor_cross(
+		&mut self,
+		caller: caller,
+		sponsor: (address, uint256),
+	) -> Result<void> {
+		self.consume_store_reads_and_writes(1, 1)?;
 
-	// 	check_is_owner_or_admin(caller, self)?;
+		check_is_owner_or_admin(caller, self)?;
 
-	// 	let sponsor = convert_uint256_to_cross_account::<T>(sponsor);
-	// 	self.set_sponsor(sponsor.as_sub().clone())
-	// 		.map_err(dispatch_to_evm::<T>)?;
-	// 	save(self)
-	// }
+		let sponsor = convert_tuple_to_cross_account::<T>(sponsor)?;
+		self.set_sponsor(sponsor.as_sub().clone())
+			.map_err(dispatch_to_evm::<T>)?;
+		save(self)
+	}
 
 	/// Whether there is a pending sponsor.
 	fn has_collection_pending_sponsor(&self) -> Result<bool> {
@@ -300,37 +299,35 @@
 		Ok(crate::eth::collection_id_to_address(self.id))
 	}
 
-	// TODO: Temprorary off. Need refactor
-	// /// Add collection admin by substrate address.
-	// /// @param newAdmin Substrate administrator address.
-	// fn add_collection_admin_substrate(
-	// 	&mut self,
-	// 	caller: caller,
-	// 	new_admin: uint256,
-	// ) -> Result<void> {
-	// 	self.consume_store_writes(2)?;
+	/// Add collection admin.
+	/// @param newAdmin Cross account administrator address.
+	fn add_collection_admin_cross(
+		&mut self,
+		caller: caller,
+		new_admin: (address, uint256),
+	) -> Result<void> {
+		self.consume_store_writes(2)?;
 
-	// 	let caller = T::CrossAccountId::from_eth(caller);
-	// 	let new_admin = convert_uint256_to_cross_account::<T>(new_admin);
-	// 	<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;
-	// 	Ok(())
-	// }
+		let caller = T::CrossAccountId::from_eth(caller);
+		let new_admin = convert_tuple_to_cross_account::<T>(new_admin)?;
+		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
 
-	// TODO: Temprorary off. Need refactor
-	// /// Remove collection admin by substrate address.
-	// /// @param admin Substrate administrator address.
-	// fn remove_collection_admin_substrate(
-	// 	&mut self,
-	// 	caller: caller,
-	// 	admin: uint256,
-	// ) -> Result<void> {
-	// 	self.consume_store_writes(2)?;
+	/// Remove collection admin.
+	/// @param admin Cross account administrator address.
+	fn remove_collection_admin_cross(
+		&mut self,
+		caller: caller,
+		admin: (address, uint256),
+	) -> Result<void> {
+		self.consume_store_writes(2)?;
 
-	// 	let caller = T::CrossAccountId::from_eth(caller);
-	// 	let admin = convert_uint256_to_cross_account::<T>(admin);
-	// 	<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;
-	// 	Ok(())
-	// }
+		let caller = T::CrossAccountId::from_eth(caller);
+		let admin = convert_tuple_to_cross_account::<T>(admin)?;
+		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
 
 	/// Add collection admin.
 	/// @param newAdmin Address of the added administrator.
@@ -479,22 +476,21 @@
 		Ok(())
 	}
 
-	// TODO: Temprorary off. Need refactor
-	// /// Add substrate user to allowed list.
-	// ///
-	// /// @param user User substrate address.
-	// fn add_to_collection_allow_list_substrate(
-	// 	&mut self,
-	// 	caller: caller,
-	// 	user: uint256,
-	// ) -> Result<void> {
-	// 	self.consume_store_writes(1)?;
+	/// Add user to allowed list.
+	///
+	/// @param user User cross account address.
+	fn add_to_collection_allow_list_cross(
+		&mut self,
+		caller: caller,
+		user: (address, uint256),
+	) -> Result<void> {
+		self.consume_store_writes(1)?;
 
-	// 	let caller = T::CrossAccountId::from_eth(caller);
-	// 	let user = convert_uint256_to_cross_account::<T>(user);
-	// 	Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;
-	// 	Ok(())
-	// }
+		let caller = T::CrossAccountId::from_eth(caller);
+		let user = convert_tuple_to_cross_account::<T>(user)?;
+		Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
 
 	/// Remove the user from the allowed list.
 	///
@@ -508,22 +504,21 @@
 		Ok(())
 	}
 
-	// TODO: Temprorary off. Need refactor
-	// /// Remove substrate user from allowed list.
-	// ///
-	// /// @param user User substrate address.
-	// fn remove_from_collection_allow_list_substrate(
-	// 	&mut self,
-	// 	caller: caller,
-	// 	user: uint256,
-	// ) -> Result<void> {
-	// 	self.consume_store_writes(1)?;
+	/// Remove user from allowed list.
+	///
+	/// @param user User cross account address.
+	fn remove_from_collection_allow_list_cross(
+		&mut self,
+		caller: caller,
+		user: (address, uint256),
+	) -> Result<void> {
+		self.consume_store_writes(1)?;
 
-	// 	let caller = T::CrossAccountId::from_eth(caller);
-	// 	let user = convert_uint256_to_cross_account::<T>(user);
-	// 	Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;
-	// 	Ok(())
-	// }
+		let caller = T::CrossAccountId::from_eth(caller);
+		let user = convert_tuple_to_cross_account::<T>(user)?;
+		Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
 
 	/// Switch permission for minting.
 	///
@@ -556,15 +551,14 @@
 		Ok(self.is_owner_or_admin(&user))
 	}
 
-	// TODO: Temprorary off. Need refactor
-	// /// Check that substrate account is the owner or admin of the collection
-	// ///
-	// /// @param user account to verify
-	// /// @return "true" if account is the owner or admin
-	// fn is_owner_or_admin_substrate(&self, user: uint256) -> Result<bool> {
-	// 	let user = convert_uint256_to_cross_account::<T>(user);
-	// 	Ok(self.is_owner_or_admin(&user))
-	// }
+	/// Check that account is the owner or admin of the collection
+	///
+	/// @param user User cross account to verify
+	/// @return "true" if account is the owner or admin
+	fn is_owner_or_admin_cross(&self, user: (address, uint256)) -> Result<bool> {
+		let user = convert_tuple_to_cross_account::<T>(user)?;
+		Ok(self.is_owner_or_admin(&user))
+	}
 
 	/// Returns collection type
 	///
@@ -580,7 +574,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuple with sponsor address and his substrate mirror.
+	/// @return Tuble with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	fn collection_owner(&self) -> Result<(address, uint256)> {
 		Ok(convert_cross_account_to_tuple::<T>(
@@ -602,20 +596,6 @@
 			.map_err(dispatch_to_evm::<T>)
 	}
 
-	// TODO: Temprorary off. Need refactor
-	// /// Changes collection owner to another substrate account
-	// ///
-	// /// @dev Owner can be changed only by current owner
-	// /// @param newOwner new owner substrate account
-	// fn set_owner_substrate(&mut self, caller: caller, new_owner: uint256) -> Result<void> {
-	// 	self.consume_store_writes(1)?;
-
-	// 	let caller = T::CrossAccountId::from_eth(caller);
-	// 	let new_owner = convert_uint256_to_cross_account::<T>(new_owner);
-	// 	self.set_owner_internal(caller, new_owner)
-	// 		.map_err(dispatch_to_evm::<T>)
-	// }
-
 	/// Get collection administrators
 	///
 	/// @return Vector of tuples with admins address and his substrate mirror.
@@ -626,6 +606,19 @@
 			.collect();
 		Ok(result)
 	}
+
+	/// Changes collection owner to another account
+	///
+	/// @dev Owner can be changed only by current owner
+	/// @param newOwner new owner cross account
+	fn set_owner_cross(&mut self, caller: caller, new_owner: (address, uint256)) -> Result<void> {
+		self.consume_store_writes(1)?;
+
+		let caller = T::CrossAccountId::from_eth(caller);
+		let new_owner = convert_tuple_to_cross_account::<T>(new_owner)?;
+		self.set_owner_internal(caller, new_owner)
+			.map_err(dispatch_to_evm::<T>)
+	}
 }
 
 /// ### Note
modifiedpallets/common/src/eth.rsdiffbeforeafterboth
--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -85,3 +85,27 @@
 		(eth, Default::default())
 	}
 }
+
+/// Convert tuple `(address, uint256)` to `CrossAccountId`.
+///
+/// If `address` in the tuple has *default* value, then the canonical form is substrate,
+/// if `uint256` has *default* value, then the ethereum form is canonical,
+/// if both values are *default* or *non default*, then this is considered an invalid address and `Error` is returned.
+pub fn convert_tuple_to_cross_account<T: Config>(
+	eth_cross_account_id: (address, uint256),
+) -> evm_coder::execution::Result<T::CrossAccountId>
+where
+	T::AccountId: From<[u8; 32]>,
+{
+	if eth_cross_account_id == Default::default() {
+		Err("All fields of cross account is zeroed".into())
+	} else if eth_cross_account_id.0 == Default::default() {
+		Ok(convert_uint256_to_cross_account::<T>(
+			eth_cross_account_id.1,
+		))
+	} else if eth_cross_account_id.1 == Default::default() {
+		Ok(T::CrossAccountId::from_eth(eth_cross_account_id.0))
+	} else {
+		Err("All fields of cross account is non zeroed".into())
+	}
+}
modifiedpallets/fungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -19,6 +19,7 @@
 use core::char::{REPLACEMENT_CHARACTER, decode_utf16};
 use core::convert::TryInto;
 use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight};
+use pallet_common::eth::convert_tuple_to_cross_account;
 use up_data_structs::CollectionMode;
 use pallet_common::erc::{CommonEvmHandler, PrecompileResult};
 use sp_std::vec::Vec;
@@ -149,7 +150,26 @@
 }
 
 #[solidity_interface(name = ERC20UniqueExtensions)]
-impl<T: Config> FungibleHandle<T> {
+impl<T: Config> FungibleHandle<T>
+where
+	T::AccountId: From<[u8; 32]>,
+{
+	#[weight(<SelfWeightOf<T>>::approve())]
+	fn approve_cross(
+		&mut self,
+		caller: caller,
+		spender: (address, uint256),
+		amount: uint256,
+	) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let spender = convert_tuple_to_cross_account::<T>(spender)?;
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+
+		<Pallet<T>>::set_allowance(self, &caller, &spender, amount)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
+
 	/// Burn tokens from account
 	/// @dev Function that burns an `amount` of the tokens of a given account,
 	/// deducting from the sender's allowance for said account.
@@ -169,6 +189,30 @@
 		Ok(true)
 	}
 
+	/// Burn tokens from account
+	/// @dev Function that burns an `amount` of the tokens of a given account,
+	/// deducting from the sender's allowance for said account.
+	/// @param from The account whose tokens will be burnt.
+	/// @param amount The amount that will be burnt.
+	#[weight(<SelfWeightOf<T>>::burn_from())]
+	fn burn_from_cross(
+		&mut self,
+		caller: caller,
+		from: (address, uint256),
+		amount: uint256,
+	) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let from = convert_tuple_to_cross_account::<T>(from)?;
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		<Pallet<T>>::burn_from(self, &caller, &from, amount, &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
+
 	/// Mint tokens for multiple accounts.
 	/// @param amounts array of pairs of account address and amount
 	#[weight(<SelfWeightOf<T>>::create_multiple_items_ex(amounts.len() as u32))]
@@ -191,6 +235,27 @@
 			.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
+
+	#[weight(<SelfWeightOf<T>>::transfer_from())]
+	fn transfer_from_cross(
+		&mut self,
+		caller: caller,
+		from: (address, uint256),
+		to: (address, uint256),
+		amount: uint256,
+	) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let from = convert_tuple_to_cross_account::<T>(from)?;
+		let to = convert_tuple_to_cross_account::<T>(to)?;
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		<Pallet<T>>::transfer_from(self, &caller, &from, &to, amount, &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
 }
 
 #[solidity_interface(
modifiedpallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth
--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -18,7 +18,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x3af103fb
+/// @dev the ERC-165 identifier for this interface is 0x25d897dc
 contract Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -72,6 +72,19 @@
 		dummy = 0;
 	}
 
+	/// Set the sponsor of the collection.
+	///
+	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+	///
+	/// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.
+	/// @dev EVM selector for this function is: 0x84a1d5a8,
+	///  or in textual repr: setCollectionSponsorCross((address,uint256))
+	function setCollectionSponsorCross(Tuple6 memory sponsor) public {
+		require(false, stub_error);
+		sponsor;
+		dummy = 0;
+	}
+
 	/// Whether there is a pending sponsor.
 	/// @dev EVM selector for this function is: 0x058ac185,
 	///  or in textual repr: hasCollectionPendingSponsor()
@@ -155,6 +168,26 @@
 	}
 
 	/// Add collection admin.
+	/// @param newAdmin Cross account administrator address.
+	/// @dev EVM selector for this function is: 0x859aa7d6,
+	///  or in textual repr: addCollectionAdminCross((address,uint256))
+	function addCollectionAdminCross(Tuple6 memory newAdmin) public {
+		require(false, stub_error);
+		newAdmin;
+		dummy = 0;
+	}
+
+	/// Remove collection admin.
+	/// @param admin Cross account administrator address.
+	/// @dev EVM selector for this function is: 0x6c0cd173,
+	///  or in textual repr: removeCollectionAdminCross((address,uint256))
+	function removeCollectionAdminCross(Tuple6 memory admin) public {
+		require(false, stub_error);
+		admin;
+		dummy = 0;
+	}
+
+	/// Add collection admin.
 	/// @param newAdmin Address of the added administrator.
 	/// @dev EVM selector for this function is: 0x92e462c7,
 	///  or in textual repr: addCollectionAdmin(address)
@@ -234,6 +267,17 @@
 		dummy = 0;
 	}
 
+	/// Add user to allowed list.
+	///
+	/// @param user User cross account address.
+	/// @dev EVM selector for this function is: 0xa0184a3a,
+	///  or in textual repr: addToCollectionAllowListCross((address,uint256))
+	function addToCollectionAllowListCross(Tuple6 memory user) public {
+		require(false, stub_error);
+		user;
+		dummy = 0;
+	}
+
 	/// Remove the user from the allowed list.
 	///
 	/// @param user Address of a removed user.
@@ -245,6 +289,17 @@
 		dummy = 0;
 	}
 
+	/// Remove user from allowed list.
+	///
+	/// @param user User cross account address.
+	/// @dev EVM selector for this function is: 0x09ba452a,
+	///  or in textual repr: removeFromCollectionAllowListCross((address,uint256))
+	function removeFromCollectionAllowListCross(Tuple6 memory user) public {
+		require(false, stub_error);
+		user;
+		dummy = 0;
+	}
+
 	/// Switch permission for minting.
 	///
 	/// @param mode Enable if "true".
@@ -269,6 +324,19 @@
 		return false;
 	}
 
+	/// Check that account is the owner or admin of the collection
+	///
+	/// @param user User cross account to verify
+	/// @return "true" if account is the owner or admin
+	/// @dev EVM selector for this function is: 0x3e75a905,
+	///  or in textual repr: isOwnerOrAdminCross((address,uint256))
+	function isOwnerOrAdminCross(Tuple6 memory user) public view returns (bool) {
+		require(false, stub_error);
+		user;
+		dummy;
+		return false;
+	}
+
 	/// Returns collection type
 	///
 	/// @return `Fungible` or `NFT` or `ReFungible`
@@ -282,7 +350,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuple with sponsor address and his substrate mirror.
+	/// @return Tuble with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
@@ -315,10 +383,32 @@
 		dummy;
 		return new Tuple6[](0);
 	}
+
+	/// Changes collection owner to another account
+	///
+	/// @dev Owner can be changed only by current owner
+	/// @param newOwner new owner cross account
+	/// @dev EVM selector for this function is: 0xe5c9913f,
+	///  or in textual repr: setOwnerCross((address,uint256))
+	function setOwnerCross(Tuple6 memory newOwner) public {
+		require(false, stub_error);
+		newOwner;
+		dummy = 0;
+	}
 }
 
-/// @dev the ERC-165 identifier for this interface is 0x63034ac5
+/// @dev the ERC-165 identifier for this interface is 0x032e5926
 contract ERC20UniqueExtensions is Dummy, ERC165 {
+	/// @dev EVM selector for this function is: 0x0ecd0ab0,
+	///  or in textual repr: approveCross((address,uint256),uint256)
+	function approveCross(Tuple6 memory spender, uint256 amount) public returns (bool) {
+		require(false, stub_error);
+		spender;
+		amount;
+		dummy = 0;
+		return false;
+	}
+
 	/// Burn tokens from account
 	/// @dev Function that burns an `amount` of the tokens of a given account,
 	/// deducting from the sender's allowance for said account.
@@ -334,6 +424,21 @@
 		return false;
 	}
 
+	/// Burn tokens from account
+	/// @dev Function that burns an `amount` of the tokens of a given account,
+	/// deducting from the sender's allowance for said account.
+	/// @param from The account whose tokens will be burnt.
+	/// @param amount The amount that will be burnt.
+	/// @dev EVM selector for this function is: 0xbb2f5a58,
+	///  or in textual repr: burnFromCross((address,uint256),uint256)
+	function burnFromCross(Tuple6 memory from, uint256 amount) public returns (bool) {
+		require(false, stub_error);
+		from;
+		amount;
+		dummy = 0;
+		return false;
+	}
+
 	/// Mint tokens for multiple accounts.
 	/// @param amounts array of pairs of account address and amount
 	/// @dev EVM selector for this function is: 0x1acf2d55,
@@ -344,6 +449,21 @@
 		dummy = 0;
 		return false;
 	}
+
+	/// @dev EVM selector for this function is: 0xd5cf430b,
+	///  or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+	function transferFromCross(
+		Tuple6 memory from,
+		Tuple6 memory to,
+		uint256 amount
+	) public returns (bool) {
+		require(false, stub_error);
+		from;
+		to;
+		amount;
+		dummy = 0;
+		return false;
+	}
 }
 
 /// @dev anonymous struct
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
before · pallets/nonfungible/src/erc.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Nonfungible Pallet EVM API18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Nonfungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.2122extern crate alloc;23use core::{24	char::{REPLACEMENT_CHARACTER, decode_utf16},25	convert::TryInto,26};27use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};28use frame_support::BoundedVec;29use up_data_structs::{30	TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,31	CollectionPropertiesVec,32};33use pallet_evm_coder_substrate::dispatch_to_evm;34use sp_std::vec::Vec;35use pallet_common::{36	erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},37	CollectionHandle, CollectionPropertyPermissions,38};39use pallet_evm::{account::CrossAccountId, PrecompileHandle};40use pallet_evm_coder_substrate::call;41use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};4243use crate::{44	AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,45	SelfWeightOf, weights::WeightInfo, TokenProperties,46};4748/// @title A contract that allows to set and delete token properties and change token property permissions.49#[solidity_interface(name = TokenProperties)]50impl<T: Config> NonfungibleHandle<T> {51	/// @notice Set permissions for token property.52	/// @dev Throws error if `msg.sender` is not admin or owner of the collection.53	/// @param key Property key.54	/// @param isMutable Permission to mutate property.55	/// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.56	/// @param tokenOwner Permission to mutate property by token owner if property is mutable.57	fn set_token_property_permission(58		&mut self,59		caller: caller,60		key: string,61		is_mutable: bool,62		collection_admin: bool,63		token_owner: bool,64	) -> Result<()> {65		let caller = T::CrossAccountId::from_eth(caller);66		<Pallet<T>>::set_property_permission(67			self,68			&caller,69			PropertyKeyPermission {70				key: <Vec<u8>>::from(key)71					.try_into()72					.map_err(|_| "too long key")?,73				permission: PropertyPermission {74					mutable: is_mutable,75					collection_admin,76					token_owner,77				},78			},79		)80		.map_err(dispatch_to_evm::<T>)81	}8283	/// @notice Set token property value.84	/// @dev Throws error if `msg.sender` has no permission to edit the property.85	/// @param tokenId ID of the token.86	/// @param key Property key.87	/// @param value Property value.88	fn set_property(89		&mut self,90		caller: caller,91		token_id: uint256,92		key: string,93		value: bytes,94	) -> Result<()> {95		let caller = T::CrossAccountId::from_eth(caller);96		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;97		let key = <Vec<u8>>::from(key)98			.try_into()99			.map_err(|_| "key too long")?;100		let value = value.0.try_into().map_err(|_| "value too long")?;101102		let nesting_budget = self103			.recorder104			.weight_calls_budget(<StructureWeight<T>>::find_parent());105106		<Pallet<T>>::set_token_property(107			self,108			&caller,109			TokenId(token_id),110			Property { key, value },111			&nesting_budget,112		)113		.map_err(dispatch_to_evm::<T>)114	}115116	/// @notice Delete token property value.117	/// @dev Throws error if `msg.sender` has no permission to edit the property.118	/// @param tokenId ID of the token.119	/// @param key Property key.120	fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {121		let caller = T::CrossAccountId::from_eth(caller);122		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;123		let key = <Vec<u8>>::from(key)124			.try_into()125			.map_err(|_| "key too long")?;126127		let nesting_budget = self128			.recorder129			.weight_calls_budget(<StructureWeight<T>>::find_parent());130131		<Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)132			.map_err(dispatch_to_evm::<T>)133	}134135	/// @notice Get token property value.136	/// @dev Throws error if key not found137	/// @param tokenId ID of the token.138	/// @param key Property key.139	/// @return Property value bytes140	fn property(&self, token_id: uint256, key: string) -> Result<bytes> {141		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;142		let key = <Vec<u8>>::from(key)143			.try_into()144			.map_err(|_| "key too long")?;145146		let props = <TokenProperties<T>>::get((self.id, token_id));147		let prop = props.get(&key).ok_or("key not found")?;148149		Ok(prop.to_vec().into())150	}151}152153#[derive(ToLog)]154pub enum ERC721Events {155	/// @dev This emits when ownership of any NFT changes by any mechanism.156	///  This event emits when NFTs are created (`from` == 0) and destroyed157	///  (`to` == 0). Exception: during contract creation, any number of NFTs158	///  may be created and assigned without emitting Transfer. At the time of159	///  any transfer, the approved address for that NFT (if any) is reset to none.160	Transfer {161		#[indexed]162		from: address,163		#[indexed]164		to: address,165		#[indexed]166		token_id: uint256,167	},168	/// @dev This emits when the approved address for an NFT is changed or169	///  reaffirmed. The zero address indicates there is no approved address.170	///  When a Transfer event emits, this also indicates that the approved171	///  address for that NFT (if any) is reset to none.172	Approval {173		#[indexed]174		owner: address,175		#[indexed]176		approved: address,177		#[indexed]178		token_id: uint256,179	},180	/// @dev This emits when an operator is enabled or disabled for an owner.181	///  The operator can manage all NFTs of the owner.182	#[allow(dead_code)]183	ApprovalForAll {184		#[indexed]185		owner: address,186		#[indexed]187		operator: address,188		approved: bool,189	},190}191192#[derive(ToLog)]193pub enum ERC721UniqueMintableEvents {194	#[allow(dead_code)]195	MintingFinished {},196}197198/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension199/// @dev See https://eips.ethereum.org/EIPS/eip-721200#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]201impl<T: Config> NonfungibleHandle<T> {202	/// @notice A descriptive name for a collection of NFTs in this contract203	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`204	#[solidity(hide, rename_selector = "name")]205	fn name_proxy(&self) -> Result<string> {206		self.name()207	}208209	/// @notice An abbreviated name for NFTs in this contract210	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`211	#[solidity(hide, rename_selector = "symbol")]212	fn symbol_proxy(&self) -> Result<string> {213		self.symbol()214	}215216	/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.217	///218	/// @dev If the token has a `url` property and it is not empty, it is returned.219	///  Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.220	///  If the collection property `baseURI` is empty or absent, return "" (empty string)221	///  otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix222	///  otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).223	///224	/// @return token's const_metadata225	#[solidity(rename_selector = "tokenURI")]226	fn token_uri(&self, token_id: uint256) -> Result<string> {227		let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;228229		match get_token_property(self, token_id_u32, &key::url()).as_deref() {230			Err(_) | Ok("") => (),231			Ok(url) => {232				return Ok(url.into());233			}234		};235236		let base_uri =237			pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())238				.map(BoundedVec::into_inner)239				.map(string::from_utf8)240				.transpose()241				.map_err(|e| {242					Error::Revert(alloc::format!(243						"Can not convert value \"baseURI\" to string with error \"{}\"",244						e245					))246				})?;247248		let base_uri = match base_uri.as_deref() {249			None | Some("") => {250				return Ok("".into());251			}252			Some(base_uri) => base_uri.into(),253		};254255		Ok(256			match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {257				Err(_) | Ok("") => base_uri,258				Ok(suffix) => base_uri + suffix,259			},260		)261	}262}263264/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension265/// @dev See https://eips.ethereum.org/EIPS/eip-721266#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]267impl<T: Config> NonfungibleHandle<T> {268	/// @notice Enumerate valid NFTs269	/// @param index A counter less than `totalSupply()`270	/// @return The token identifier for the `index`th NFT,271	///  (sort order not specified)272	fn token_by_index(&self, index: uint256) -> Result<uint256> {273		Ok(index)274	}275276	/// @dev Not implemented277	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {278		// TODO: Not implemetable279		Err("not implemented".into())280	}281282	/// @notice Count NFTs tracked by this contract283	/// @return A count of valid NFTs tracked by this contract, where each one of284	///  them has an assigned and queryable owner not equal to the zero address285	fn total_supply(&self) -> Result<uint256> {286		self.consume_store_reads(1)?;287		Ok(<Pallet<T>>::total_supply(self).into())288	}289}290291/// @title ERC-721 Non-Fungible Token Standard292/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md293#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]294impl<T: Config> NonfungibleHandle<T> {295	/// @notice Count all NFTs assigned to an owner296	/// @dev NFTs assigned to the zero address are considered invalid, and this297	///  function throws for queries about the zero address.298	/// @param owner An address for whom to query the balance299	/// @return The number of NFTs owned by `owner`, possibly zero300	fn balance_of(&self, owner: address) -> Result<uint256> {301		self.consume_store_reads(1)?;302		let owner = T::CrossAccountId::from_eth(owner);303		let balance = <AccountBalance<T>>::get((self.id, owner));304		Ok(balance.into())305	}306	/// @notice Find the owner of an NFT307	/// @dev NFTs assigned to zero address are considered invalid, and queries308	///  about them do throw.309	/// @param tokenId The identifier for an NFT310	/// @return The address of the owner of the NFT311	fn owner_of(&self, token_id: uint256) -> Result<address> {312		self.consume_store_reads(1)?;313		let token: TokenId = token_id.try_into()?;314		Ok(*<TokenData<T>>::get((self.id, token))315			.ok_or("token not found")?316			.owner317			.as_eth())318	}319	/// @dev Not implemented320	#[solidity(rename_selector = "safeTransferFrom")]321	fn safe_transfer_from_with_data(322		&mut self,323		_from: address,324		_to: address,325		_token_id: uint256,326		_data: bytes,327	) -> Result<void> {328		// TODO: Not implemetable329		Err("not implemented".into())330	}331	/// @dev Not implemented332	fn safe_transfer_from(333		&mut self,334		_from: address,335		_to: address,336		_token_id: uint256,337	) -> Result<void> {338		// TODO: Not implemetable339		Err("not implemented".into())340	}341342	/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE343	///  TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE344	///  THEY MAY BE PERMANENTLY LOST345	/// @dev Throws unless `msg.sender` is the current owner or an authorized346	///  operator for this NFT. Throws if `from` is not the current owner. Throws347	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.348	/// @param from The current owner of the NFT349	/// @param to The new owner350	/// @param tokenId The NFT to transfer351	#[weight(<SelfWeightOf<T>>::transfer_from())]352	fn transfer_from(353		&mut self,354		caller: caller,355		from: address,356		to: address,357		token_id: uint256,358	) -> Result<void> {359		let caller = T::CrossAccountId::from_eth(caller);360		let from = T::CrossAccountId::from_eth(from);361		let to = T::CrossAccountId::from_eth(to);362		let token = token_id.try_into()?;363		let budget = self364			.recorder365			.weight_calls_budget(<StructureWeight<T>>::find_parent());366367		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)368			.map_err(dispatch_to_evm::<T>)?;369		Ok(())370	}371372	/// @notice Set or reaffirm the approved address for an NFT373	/// @dev The zero address indicates there is no approved address.374	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized375	///  operator of the current owner.376	/// @param approved The new approved NFT controller377	/// @param tokenId The NFT to approve378	#[weight(<SelfWeightOf<T>>::approve())]379	fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {380		let caller = T::CrossAccountId::from_eth(caller);381		let approved = T::CrossAccountId::from_eth(approved);382		let token = token_id.try_into()?;383384		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))385			.map_err(dispatch_to_evm::<T>)?;386		Ok(())387	}388389	/// @dev Not implemented390	fn set_approval_for_all(391		&mut self,392		_caller: caller,393		_operator: address,394		_approved: bool,395	) -> Result<void> {396		// TODO: Not implemetable397		Err("not implemented".into())398	}399400	/// @dev Not implemented401	fn get_approved(&self, _token_id: uint256) -> Result<address> {402		// TODO: Not implemetable403		Err("not implemented".into())404	}405406	/// @dev Not implemented407	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {408		// TODO: Not implemetable409		Err("not implemented".into())410	}411}412413/// @title ERC721 Token that can be irreversibly burned (destroyed).414#[solidity_interface(name = ERC721Burnable)]415impl<T: Config> NonfungibleHandle<T> {416	/// @notice Burns a specific ERC721 token.417	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized418	///  operator of the current owner.419	/// @param tokenId The NFT to approve420	#[weight(<SelfWeightOf<T>>::burn_item())]421	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {422		let caller = T::CrossAccountId::from_eth(caller);423		let token = token_id.try_into()?;424425		<Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;426		Ok(())427	}428}429430/// @title ERC721 minting logic.431#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]432impl<T: Config> NonfungibleHandle<T> {433	fn minting_finished(&self) -> Result<bool> {434		Ok(false)435	}436437	/// @notice Function to mint token.438	/// @param to The new owner439	/// @return uint256 The id of the newly minted token440	#[weight(<SelfWeightOf<T>>::create_item())]441	fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {442		let token_id: uint256 = <TokensMinted<T>>::get(self.id)443			.checked_add(1)444			.ok_or("item id overflow")?445			.into();446		self.mint_check_id(caller, to, token_id)?;447		Ok(token_id)448	}449450	/// @notice Function to mint token.451	/// @dev `tokenId` should be obtained with `nextTokenId` method,452	///  unlike standard, you can't specify it manually453	/// @param to The new owner454	/// @param tokenId ID of the minted NFT455	#[solidity(hide, rename_selector = "mint")]456	#[weight(<SelfWeightOf<T>>::create_item())]457	fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {458		let caller = T::CrossAccountId::from_eth(caller);459		let to = T::CrossAccountId::from_eth(to);460		let token_id: u32 = token_id.try_into()?;461		let budget = self462			.recorder463			.weight_calls_budget(<StructureWeight<T>>::find_parent());464465		if <TokensMinted<T>>::get(self.id)466			.checked_add(1)467			.ok_or("item id overflow")?468			!= token_id469		{470			return Err("item id should be next".into());471		}472473		<Pallet<T>>::create_item(474			self,475			&caller,476			CreateItemData::<T> {477				properties: BoundedVec::default(),478				owner: to,479			},480			&budget,481		)482		.map_err(dispatch_to_evm::<T>)?;483484		Ok(true)485	}486487	/// @notice Function to mint token with the given tokenUri.488	/// @param to The new owner489	/// @param tokenUri Token URI that would be stored in the NFT properties490	/// @return uint256 The id of the newly minted token491	#[solidity(rename_selector = "mintWithTokenURI")]492	#[weight(<SelfWeightOf<T>>::create_item())]493	fn mint_with_token_uri(494		&mut self,495		caller: caller,496		to: address,497		token_uri: string,498	) -> Result<uint256> {499		let token_id: uint256 = <TokensMinted<T>>::get(self.id)500			.checked_add(1)501			.ok_or("item id overflow")?502			.into();503		self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;504		Ok(token_id)505	}506507	/// @notice Function to mint token with the given tokenUri.508	/// @dev `tokenId` should be obtained with `nextTokenId` method,509	///  unlike standard, you can't specify it manually510	/// @param to The new owner511	/// @param tokenId ID of the minted NFT512	/// @param tokenUri Token URI that would be stored in the NFT properties513	#[solidity(hide, rename_selector = "mintWithTokenURI")]514	#[weight(<SelfWeightOf<T>>::create_item())]515	fn mint_with_token_uri_check_id(516		&mut self,517		caller: caller,518		to: address,519		token_id: uint256,520		token_uri: string,521	) -> Result<bool> {522		let key = key::url();523		let permission = get_token_permission::<T>(self.id, &key)?;524		if !permission.collection_admin {525			return Err("Operation is not allowed".into());526		}527528		let caller = T::CrossAccountId::from_eth(caller);529		let to = T::CrossAccountId::from_eth(to);530		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;531		let budget = self532			.recorder533			.weight_calls_budget(<StructureWeight<T>>::find_parent());534535		if <TokensMinted<T>>::get(self.id)536			.checked_add(1)537			.ok_or("item id overflow")?538			!= token_id539		{540			return Err("item id should be next".into());541		}542543		let mut properties = CollectionPropertiesVec::default();544		properties545			.try_push(Property {546				key,547				value: token_uri548					.into_bytes()549					.try_into()550					.map_err(|_| "token uri is too long")?,551			})552			.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;553554		<Pallet<T>>::create_item(555			self,556			&caller,557			CreateItemData::<T> {558				properties,559				owner: to,560			},561			&budget,562		)563		.map_err(dispatch_to_evm::<T>)?;564		Ok(true)565	}566567	/// @dev Not implemented568	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {569		Err("not implementable".into())570	}571}572573fn get_token_property<T: Config>(574	collection: &CollectionHandle<T>,575	token_id: u32,576	key: &up_data_structs::PropertyKey,577) -> Result<string> {578	collection.consume_store_reads(1)?;579	let properties = <TokenProperties<T>>::try_get((collection.id, token_id))580		.map_err(|_| Error::Revert("Token properties not found".into()))?;581	if let Some(property) = properties.get(key) {582		return Ok(string::from_utf8_lossy(property).into());583	}584585	Err("Property tokenURI not found".into())586}587588fn get_token_permission<T: Config>(589	collection_id: CollectionId,590	key: &PropertyKey,591) -> Result<PropertyPermission> {592	let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)593		.map_err(|_| Error::Revert("No permissions for collection".into()))?;594	let a = token_property_permissions595		.get(key)596		.map(Clone::clone)597		.ok_or_else(|| {598			let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();599			Error::Revert(alloc::format!("No permission for key {}", key))600		})?;601	Ok(a)602}603604/// @title Unique extensions for ERC721.605#[solidity_interface(name = ERC721UniqueExtensions)]606impl<T: Config> NonfungibleHandle<T> {607	/// @notice A descriptive name for a collection of NFTs in this contract608	fn name(&self) -> Result<string> {609		Ok(decode_utf16(self.name.iter().copied())610			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))611			.collect::<string>())612	}613614	/// @notice An abbreviated name for NFTs in this contract615	fn symbol(&self) -> Result<string> {616		Ok(string::from_utf8_lossy(&self.token_prefix).into())617	}618619	/// @notice Transfer ownership of an NFT620	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`621	///  is the zero address. Throws if `tokenId` is not a valid NFT.622	/// @param to The new owner623	/// @param tokenId The NFT to transfer624	#[weight(<SelfWeightOf<T>>::transfer())]625	fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {626		let caller = T::CrossAccountId::from_eth(caller);627		let to = T::CrossAccountId::from_eth(to);628		let token = token_id.try_into()?;629		let budget = self630			.recorder631			.weight_calls_budget(<StructureWeight<T>>::find_parent());632633		<Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;634		Ok(())635	}636637	/// @notice Burns a specific ERC721 token.638	/// @dev Throws unless `msg.sender` is the current owner or an authorized639	///  operator for this NFT. Throws if `from` is not the current owner. Throws640	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.641	/// @param from The current owner of the NFT642	/// @param tokenId The NFT to transfer643	#[weight(<SelfWeightOf<T>>::burn_from())]644	fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {645		let caller = T::CrossAccountId::from_eth(caller);646		let from = T::CrossAccountId::from_eth(from);647		let token = token_id.try_into()?;648		let budget = self649			.recorder650			.weight_calls_budget(<StructureWeight<T>>::find_parent());651652		<Pallet<T>>::burn_from(self, &caller, &from, token, &budget)653			.map_err(dispatch_to_evm::<T>)?;654		Ok(())655	}656657	/// @notice Returns next free NFT ID.658	fn next_token_id(&self) -> Result<uint256> {659		self.consume_store_reads(1)?;660		Ok(<TokensMinted<T>>::get(self.id)661			.checked_add(1)662			.ok_or("item id overflow")?663			.into())664	}665666	/// @notice Function to mint multiple tokens.667	/// @dev `tokenIds` should be an array of consecutive numbers and first number668	///  should be obtained with `nextTokenId` method669	/// @param to The new owner670	/// @param tokenIds IDs of the minted NFTs671	#[solidity(hide)]672	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]673	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {674		let caller = T::CrossAccountId::from_eth(caller);675		let to = T::CrossAccountId::from_eth(to);676		let mut expected_index = <TokensMinted<T>>::get(self.id)677			.checked_add(1)678			.ok_or("item id overflow")?;679		let budget = self680			.recorder681			.weight_calls_budget(<StructureWeight<T>>::find_parent());682683		let total_tokens = token_ids.len();684		for id in token_ids.into_iter() {685			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;686			if id != expected_index {687				return Err("item id should be next".into());688			}689			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;690		}691		let data = (0..total_tokens)692			.map(|_| CreateItemData::<T> {693				properties: BoundedVec::default(),694				owner: to.clone(),695			})696			.collect();697698		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)699			.map_err(dispatch_to_evm::<T>)?;700		Ok(true)701	}702703	/// @notice Function to mint multiple tokens with the given tokenUris.704	/// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive705	///  numbers and first number should be obtained with `nextTokenId` method706	/// @param to The new owner707	/// @param tokens array of pairs of token ID and token URI for minted tokens708	#[solidity(hide, rename_selector = "mintBulkWithTokenURI")]709	#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]710	fn mint_bulk_with_token_uri(711		&mut self,712		caller: caller,713		to: address,714		tokens: Vec<(uint256, string)>,715	) -> Result<bool> {716		let key = key::url();717		let caller = T::CrossAccountId::from_eth(caller);718		let to = T::CrossAccountId::from_eth(to);719		let mut expected_index = <TokensMinted<T>>::get(self.id)720			.checked_add(1)721			.ok_or("item id overflow")?;722		let budget = self723			.recorder724			.weight_calls_budget(<StructureWeight<T>>::find_parent());725726		let mut data = Vec::with_capacity(tokens.len());727		for (id, token_uri) in tokens {728			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;729			if id != expected_index {730				return Err("item id should be next".into());731			}732			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;733734			let mut properties = CollectionPropertiesVec::default();735			properties736				.try_push(Property {737					key: key.clone(),738					value: token_uri739						.into_bytes()740						.try_into()741						.map_err(|_| "token uri is too long")?,742				})743				.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;744745			data.push(CreateItemData::<T> {746				properties,747				owner: to.clone(),748			});749		}750751		<Pallet<T>>::create_multiple_items(self, &caller, data, &budget)752			.map_err(dispatch_to_evm::<T>)?;753		Ok(true)754	}755}756757#[solidity_interface(758	name = UniqueNFT,759	is(760		ERC721,761		ERC721Enumerable,762		ERC721UniqueExtensions,763		ERC721UniqueMintable,764		ERC721Burnable,765		ERC721Metadata(if(this.flags.erc721metadata)),766		Collection(via(common_mut returns CollectionHandle<T>)),767		TokenProperties,768	)769)]770impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}771772// Not a tests, but code generators773generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);774generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);775776impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>777where778	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,779{780	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");781782	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {783		call::<T, UniqueNFTCall<T>, _, _>(handle, self)784	}785}
modifiedpallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -91,7 +91,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x3af103fb
+/// @dev the ERC-165 identifier for this interface is 0x25d897dc
 contract Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -145,6 +145,19 @@
 		dummy = 0;
 	}
 
+	/// Set the sponsor of the collection.
+	///
+	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+	///
+	/// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.
+	/// @dev EVM selector for this function is: 0x84a1d5a8,
+	///  or in textual repr: setCollectionSponsorCross((address,uint256))
+	function setCollectionSponsorCross(Tuple6 memory sponsor) public {
+		require(false, stub_error);
+		sponsor;
+		dummy = 0;
+	}
+
 	/// Whether there is a pending sponsor.
 	/// @dev EVM selector for this function is: 0x058ac185,
 	///  or in textual repr: hasCollectionPendingSponsor()
@@ -177,10 +190,10 @@
 	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
 	/// @dev EVM selector for this function is: 0x6ec0a9f1,
 	///  or in textual repr: collectionSponsor()
-	function collectionSponsor() public view returns (Tuple17 memory) {
+	function collectionSponsor() public view returns (Tuple6 memory) {
 		require(false, stub_error);
 		dummy;
-		return Tuple17(0x0000000000000000000000000000000000000000, 0);
+		return Tuple6(0x0000000000000000000000000000000000000000, 0);
 	}
 
 	/// Set limits for the collection.
@@ -228,6 +241,26 @@
 	}
 
 	/// Add collection admin.
+	/// @param newAdmin Cross account administrator address.
+	/// @dev EVM selector for this function is: 0x859aa7d6,
+	///  or in textual repr: addCollectionAdminCross((address,uint256))
+	function addCollectionAdminCross(Tuple6 memory newAdmin) public {
+		require(false, stub_error);
+		newAdmin;
+		dummy = 0;
+	}
+
+	/// Remove collection admin.
+	/// @param admin Cross account administrator address.
+	/// @dev EVM selector for this function is: 0x6c0cd173,
+	///  or in textual repr: removeCollectionAdminCross((address,uint256))
+	function removeCollectionAdminCross(Tuple6 memory admin) public {
+		require(false, stub_error);
+		admin;
+		dummy = 0;
+	}
+
+	/// Add collection admin.
 	/// @param newAdmin Address of the added administrator.
 	/// @dev EVM selector for this function is: 0x92e462c7,
 	///  or in textual repr: addCollectionAdmin(address)
@@ -307,6 +340,17 @@
 		dummy = 0;
 	}
 
+	/// Add user to allowed list.
+	///
+	/// @param user User cross account address.
+	/// @dev EVM selector for this function is: 0xa0184a3a,
+	///  or in textual repr: addToCollectionAllowListCross((address,uint256))
+	function addToCollectionAllowListCross(Tuple6 memory user) public {
+		require(false, stub_error);
+		user;
+		dummy = 0;
+	}
+
 	/// Remove the user from the allowed list.
 	///
 	/// @param user Address of a removed user.
@@ -318,6 +362,17 @@
 		dummy = 0;
 	}
 
+	/// Remove user from allowed list.
+	///
+	/// @param user User cross account address.
+	/// @dev EVM selector for this function is: 0x09ba452a,
+	///  or in textual repr: removeFromCollectionAllowListCross((address,uint256))
+	function removeFromCollectionAllowListCross(Tuple6 memory user) public {
+		require(false, stub_error);
+		user;
+		dummy = 0;
+	}
+
 	/// Switch permission for minting.
 	///
 	/// @param mode Enable if "true".
@@ -342,6 +397,19 @@
 		return false;
 	}
 
+	/// Check that account is the owner or admin of the collection
+	///
+	/// @param user User cross account to verify
+	/// @return "true" if account is the owner or admin
+	/// @dev EVM selector for this function is: 0x3e75a905,
+	///  or in textual repr: isOwnerOrAdminCross((address,uint256))
+	function isOwnerOrAdminCross(Tuple6 memory user) public view returns (bool) {
+		require(false, stub_error);
+		user;
+		dummy;
+		return false;
+	}
+
 	/// Returns collection type
 	///
 	/// @return `Fungible` or `NFT` or `ReFungible`
@@ -355,14 +423,14 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuple with sponsor address and his substrate mirror.
+	/// @return Tuble with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
-	function collectionOwner() public view returns (Tuple17 memory) {
+	function collectionOwner() public view returns (Tuple6 memory) {
 		require(false, stub_error);
 		dummy;
-		return Tuple17(0x0000000000000000000000000000000000000000, 0);
+		return Tuple6(0x0000000000000000000000000000000000000000, 0);
 	}
 
 	/// Changes collection owner to another account
@@ -383,17 +451,23 @@
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0x5813216b,
 	///  or in textual repr: collectionAdmins()
-	function collectionAdmins() public view returns (Tuple17[] memory) {
+	function collectionAdmins() public view returns (Tuple6[] memory) {
 		require(false, stub_error);
 		dummy;
-		return new Tuple17[](0);
+		return new Tuple6[](0);
 	}
-}
 
-/// @dev anonymous struct
-struct Tuple17 {
-	address field_0;
-	uint256 field_1;
+	/// Changes collection owner to another account
+	///
+	/// @dev Owner can be changed only by current owner
+	/// @param newOwner new owner cross account
+	/// @dev EVM selector for this function is: 0xe5c9913f,
+	///  or in textual repr: setOwnerCross((address,uint256))
+	function setOwnerCross(Tuple6 memory newOwner) public {
+		require(false, stub_error);
+		newOwner;
+		dummy = 0;
+	}
 }
 
 /// @title ERC-721 Non-Fungible Token Standard, optional metadata extension
@@ -540,7 +614,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x4468500d
+/// @dev the ERC-165 identifier for this interface is 0x244543ee
 contract ERC721UniqueExtensions is Dummy, ERC165 {
 	/// @notice A descriptive name for a collection of NFTs in this contract
 	/// @dev EVM selector for this function is: 0x06fdde03,
@@ -560,6 +634,21 @@
 		return "";
 	}
 
+	/// @notice Set or reaffirm the approved address for an NFT
+	/// @dev The zero address indicates there is no approved address.
+	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized
+	///  operator of the current owner.
+	/// @param approved The new substrate address approved NFT controller
+	/// @param tokenId The NFT to approve
+	/// @dev EVM selector for this function is: 0x0ecd0ab0,
+	///  or in textual repr: approveCross((address,uint256),uint256)
+	function approveCross(Tuple6 memory approved, uint256 tokenId) public {
+		require(false, stub_error);
+		approved;
+		tokenId;
+		dummy = 0;
+	}
+
 	/// @notice Transfer ownership of an NFT
 	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
 	///  is the zero address. Throws if `tokenId` is not a valid NFT.
@@ -574,6 +663,26 @@
 		dummy = 0;
 	}
 
+	/// @notice Transfer ownership of an NFT from cross account address to cross account address
+	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+	///  is the zero address. Throws if `tokenId` is not a valid NFT.
+	/// @param from Cross acccount address of current owner
+	/// @param to Cross acccount address of new owner
+	/// @param tokenId The NFT to transfer
+	/// @dev EVM selector for this function is: 0xd5cf430b,
+	///  or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+	function transferFromCross(
+		Tuple6 memory from,
+		Tuple6 memory to,
+		uint256 tokenId
+	) public {
+		require(false, stub_error);
+		from;
+		to;
+		tokenId;
+		dummy = 0;
+	}
+
 	/// @notice Burns a specific ERC721 token.
 	/// @dev Throws unless `msg.sender` is the current owner or an authorized
 	///  operator for this NFT. Throws if `from` is not the current owner. Throws
@@ -589,6 +698,21 @@
 		dummy = 0;
 	}
 
+	/// @notice Burns a specific ERC721 token.
+	/// @dev Throws unless `msg.sender` is the current owner or an authorized
+	///  operator for this NFT. Throws if `from` is not the current owner. Throws
+	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.
+	/// @param from The current owner of the NFT
+	/// @param tokenId The NFT to transfer
+	/// @dev EVM selector for this function is: 0xbb2f5a58,
+	///  or in textual repr: burnFromCross((address,uint256),uint256)
+	function burnFromCross(Tuple6 memory from, uint256 tokenId) public {
+		require(false, stub_error);
+		from;
+		tokenId;
+		dummy = 0;
+	}
+
 	/// @notice Returns next free NFT ID.
 	/// @dev EVM selector for this function is: 0x75794a3c,
 	///  or in textual repr: nextTokenId()
@@ -619,7 +743,7 @@
 	// /// @param tokens array of pairs of token ID and token URI for minted tokens
 	// /// @dev EVM selector for this function is: 0x36543006,
 	// ///  or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
-	// function mintBulkWithTokenURI(address to, Tuple6[] memory tokens) public returns (bool) {
+	// function mintBulkWithTokenURI(address to, Tuple8[] memory tokens) public returns (bool) {
 	// 	require(false, stub_error);
 	// 	to;
 	// 	tokens;
@@ -630,11 +754,17 @@
 }
 
 /// @dev anonymous struct
-struct Tuple6 {
+struct Tuple8 {
 	uint256 field_0;
 	string field_1;
 }
 
+/// @dev anonymous struct
+struct Tuple6 {
+	address field_0;
+	uint256 field_1;
+}
+
 /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 /// @dev See https://eips.ethereum.org/EIPS/eip-721
 /// @dev the ERC-165 identifier for this interface is 0x780e9d63
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -30,6 +30,7 @@
 use pallet_common::{
 	CollectionHandle, CollectionPropertyPermissions,
 	erc::{CommonEvmHandler, CollectionCall, static_property::key},
+	eth::convert_tuple_to_cross_account,
 };
 use pallet_evm::{account::CrossAccountId, PrecompileHandle};
 use pallet_evm_coder_substrate::{call, dispatch_to_evm};
@@ -194,7 +195,10 @@
 }
 
 #[solidity_interface(name = ERC721Metadata)]
-impl<T: Config> RefungibleHandle<T> {
+impl<T: Config> RefungibleHandle<T>
+where
+	T::AccountId: From<[u8; 32]>,
+{
 	/// @notice A descriptive name for a collection of NFTs in this contract
 	/// @dev real implementation of this function lies in `ERC721UniqueExtensions`
 	#[solidity(hide, rename_selector = "name")]
@@ -631,7 +635,10 @@
 
 /// @title Unique extensions for ERC721.
 #[solidity_interface(name = ERC721UniqueExtensions)]
-impl<T: Config> RefungibleHandle<T> {
+impl<T: Config> RefungibleHandle<T>
+where
+	T::AccountId: From<[u8; 32]>,
+{
 	/// @notice A descriptive name for a collection of NFTs in this contract
 	fn name(&self) -> Result<string> {
 		Ok(decode_utf16(self.name.iter().copied())
@@ -659,14 +666,44 @@
 			.recorder
 			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		let balance = balance(&self, token, &caller)?;
-		ensure_single_owner(&self, token, balance)?;
+		let balance = balance(self, token, &caller)?;
+		ensure_single_owner(self, token, balance)?;
 
 		<Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)
 			.map_err(dispatch_to_evm::<T>)?;
 		Ok(())
 	}
 
+	/// @notice Transfer ownership of an RFT
+	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+	///  is the zero address. Throws if `tokenId` is not a valid RFT.
+	///  Throws if RFT pieces have multiple owners.
+	/// @param to The new owner
+	/// @param tokenId The RFT to transfer
+	#[weight(<SelfWeightOf<T>>::transfer_creating_removing())]
+	fn transfer_from_cross(
+		&mut self,
+		caller: caller,
+		from: (address, uint256),
+		to: (address, uint256),
+		token_id: uint256,
+	) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let from = convert_tuple_to_cross_account::<T>(from)?;
+		let to = convert_tuple_to_cross_account::<T>(to)?;
+		let token_id = token_id.try_into()?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		let balance = balance(self, token_id, &from)?;
+		ensure_single_owner(self, token_id, balance)?;
+
+		Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
 	/// @notice Burns a specific ERC721 token.
 	/// @dev Throws unless `msg.sender` is the current owner or an authorized
 	///  operator for this RFT. Throws if `from` is not the current owner. Throws
@@ -683,8 +720,37 @@
 			.recorder
 			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		let balance = balance(&self, token, &caller)?;
-		ensure_single_owner(&self, token, balance)?;
+		let balance = balance(self, token, &from)?;
+		ensure_single_owner(self, token, balance)?;
+
+		<Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
+	/// @notice Burns a specific ERC721 token.
+	/// @dev Throws unless `msg.sender` is the current owner or an authorized
+	///  operator for this RFT. Throws if `from` is not the current owner. Throws
+	///  if `to` is the zero address. Throws if `tokenId` is not a valid RFT.
+	///  Throws if RFT pieces have multiple owners.
+	/// @param from The current owner of the RFT
+	/// @param tokenId The RFT to transfer
+	#[weight(<SelfWeightOf<T>>::burn_from())]
+	fn burn_from_cross(
+		&mut self,
+		caller: caller,
+		from: (address, uint256),
+		token_id: uint256,
+	) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let from = convert_tuple_to_cross_account::<T>(from)?;
+		let token = token_id.try_into()?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		let balance = balance(self, token, &from)?;
+		ensure_single_owner(self, token, balance)?;
 
 		<Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)
 			.map_err(dispatch_to_evm::<T>)?;
modifiedpallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth
--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -91,7 +91,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x3af103fb
+/// @dev the ERC-165 identifier for this interface is 0x25d897dc
 contract Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -145,6 +145,19 @@
 		dummy = 0;
 	}
 
+	/// Set the sponsor of the collection.
+	///
+	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+	///
+	/// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.
+	/// @dev EVM selector for this function is: 0x84a1d5a8,
+	///  or in textual repr: setCollectionSponsorCross((address,uint256))
+	function setCollectionSponsorCross(Tuple6 memory sponsor) public {
+		require(false, stub_error);
+		sponsor;
+		dummy = 0;
+	}
+
 	/// Whether there is a pending sponsor.
 	/// @dev EVM selector for this function is: 0x058ac185,
 	///  or in textual repr: hasCollectionPendingSponsor()
@@ -177,10 +190,10 @@
 	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
 	/// @dev EVM selector for this function is: 0x6ec0a9f1,
 	///  or in textual repr: collectionSponsor()
-	function collectionSponsor() public view returns (Tuple17 memory) {
+	function collectionSponsor() public view returns (Tuple6 memory) {
 		require(false, stub_error);
 		dummy;
-		return Tuple17(0x0000000000000000000000000000000000000000, 0);
+		return Tuple6(0x0000000000000000000000000000000000000000, 0);
 	}
 
 	/// Set limits for the collection.
@@ -228,6 +241,26 @@
 	}
 
 	/// Add collection admin.
+	/// @param newAdmin Cross account administrator address.
+	/// @dev EVM selector for this function is: 0x859aa7d6,
+	///  or in textual repr: addCollectionAdminCross((address,uint256))
+	function addCollectionAdminCross(Tuple6 memory newAdmin) public {
+		require(false, stub_error);
+		newAdmin;
+		dummy = 0;
+	}
+
+	/// Remove collection admin.
+	/// @param admin Cross account administrator address.
+	/// @dev EVM selector for this function is: 0x6c0cd173,
+	///  or in textual repr: removeCollectionAdminCross((address,uint256))
+	function removeCollectionAdminCross(Tuple6 memory admin) public {
+		require(false, stub_error);
+		admin;
+		dummy = 0;
+	}
+
+	/// Add collection admin.
 	/// @param newAdmin Address of the added administrator.
 	/// @dev EVM selector for this function is: 0x92e462c7,
 	///  or in textual repr: addCollectionAdmin(address)
@@ -307,6 +340,17 @@
 		dummy = 0;
 	}
 
+	/// Add user to allowed list.
+	///
+	/// @param user User cross account address.
+	/// @dev EVM selector for this function is: 0xa0184a3a,
+	///  or in textual repr: addToCollectionAllowListCross((address,uint256))
+	function addToCollectionAllowListCross(Tuple6 memory user) public {
+		require(false, stub_error);
+		user;
+		dummy = 0;
+	}
+
 	/// Remove the user from the allowed list.
 	///
 	/// @param user Address of a removed user.
@@ -318,6 +362,17 @@
 		dummy = 0;
 	}
 
+	/// Remove user from allowed list.
+	///
+	/// @param user User cross account address.
+	/// @dev EVM selector for this function is: 0x09ba452a,
+	///  or in textual repr: removeFromCollectionAllowListCross((address,uint256))
+	function removeFromCollectionAllowListCross(Tuple6 memory user) public {
+		require(false, stub_error);
+		user;
+		dummy = 0;
+	}
+
 	/// Switch permission for minting.
 	///
 	/// @param mode Enable if "true".
@@ -342,6 +397,19 @@
 		return false;
 	}
 
+	/// Check that account is the owner or admin of the collection
+	///
+	/// @param user User cross account to verify
+	/// @return "true" if account is the owner or admin
+	/// @dev EVM selector for this function is: 0x3e75a905,
+	///  or in textual repr: isOwnerOrAdminCross((address,uint256))
+	function isOwnerOrAdminCross(Tuple6 memory user) public view returns (bool) {
+		require(false, stub_error);
+		user;
+		dummy;
+		return false;
+	}
+
 	/// Returns collection type
 	///
 	/// @return `Fungible` or `NFT` or `ReFungible`
@@ -355,14 +423,14 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuple with sponsor address and his substrate mirror.
+	/// @return Tuble with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
-	function collectionOwner() public view returns (Tuple17 memory) {
+	function collectionOwner() public view returns (Tuple6 memory) {
 		require(false, stub_error);
 		dummy;
-		return Tuple17(0x0000000000000000000000000000000000000000, 0);
+		return Tuple6(0x0000000000000000000000000000000000000000, 0);
 	}
 
 	/// Changes collection owner to another account
@@ -383,17 +451,23 @@
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0x5813216b,
 	///  or in textual repr: collectionAdmins()
-	function collectionAdmins() public view returns (Tuple17[] memory) {
+	function collectionAdmins() public view returns (Tuple6[] memory) {
 		require(false, stub_error);
 		dummy;
-		return new Tuple17[](0);
+		return new Tuple6[](0);
 	}
-}
 
-/// @dev anonymous struct
-struct Tuple17 {
-	address field_0;
-	uint256 field_1;
+	/// Changes collection owner to another account
+	///
+	/// @dev Owner can be changed only by current owner
+	/// @param newOwner new owner cross account
+	/// @dev EVM selector for this function is: 0xe5c9913f,
+	///  or in textual repr: setOwnerCross((address,uint256))
+	function setOwnerCross(Tuple6 memory newOwner) public {
+		require(false, stub_error);
+		newOwner;
+		dummy = 0;
+	}
 }
 
 /// @dev the ERC-165 identifier for this interface is 0x5b5e139f
@@ -538,7 +612,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0xef1eaacb
+/// @dev the ERC-165 identifier for this interface is 0x81feb398
 contract ERC721UniqueExtensions is Dummy, ERC165 {
 	/// @notice A descriptive name for a collection of NFTs in this contract
 	/// @dev EVM selector for this function is: 0x06fdde03,
@@ -573,6 +647,26 @@
 		dummy = 0;
 	}
 
+	/// @notice Transfer ownership of an RFT
+	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+	///  is the zero address. Throws if `tokenId` is not a valid RFT.
+	///  Throws if RFT pieces have multiple owners.
+	/// @param to The new owner
+	/// @param tokenId The RFT to transfer
+	/// @dev EVM selector for this function is: 0xd5cf430b,
+	///  or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+	function transferFromCross(
+		Tuple6 memory from,
+		Tuple6 memory to,
+		uint256 tokenId
+	) public {
+		require(false, stub_error);
+		from;
+		to;
+		tokenId;
+		dummy = 0;
+	}
+
 	/// @notice Burns a specific ERC721 token.
 	/// @dev Throws unless `msg.sender` is the current owner or an authorized
 	///  operator for this RFT. Throws if `from` is not the current owner. Throws
@@ -589,6 +683,22 @@
 		dummy = 0;
 	}
 
+	/// @notice Burns a specific ERC721 token.
+	/// @dev Throws unless `msg.sender` is the current owner or an authorized
+	///  operator for this RFT. Throws if `from` is not the current owner. Throws
+	///  if `to` is the zero address. Throws if `tokenId` is not a valid RFT.
+	///  Throws if RFT pieces have multiple owners.
+	/// @param from The current owner of the RFT
+	/// @param tokenId The RFT to transfer
+	/// @dev EVM selector for this function is: 0xbb2f5a58,
+	///  or in textual repr: burnFromCross((address,uint256),uint256)
+	function burnFromCross(Tuple6 memory from, uint256 tokenId) public {
+		require(false, stub_error);
+		from;
+		tokenId;
+		dummy = 0;
+	}
+
 	/// @notice Returns next free RFT ID.
 	/// @dev EVM selector for this function is: 0x75794a3c,
 	///  or in textual repr: nextTokenId()
@@ -620,7 +730,7 @@
 	// /// @param tokens array of pairs of token ID and token URI for minted tokens
 	// /// @dev EVM selector for this function is: 0x36543006,
 	// ///  or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
-	// function mintBulkWithTokenURI(address to, Tuple6[] memory tokens) public returns (bool) {
+	// function mintBulkWithTokenURI(address to, Tuple8[] memory tokens) public returns (bool) {
 	// 	require(false, stub_error);
 	// 	to;
 	// 	tokens;
@@ -642,11 +752,17 @@
 }
 
 /// @dev anonymous struct
-struct Tuple6 {
+struct Tuple8 {
 	uint256 field_0;
 	string field_1;
 }
 
+/// @dev anonymous struct
+struct Tuple6 {
+	address field_0;
+	uint256 field_1;
+}
+
 /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 /// @dev See https://eips.ethereum.org/EIPS/eip-721
 /// @dev the ERC-165 identifier for this interface is 0x780e9d63
modifiedtests/src/eth/allowlist.test.tsdiffbeforeafterboth
--- a/tests/src/eth/allowlist.test.ts
+++ b/tests/src/eth/allowlist.test.ts
@@ -89,22 +89,22 @@
     expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.false;
   });
 
-  // TODO: Temprorary off. Need refactor
-  // itEth('Collection allowlist can be added and removed by [sub] address', async ({helper}) => {
-  //   const owner = await helper.eth.createAccountWithBalance(donor);
-  //   const user = donor;
-
-  //   const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
-  //   const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
-
-  //   expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
-  //   await collectionEvm.methods.addToCollectionAllowListSubstrate(user.addressRaw).send({from: owner});
-  //   expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true;
+  //itEth('Collection allowlist can be added and removed by [cross] address', async ({helper}) => {
+  //  const owner = await helper.eth.createAccountWithBalance(donor);
+  //  const user = donor;
+  //  
+  //  const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
+  //  const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
+  //  const userCross = helper.ethCrossAccount.fromKeyringPair(user);
+  //  
+  //  expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
+  //  await collectionEvm.methods.addToCollectionAllowListCross(userCross).send({from: owner});
+  //  expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true;
+  //  
+  //  await collectionEvm.methods.removeFromCollectionAllowListCross(userCross).send({from: owner});
+  //  expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
+  //});
 
-  //   await collectionEvm.methods.removeFromCollectionAllowListSubstrate(user.addressRaw).send({from: owner});
-  //   expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
-  // });
-
   itEth('Collection allowlist can not be add and remove [eth] address by not owner', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const notOwner = await helper.eth.createAccountWithBalance(donor);
@@ -117,28 +117,27 @@
     await expect(collectionEvm.methods.addToCollectionAllowList(user).call({from: notOwner})).to.be.rejectedWith('NoPermission');
     expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.false;
     await collectionEvm.methods.addToCollectionAllowList(user).send({from: owner});
-
     expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.true;
     await expect(collectionEvm.methods.removeFromCollectionAllowList(user).call({from: notOwner})).to.be.rejectedWith('NoPermission');
     expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.true;
   });
 
-  // TODO: Temprorary off. Need refactor
-  // itEth('Collection allowlist can not be add and remove [sub] address by not owner', async ({helper}) => {
-  //   const owner = await helper.eth.createAccountWithBalance(donor);
-  //   const notOwner = await helper.eth.createAccountWithBalance(donor);
-  //   const user = donor;
-
-  //   const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
-  //   const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
-
-  //   expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
-  //   await expect(collectionEvm.methods.addToCollectionAllowListSubstrate(user.addressRaw).call({from: notOwner})).to.be.rejectedWith('NoPermission');
-  //   expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
-  //   await collectionEvm.methods.addToCollectionAllowListSubstrate(user.addressRaw).send({from: owner});
-
-  //   expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true;
-  //   await expect(collectionEvm.methods.removeFromCollectionAllowListSubstrate(user.addressRaw).call({from: notOwner})).to.be.rejectedWith('NoPermission');
-  //   expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true;
-  // });
+  //itEth('Collection allowlist can not be add and remove [cross] address by not owner', async ({helper}) => {
+  //  const owner = await helper.eth.createAccountWithBalance(donor);
+  //  const notOwner = await helper.eth.createAccountWithBalance(donor);
+  //  const user = donor;
+  //  
+  //  const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
+  //  const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
+  //  
+  //  expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
+  //  const userCross = helper.ethCrossAccount.fromKeyringPair(user);
+  //  await expect(collectionEvm.methods.addToCollectionAllowListCross(userCross).call({from: notOwner})).to.be.rejectedWith('NoPermission');
+  //  expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
+  //  await collectionEvm.methods.addToCollectionAllowListCross(userCross).send({from: owner});
+  //  
+  //  expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true;
+  //  await expect(collectionEvm.methods.removeFromCollectionAllowListCross(userCross).call({from: notOwner})).to.be.rejectedWith('NoPermission');
+  //  expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true;
+  //});
 });
modifiedtests/src/eth/api/UniqueFungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -13,7 +13,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x3af103fb
+/// @dev the ERC-165 identifier for this interface is 0x25d897dc
 interface Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -49,6 +49,15 @@
 	///  or in textual repr: setCollectionSponsor(address)
 	function setCollectionSponsor(address sponsor) external;
 
+	/// Set the sponsor of the collection.
+	///
+	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+	///
+	/// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.
+	/// @dev EVM selector for this function is: 0x84a1d5a8,
+	///  or in textual repr: setCollectionSponsorCross((address,uint256))
+	function setCollectionSponsorCross(Tuple6 memory sponsor) external;
+
 	/// Whether there is a pending sponsor.
 	/// @dev EVM selector for this function is: 0x058ac185,
 	///  or in textual repr: hasCollectionPendingSponsor()
@@ -104,6 +113,18 @@
 	function contractAddress() external view returns (address);
 
 	/// Add collection admin.
+	/// @param newAdmin Cross account administrator address.
+	/// @dev EVM selector for this function is: 0x859aa7d6,
+	///  or in textual repr: addCollectionAdminCross((address,uint256))
+	function addCollectionAdminCross(Tuple6 memory newAdmin) external;
+
+	/// Remove collection admin.
+	/// @param admin Cross account administrator address.
+	/// @dev EVM selector for this function is: 0x6c0cd173,
+	///  or in textual repr: removeCollectionAdminCross((address,uint256))
+	function removeCollectionAdminCross(Tuple6 memory admin) external;
+
+	/// Add collection admin.
 	/// @param newAdmin Address of the added administrator.
 	/// @dev EVM selector for this function is: 0x92e462c7,
 	///  or in textual repr: addCollectionAdmin(address)
@@ -153,6 +174,13 @@
 	///  or in textual repr: addToCollectionAllowList(address)
 	function addToCollectionAllowList(address user) external;
 
+	/// Add user to allowed list.
+	///
+	/// @param user User cross account address.
+	/// @dev EVM selector for this function is: 0xa0184a3a,
+	///  or in textual repr: addToCollectionAllowListCross((address,uint256))
+	function addToCollectionAllowListCross(Tuple6 memory user) external;
+
 	/// Remove the user from the allowed list.
 	///
 	/// @param user Address of a removed user.
@@ -160,6 +188,13 @@
 	///  or in textual repr: removeFromCollectionAllowList(address)
 	function removeFromCollectionAllowList(address user) external;
 
+	/// Remove user from allowed list.
+	///
+	/// @param user User cross account address.
+	/// @dev EVM selector for this function is: 0x09ba452a,
+	///  or in textual repr: removeFromCollectionAllowListCross((address,uint256))
+	function removeFromCollectionAllowListCross(Tuple6 memory user) external;
+
 	/// Switch permission for minting.
 	///
 	/// @param mode Enable if "true".
@@ -175,6 +210,14 @@
 	///  or in textual repr: isOwnerOrAdmin(address)
 	function isOwnerOrAdmin(address user) external view returns (bool);
 
+	/// Check that account is the owner or admin of the collection
+	///
+	/// @param user User cross account to verify
+	/// @return "true" if account is the owner or admin
+	/// @dev EVM selector for this function is: 0x3e75a905,
+	///  or in textual repr: isOwnerOrAdminCross((address,uint256))
+	function isOwnerOrAdminCross(Tuple6 memory user) external view returns (bool);
+
 	/// Returns collection type
 	///
 	/// @return `Fungible` or `NFT` or `ReFungible`
@@ -184,7 +227,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuple with sponsor address and his substrate mirror.
+	/// @return Tuble with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
@@ -205,10 +248,22 @@
 	/// @dev EVM selector for this function is: 0x5813216b,
 	///  or in textual repr: collectionAdmins()
 	function collectionAdmins() external view returns (Tuple6[] memory);
+
+	/// Changes collection owner to another account
+	///
+	/// @dev Owner can be changed only by current owner
+	/// @param newOwner new owner cross account
+	/// @dev EVM selector for this function is: 0xe5c9913f,
+	///  or in textual repr: setOwnerCross((address,uint256))
+	function setOwnerCross(Tuple6 memory newOwner) external;
 }
 
-/// @dev the ERC-165 identifier for this interface is 0x63034ac5
+/// @dev the ERC-165 identifier for this interface is 0x032e5926
 interface ERC20UniqueExtensions is Dummy, ERC165 {
+	/// @dev EVM selector for this function is: 0x0ecd0ab0,
+	///  or in textual repr: approveCross((address,uint256),uint256)
+	function approveCross(Tuple6 memory spender, uint256 amount) external returns (bool);
+
 	/// Burn tokens from account
 	/// @dev Function that burns an `amount` of the tokens of a given account,
 	/// deducting from the sender's allowance for said account.
@@ -218,11 +273,28 @@
 	///  or in textual repr: burnFrom(address,uint256)
 	function burnFrom(address from, uint256 amount) external returns (bool);
 
+	/// Burn tokens from account
+	/// @dev Function that burns an `amount` of the tokens of a given account,
+	/// deducting from the sender's allowance for said account.
+	/// @param from The account whose tokens will be burnt.
+	/// @param amount The amount that will be burnt.
+	/// @dev EVM selector for this function is: 0xbb2f5a58,
+	///  or in textual repr: burnFromCross((address,uint256),uint256)
+	function burnFromCross(Tuple6 memory from, uint256 amount) external returns (bool);
+
 	/// Mint tokens for multiple accounts.
 	/// @param amounts array of pairs of account address and amount
 	/// @dev EVM selector for this function is: 0x1acf2d55,
 	///  or in textual repr: mintBulk((address,uint256)[])
 	function mintBulk(Tuple6[] memory amounts) external returns (bool);
+
+	/// @dev EVM selector for this function is: 0xd5cf430b,
+	///  or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+	function transferFromCross(
+		Tuple6 memory from,
+		Tuple6 memory to,
+		uint256 amount
+	) external returns (bool);
 }
 
 /// @dev anonymous struct
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -62,7 +62,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x3af103fb
+/// @dev the ERC-165 identifier for this interface is 0x25d897dc
 interface Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -98,6 +98,15 @@
 	///  or in textual repr: setCollectionSponsor(address)
 	function setCollectionSponsor(address sponsor) external;
 
+	/// Set the sponsor of the collection.
+	///
+	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+	///
+	/// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.
+	/// @dev EVM selector for this function is: 0x84a1d5a8,
+	///  or in textual repr: setCollectionSponsorCross((address,uint256))
+	function setCollectionSponsorCross(Tuple6 memory sponsor) external;
+
 	/// Whether there is a pending sponsor.
 	/// @dev EVM selector for this function is: 0x058ac185,
 	///  or in textual repr: hasCollectionPendingSponsor()
@@ -120,7 +129,7 @@
 	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
 	/// @dev EVM selector for this function is: 0x6ec0a9f1,
 	///  or in textual repr: collectionSponsor()
-	function collectionSponsor() external view returns (Tuple17 memory);
+	function collectionSponsor() external view returns (Tuple6 memory);
 
 	/// Set limits for the collection.
 	/// @dev Throws error if limit not found.
@@ -153,6 +162,18 @@
 	function contractAddress() external view returns (address);
 
 	/// Add collection admin.
+	/// @param newAdmin Cross account administrator address.
+	/// @dev EVM selector for this function is: 0x859aa7d6,
+	///  or in textual repr: addCollectionAdminCross((address,uint256))
+	function addCollectionAdminCross(Tuple6 memory newAdmin) external;
+
+	/// Remove collection admin.
+	/// @param admin Cross account administrator address.
+	/// @dev EVM selector for this function is: 0x6c0cd173,
+	///  or in textual repr: removeCollectionAdminCross((address,uint256))
+	function removeCollectionAdminCross(Tuple6 memory admin) external;
+
+	/// Add collection admin.
 	/// @param newAdmin Address of the added administrator.
 	/// @dev EVM selector for this function is: 0x92e462c7,
 	///  or in textual repr: addCollectionAdmin(address)
@@ -202,6 +223,13 @@
 	///  or in textual repr: addToCollectionAllowList(address)
 	function addToCollectionAllowList(address user) external;
 
+	/// Add user to allowed list.
+	///
+	/// @param user User cross account address.
+	/// @dev EVM selector for this function is: 0xa0184a3a,
+	///  or in textual repr: addToCollectionAllowListCross((address,uint256))
+	function addToCollectionAllowListCross(Tuple6 memory user) external;
+
 	/// Remove the user from the allowed list.
 	///
 	/// @param user Address of a removed user.
@@ -209,6 +237,13 @@
 	///  or in textual repr: removeFromCollectionAllowList(address)
 	function removeFromCollectionAllowList(address user) external;
 
+	/// Remove user from allowed list.
+	///
+	/// @param user User cross account address.
+	/// @dev EVM selector for this function is: 0x09ba452a,
+	///  or in textual repr: removeFromCollectionAllowListCross((address,uint256))
+	function removeFromCollectionAllowListCross(Tuple6 memory user) external;
+
 	/// Switch permission for minting.
 	///
 	/// @param mode Enable if "true".
@@ -224,6 +259,14 @@
 	///  or in textual repr: isOwnerOrAdmin(address)
 	function isOwnerOrAdmin(address user) external view returns (bool);
 
+	/// Check that account is the owner or admin of the collection
+	///
+	/// @param user User cross account to verify
+	/// @return "true" if account is the owner or admin
+	/// @dev EVM selector for this function is: 0x3e75a905,
+	///  or in textual repr: isOwnerOrAdminCross((address,uint256))
+	function isOwnerOrAdminCross(Tuple6 memory user) external view returns (bool);
+
 	/// Returns collection type
 	///
 	/// @return `Fungible` or `NFT` or `ReFungible`
@@ -233,11 +276,11 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuple with sponsor address and his substrate mirror.
+	/// @return Tuble with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
-	function collectionOwner() external view returns (Tuple17 memory);
+	function collectionOwner() external view returns (Tuple6 memory);
 
 	/// Changes collection owner to another account
 	///
@@ -253,13 +296,15 @@
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0x5813216b,
 	///  or in textual repr: collectionAdmins()
-	function collectionAdmins() external view returns (Tuple17[] memory);
-}
+	function collectionAdmins() external view returns (Tuple6[] memory);
 
-/// @dev anonymous struct
-struct Tuple17 {
-	address field_0;
-	uint256 field_1;
+	/// Changes collection owner to another account
+	///
+	/// @dev Owner can be changed only by current owner
+	/// @param newOwner new owner cross account
+	/// @dev EVM selector for this function is: 0xe5c9913f,
+	///  or in textual repr: setOwnerCross((address,uint256))
+	function setOwnerCross(Tuple6 memory newOwner) external;
 }
 
 /// @title ERC-721 Non-Fungible Token Standard, optional metadata extension
@@ -357,7 +402,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x4468500d
+/// @dev the ERC-165 identifier for this interface is 0x244543ee
 interface ERC721UniqueExtensions is Dummy, ERC165 {
 	/// @notice A descriptive name for a collection of NFTs in this contract
 	/// @dev EVM selector for this function is: 0x06fdde03,
@@ -369,6 +414,16 @@
 	///  or in textual repr: symbol()
 	function symbol() external view returns (string memory);
 
+	/// @notice Set or reaffirm the approved address for an NFT
+	/// @dev The zero address indicates there is no approved address.
+	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized
+	///  operator of the current owner.
+	/// @param approved The new substrate address approved NFT controller
+	/// @param tokenId The NFT to approve
+	/// @dev EVM selector for this function is: 0x0ecd0ab0,
+	///  or in textual repr: approveCross((address,uint256),uint256)
+	function approveCross(Tuple6 memory approved, uint256 tokenId) external;
+
 	/// @notice Transfer ownership of an NFT
 	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
 	///  is the zero address. Throws if `tokenId` is not a valid NFT.
@@ -378,6 +433,20 @@
 	///  or in textual repr: transfer(address,uint256)
 	function transfer(address to, uint256 tokenId) external;
 
+	/// @notice Transfer ownership of an NFT from cross account address to cross account address
+	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+	///  is the zero address. Throws if `tokenId` is not a valid NFT.
+	/// @param from Cross acccount address of current owner
+	/// @param to Cross acccount address of new owner
+	/// @param tokenId The NFT to transfer
+	/// @dev EVM selector for this function is: 0xd5cf430b,
+	///  or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+	function transferFromCross(
+		Tuple6 memory from,
+		Tuple6 memory to,
+		uint256 tokenId
+	) external;
+
 	/// @notice Burns a specific ERC721 token.
 	/// @dev Throws unless `msg.sender` is the current owner or an authorized
 	///  operator for this NFT. Throws if `from` is not the current owner. Throws
@@ -388,6 +457,16 @@
 	///  or in textual repr: burnFrom(address,uint256)
 	function burnFrom(address from, uint256 tokenId) external;
 
+	/// @notice Burns a specific ERC721 token.
+	/// @dev Throws unless `msg.sender` is the current owner or an authorized
+	///  operator for this NFT. Throws if `from` is not the current owner. Throws
+	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.
+	/// @param from The current owner of the NFT
+	/// @param tokenId The NFT to transfer
+	/// @dev EVM selector for this function is: 0xbb2f5a58,
+	///  or in textual repr: burnFromCross((address,uint256),uint256)
+	function burnFromCross(Tuple6 memory from, uint256 tokenId) external;
+
 	/// @notice Returns next free NFT ID.
 	/// @dev EVM selector for this function is: 0x75794a3c,
 	///  or in textual repr: nextTokenId()
@@ -408,16 +487,22 @@
 	// /// @param tokens array of pairs of token ID and token URI for minted tokens
 	// /// @dev EVM selector for this function is: 0x36543006,
 	// ///  or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
-	// function mintBulkWithTokenURI(address to, Tuple6[] memory tokens) external returns (bool);
+	// function mintBulkWithTokenURI(address to, Tuple8[] memory tokens) external returns (bool);
 
 }
 
 /// @dev anonymous struct
-struct Tuple6 {
+struct Tuple8 {
 	uint256 field_0;
 	string field_1;
 }
 
+/// @dev anonymous struct
+struct Tuple6 {
+	address field_0;
+	uint256 field_1;
+}
+
 /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 /// @dev See https://eips.ethereum.org/EIPS/eip-721
 /// @dev the ERC-165 identifier for this interface is 0x780e9d63
modifiedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -62,7 +62,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x3af103fb
+/// @dev the ERC-165 identifier for this interface is 0x25d897dc
 interface Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -98,6 +98,15 @@
 	///  or in textual repr: setCollectionSponsor(address)
 	function setCollectionSponsor(address sponsor) external;
 
+	/// Set the sponsor of the collection.
+	///
+	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+	///
+	/// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.
+	/// @dev EVM selector for this function is: 0x84a1d5a8,
+	///  or in textual repr: setCollectionSponsorCross((address,uint256))
+	function setCollectionSponsorCross(Tuple6 memory sponsor) external;
+
 	/// Whether there is a pending sponsor.
 	/// @dev EVM selector for this function is: 0x058ac185,
 	///  or in textual repr: hasCollectionPendingSponsor()
@@ -120,7 +129,7 @@
 	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
 	/// @dev EVM selector for this function is: 0x6ec0a9f1,
 	///  or in textual repr: collectionSponsor()
-	function collectionSponsor() external view returns (Tuple17 memory);
+	function collectionSponsor() external view returns (Tuple6 memory);
 
 	/// Set limits for the collection.
 	/// @dev Throws error if limit not found.
@@ -153,6 +162,18 @@
 	function contractAddress() external view returns (address);
 
 	/// Add collection admin.
+	/// @param newAdmin Cross account administrator address.
+	/// @dev EVM selector for this function is: 0x859aa7d6,
+	///  or in textual repr: addCollectionAdminCross((address,uint256))
+	function addCollectionAdminCross(Tuple6 memory newAdmin) external;
+
+	/// Remove collection admin.
+	/// @param admin Cross account administrator address.
+	/// @dev EVM selector for this function is: 0x6c0cd173,
+	///  or in textual repr: removeCollectionAdminCross((address,uint256))
+	function removeCollectionAdminCross(Tuple6 memory admin) external;
+
+	/// Add collection admin.
 	/// @param newAdmin Address of the added administrator.
 	/// @dev EVM selector for this function is: 0x92e462c7,
 	///  or in textual repr: addCollectionAdmin(address)
@@ -202,6 +223,13 @@
 	///  or in textual repr: addToCollectionAllowList(address)
 	function addToCollectionAllowList(address user) external;
 
+	/// Add user to allowed list.
+	///
+	/// @param user User cross account address.
+	/// @dev EVM selector for this function is: 0xa0184a3a,
+	///  or in textual repr: addToCollectionAllowListCross((address,uint256))
+	function addToCollectionAllowListCross(Tuple6 memory user) external;
+
 	/// Remove the user from the allowed list.
 	///
 	/// @param user Address of a removed user.
@@ -209,6 +237,13 @@
 	///  or in textual repr: removeFromCollectionAllowList(address)
 	function removeFromCollectionAllowList(address user) external;
 
+	/// Remove user from allowed list.
+	///
+	/// @param user User cross account address.
+	/// @dev EVM selector for this function is: 0x09ba452a,
+	///  or in textual repr: removeFromCollectionAllowListCross((address,uint256))
+	function removeFromCollectionAllowListCross(Tuple6 memory user) external;
+
 	/// Switch permission for minting.
 	///
 	/// @param mode Enable if "true".
@@ -224,6 +259,14 @@
 	///  or in textual repr: isOwnerOrAdmin(address)
 	function isOwnerOrAdmin(address user) external view returns (bool);
 
+	/// Check that account is the owner or admin of the collection
+	///
+	/// @param user User cross account to verify
+	/// @return "true" if account is the owner or admin
+	/// @dev EVM selector for this function is: 0x3e75a905,
+	///  or in textual repr: isOwnerOrAdminCross((address,uint256))
+	function isOwnerOrAdminCross(Tuple6 memory user) external view returns (bool);
+
 	/// Returns collection type
 	///
 	/// @return `Fungible` or `NFT` or `ReFungible`
@@ -233,11 +276,11 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuple with sponsor address and his substrate mirror.
+	/// @return Tuble with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
-	function collectionOwner() external view returns (Tuple17 memory);
+	function collectionOwner() external view returns (Tuple6 memory);
 
 	/// Changes collection owner to another account
 	///
@@ -253,13 +296,15 @@
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0x5813216b,
 	///  or in textual repr: collectionAdmins()
-	function collectionAdmins() external view returns (Tuple17[] memory);
-}
+	function collectionAdmins() external view returns (Tuple6[] memory);
 
-/// @dev anonymous struct
-struct Tuple17 {
-	address field_0;
-	uint256 field_1;
+	/// Changes collection owner to another account
+	///
+	/// @dev Owner can be changed only by current owner
+	/// @param newOwner new owner cross account
+	/// @dev EVM selector for this function is: 0xe5c9913f,
+	///  or in textual repr: setOwnerCross((address,uint256))
+	function setOwnerCross(Tuple6 memory newOwner) external;
 }
 
 /// @dev the ERC-165 identifier for this interface is 0x5b5e139f
@@ -355,7 +400,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0xef1eaacb
+/// @dev the ERC-165 identifier for this interface is 0x81feb398
 interface ERC721UniqueExtensions is Dummy, ERC165 {
 	/// @notice A descriptive name for a collection of NFTs in this contract
 	/// @dev EVM selector for this function is: 0x06fdde03,
@@ -377,6 +422,20 @@
 	///  or in textual repr: transfer(address,uint256)
 	function transfer(address to, uint256 tokenId) external;
 
+	/// @notice Transfer ownership of an RFT
+	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+	///  is the zero address. Throws if `tokenId` is not a valid RFT.
+	///  Throws if RFT pieces have multiple owners.
+	/// @param to The new owner
+	/// @param tokenId The RFT to transfer
+	/// @dev EVM selector for this function is: 0xd5cf430b,
+	///  or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+	function transferFromCross(
+		Tuple6 memory from,
+		Tuple6 memory to,
+		uint256 tokenId
+	) external;
+
 	/// @notice Burns a specific ERC721 token.
 	/// @dev Throws unless `msg.sender` is the current owner or an authorized
 	///  operator for this RFT. Throws if `from` is not the current owner. Throws
@@ -388,6 +447,17 @@
 	///  or in textual repr: burnFrom(address,uint256)
 	function burnFrom(address from, uint256 tokenId) external;
 
+	/// @notice Burns a specific ERC721 token.
+	/// @dev Throws unless `msg.sender` is the current owner or an authorized
+	///  operator for this RFT. Throws if `from` is not the current owner. Throws
+	///  if `to` is the zero address. Throws if `tokenId` is not a valid RFT.
+	///  Throws if RFT pieces have multiple owners.
+	/// @param from The current owner of the RFT
+	/// @param tokenId The RFT to transfer
+	/// @dev EVM selector for this function is: 0xbb2f5a58,
+	///  or in textual repr: burnFromCross((address,uint256),uint256)
+	function burnFromCross(Tuple6 memory from, uint256 tokenId) external;
+
 	/// @notice Returns next free RFT ID.
 	/// @dev EVM selector for this function is: 0x75794a3c,
 	///  or in textual repr: nextTokenId()
@@ -409,7 +479,7 @@
 	// /// @param tokens array of pairs of token ID and token URI for minted tokens
 	// /// @dev EVM selector for this function is: 0x36543006,
 	// ///  or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
-	// function mintBulkWithTokenURI(address to, Tuple6[] memory tokens) external returns (bool);
+	// function mintBulkWithTokenURI(address to, Tuple8[] memory tokens) external returns (bool);
 
 	/// Returns EVM address for refungible token
 	///
@@ -420,11 +490,17 @@
 }
 
 /// @dev anonymous struct
-struct Tuple6 {
+struct Tuple8 {
 	uint256 field_0;
 	string field_1;
 }
 
+/// @dev anonymous struct
+struct Tuple6 {
+	address field_0;
+	uint256 field_1;
+}
+
 /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 /// @dev See https://eips.ethereum.org/EIPS/eip-721
 /// @dev the ERC-165 identifier for this interface is 0x780e9d63
modifiedtests/src/eth/base.test.tsdiffbeforeafterboth
--- a/tests/src/eth/base.test.ts
+++ b/tests/src/eth/base.test.ts
@@ -116,8 +116,8 @@
     await checkInterface(helper, '0x780e9d63', true, true);
   });
 
-  itEth('ERC721UniqueExtensions - 0x4468500d - support', async ({helper}) => {
-    await checkInterface(helper, '0x4468500d', true, true);
+  itEth('ERC721UniqueExtensions - 0x244543ee - support', async ({helper}) => {
+    await checkInterface(helper, '0x244543ee', true, true);
   });
 
   itEth('ERC721Burnable - 0x42966c68 - support', async ({helper}) => {
modifiedtests/src/eth/collectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collectionAdmin.test.ts
+++ b/tests/src/eth/collectionAdmin.test.ts
@@ -50,17 +50,18 @@
       .to.be.eq(newAdmin.toLocaleLowerCase());
   });
 
-  itEth.skip('Add substrate admin by owner', async ({helper}) => {
+  itEth('Add cross account admin by owner', async ({helper, privateKey}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
+        
     const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
-
-    const [newAdmin] = await helper.arrange.createAccounts([10n], donor);
-    await collectionEvm.methods.addCollectionAdminSubstrate(newAdmin.addressRaw).send();
+    
+    const newAdmin = await privateKey('//Bob');
+    const newAdminCross = helper.ethCrossAccount.fromKeyringPair(newAdmin);
+    await collectionEvm.methods.addCollectionAdminCross(newAdminCross).send();
 
-    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);
-    expect(adminList[0].asSubstrate.toString().toLocaleLowerCase())
-      .to.be.eq(newAdmin.address.toLocaleLowerCase());
+    const adminList = await helper.collection.getAdmins(collectionId);
+    expect(adminList).to.be.like([{Substrate: newAdmin.address}]);
   });
 
   itEth('Verify owner or admin', async ({helper}) => {
modifiedtests/src/eth/fractionalizer/Fractionalizer.soldiffbeforeafterboth
--- a/tests/src/eth/fractionalizer/Fractionalizer.sol
+++ b/tests/src/eth/fractionalizer/Fractionalizer.sol
@@ -84,7 +84,11 @@
 	) external payable onlyOwner {
 		require(rftCollection == address(0), "RFT collection is already set");
 		address collectionHelpers = 0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F;
-		rftCollection = CollectionHelpers(collectionHelpers).createRFTCollection{value: msg.value}(_name, _description, _tokenPrefix);
+		rftCollection = CollectionHelpers(collectionHelpers).createRFTCollection{value: msg.value}(
+			_name,
+			_description,
+			_tokenPrefix
+		);
 		emit RFTCollectionSet(rftCollection);
 	}
 
modifiedtests/src/eth/fungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/fungible.test.ts
+++ b/tests/src/eth/fungible.test.ts
@@ -148,6 +148,49 @@
     }
   });
 
+  itEth('Can perform burnFromCross()', async ({helper, privateKey}) => {
+    const alice = await privateKey('//Alice');
+    const sender = await helper.eth.createAccountWithBalance(alice, 100n);
+
+    const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);
+
+    await collection.mint(alice, 200n, {Substrate: alice.address});
+    await collection.approveTokens(alice, {Ethereum: sender}, 100n);
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'ft');
+
+    const fromBalanceBefore = await collection.getBalance({Substrate: alice.address});
+    
+    const ownerCross = helper.ethCrossAccount.fromKeyringPair(alice);
+    const result = await contract.methods.burnFromCross(ownerCross, 49).send({from: sender});
+    const events = result.events;
+
+    expect(events).to.be.like({
+      Transfer: {
+        address: helper.ethAddress.fromCollectionId(collection.collectionId),
+        event: 'Transfer',
+        returnValues: {
+          from: helper.address.substrateToEth(alice.address),
+          to: '0x0000000000000000000000000000000000000000',
+          value: '49',
+        },
+      },
+      Approval: {
+        address: helper.ethAddress.fromCollectionId(collection.collectionId),
+        returnValues: {
+          owner: helper.address.substrateToEth(alice.address),
+          spender: sender,
+          value: '51',
+        },
+        event: 'Approval',
+      },
+    });
+
+    const fromBalanceAfter = await collection.getBalance({Substrate: alice.address});
+    expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(49n);
+  });
+
   itEth('Can perform transferFrom()', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const spender = await helper.eth.createAccountWithBalance(donor);
@@ -216,6 +259,54 @@
       expect(+balance).to.equal(50);
     }
   });
+
+  itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {
+    const alice = await privateKey('//Alice');
+    const sender = await helper.eth.createAccountWithBalance(alice, 100n);
+
+    const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);
+
+    const receiver = helper.eth.createAccount();
+
+    await collection.mint(alice, 200n, {Substrate: alice.address});
+    await collection.approveTokens(alice, {Ethereum: sender}, 100n);
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'ft');
+
+    const from = helper.ethCrossAccount.fromKeyringPair(alice);
+    const to = helper.ethCrossAccount.fromAddress(receiver);
+
+    const fromBalanceBefore = await collection.getBalance({Substrate: alice.address});
+    const toBalanceBefore = await collection.getBalance({Ethereum: receiver});
+    
+    const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});
+
+    expect(result.events).to.be.like({
+      Transfer: {
+        address,
+        event: 'Transfer',
+        returnValues: {
+          from: helper.address.substrateToEth(alice.address),
+          to: receiver,
+          value: '51',
+        },
+      },
+      Approval: {
+        address,
+        event: 'Approval',
+        returnValues: {
+          owner: helper.address.substrateToEth(alice.address),
+          spender: sender,
+          value: '49',
+        },
+      }});
+
+    const fromBalanceAfter = await collection.getBalance({Substrate: alice.address});
+    expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(51n);
+    const toBalanceAfter = await collection.getBalance({Ethereum: receiver});
+    expect(toBalanceAfter - toBalanceBefore).to.be.eq(51n);
+  });
 });
 
 describe('Fungible: Fees', () => {
@@ -362,4 +453,44 @@
     expect(event.returnValues.to).to.be.equal(receiver);
     expect(event.returnValues.value).to.be.equal('51');
   });
+
+  itEth('Events emitted for transferFromCross()', async ({helper, privateKey}) => {
+    const alice = await privateKey('//Alice');
+    const sender = await helper.eth.createAccountWithBalance(alice, 100n);
+
+    const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);
+
+    const receiver = helper.eth.createAccount();
+
+    await collection.mint(alice, 200n, {Substrate: alice.address});
+    await collection.approveTokens(alice, {Ethereum: sender}, 100n);
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'ft');
+
+    const from = helper.ethCrossAccount.fromKeyringPair(alice);
+    const to = helper.ethCrossAccount.fromAddress(receiver);
+    
+    const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});
+
+    expect(result.events).to.be.like({
+      Transfer: {
+        address,
+        event: 'Transfer',
+        returnValues: {
+          from: helper.address.substrateToEth(alice.address),
+          to: receiver,
+          value: '51',
+        },
+      },
+      Approval: {
+        address,
+        event: 'Approval',
+        returnValues: {
+          owner: helper.address.substrateToEth(alice.address),
+          spender: sender,
+          value: '49',
+        },
+      }});
+  });
 });
modifiedtests/src/eth/fungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/fungibleAbi.json
+++ b/tests/src/eth/fungibleAbi.json
@@ -60,6 +60,23 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "newAdmin",
+        "type": "tuple"
+      }
+    ],
+    "name": "addCollectionAdminCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "user", "type": "address" }
     ],
     "name": "addToCollectionAllowList",
@@ -69,6 +86,23 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "user",
+        "type": "tuple"
+      }
+    ],
+    "name": "addToCollectionAllowListCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "owner", "type": "address" },
       { "internalType": "address", "name": "spender", "type": "address" }
     ],
@@ -98,6 +132,24 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "spender",
+        "type": "tuple"
+      },
+      { "internalType": "uint256", "name": "amount", "type": "uint256" }
+    ],
+    "name": "approveCross",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "owner", "type": "address" }
     ],
     "name": "balanceOf",
@@ -117,6 +169,24 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "from",
+        "type": "tuple"
+      },
+      { "internalType": "uint256", "name": "amount", "type": "uint256" }
+    ],
+    "name": "burnFromCross",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "newOwner", "type": "address" }
     ],
     "name": "changeCollectionOwner",
@@ -228,6 +298,23 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "user",
+        "type": "tuple"
+      }
+    ],
+    "name": "isOwnerOrAdminCross",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "to", "type": "address" },
       { "internalType": "uint256", "name": "amount", "type": "uint256" }
     ],
@@ -270,6 +357,23 @@
     "type": "function"
   },
   {
+    "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "admin",
+        "type": "tuple"
+      }
+    ],
+    "name": "removeCollectionAdminCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
     "inputs": [],
     "name": "removeCollectionSponsor",
     "outputs": [],
@@ -286,6 +390,23 @@
     "type": "function"
   },
   {
+    "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "user",
+        "type": "tuple"
+      }
+    ],
+    "name": "removeFromCollectionAllowListCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
     "inputs": [{ "internalType": "uint8", "name": "mode", "type": "uint8" }],
     "name": "setCollectionAccess",
     "outputs": [],
@@ -361,6 +482,40 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "sponsor",
+        "type": "tuple"
+      }
+    ],
+    "name": "setCollectionSponsorCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "newOwner",
+        "type": "tuple"
+      }
+    ],
+    "name": "setOwnerCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }
     ],
     "name": "supportsInterface",
@@ -404,6 +559,33 @@
     "type": "function"
   },
   {
+    "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "from",
+        "type": "tuple"
+      },
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "to",
+        "type": "tuple"
+      },
+      { "internalType": "uint256", "name": "amount", "type": "uint256" }
+    ],
+    "name": "transferFromCross",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
     "inputs": [],
     "name": "uniqueCollectionType",
     "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -246,6 +246,64 @@
     }
   });
 
+  itEth('Can perform burnFromCross()', async ({helper, privateKey}) => {
+    const alice = await privateKey('//Alice');
+    const collection = await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+    const owner = await privateKey('//Bob');
+    const spender = await helper.eth.createAccountWithBalance(alice, 100n);
+
+    const token = await collection.mintToken(alice, {Substrate: owner.address});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'nft');
+
+    {
+      await token.approve(owner, {Ethereum: spender});
+      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);
+      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender});
+      const events = result.events.Transfer;
+
+      expect(events).to.be.like({
+        address,
+        event: 'Transfer',
+        returnValues: {
+          from: helper.address.substrateToEth(owner.address),
+          to: '0x0000000000000000000000000000000000000000',
+          tokenId: token.tokenId.toString(),
+        },
+      });
+    }
+  });
+
+  itEth('Can perform approveCross()', async ({helper, privateKey}) => {
+    const alice = await privateKey('//Alice');
+    const collection = await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+    const owner = await helper.eth.createAccountWithBalance(alice, 100n);
+    const receiver = await privateKey('//Charlie');
+
+    const token = await collection.mintToken(alice, {Ethereum: owner});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'nft');
+
+    {
+      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);
+      const result = await contract.methods.approveCross(recieverCross, token.tokenId).send({from: owner});
+      const event = result.events.Approval;
+      expect(event).to.be.like({
+        address: helper.ethAddress.fromCollectionId(collection.collectionId),
+        event: 'Approval',
+        returnValues: {
+          owner,
+          approved: helper.address.substrateToEth(receiver.address),
+          tokenId: token.tokenId.toString(),
+        },
+      });
+    }
+  });
+
   itEth('Can perform transferFrom()', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const spender = await helper.eth.createAccountWithBalance(donor);
@@ -351,6 +409,40 @@
     expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));
   });
 
+  itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {
+    const alice = await privateKey('//Alice');
+    const collection = await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+    const owner = await privateKey('//Bob');
+    const spender = await helper.eth.createAccountWithBalance(alice, 100n);
+    const receiver = await privateKey('//Charlie');
+
+    const token = await collection.mintToken(alice, {Substrate: owner.address});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'nft');
+
+    await token.approve(owner, {Ethereum: spender});
+
+    {
+      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);
+      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);
+      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});
+      const event = result.events.Transfer;
+      expect(event).to.be.like({
+        address: helper.ethAddress.fromCollectionId(collection.collectionId),
+        event: 'Transfer',
+        returnValues: {
+          from: helper.address.substrateToEth(owner.address),
+          to: helper.address.substrateToEth(receiver.address),
+          tokenId: token.tokenId.toString(),
+        },
+      });
+    }
+
+    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});
+  });
+
   itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
modifiedtests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -91,6 +91,23 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "newAdmin",
+        "type": "tuple"
+      }
+    ],
+    "name": "addCollectionAdminCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "user", "type": "address" }
     ],
     "name": "addToCollectionAllowList",
@@ -100,6 +117,23 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "user",
+        "type": "tuple"
+      }
+    ],
+    "name": "addToCollectionAllowListCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "user", "type": "address" }
     ],
     "name": "allowed",
@@ -119,6 +153,24 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "approved",
+        "type": "tuple"
+      },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "approveCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "owner", "type": "address" }
     ],
     "name": "balanceOf",
@@ -147,6 +199,24 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "from",
+        "type": "tuple"
+      },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "burnFromCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "newOwner", "type": "address" }
     ],
     "name": "changeCollectionOwner",
@@ -163,7 +233,7 @@
           { "internalType": "address", "name": "field_0", "type": "address" },
           { "internalType": "uint256", "name": "field_1", "type": "uint256" }
         ],
-        "internalType": "struct Tuple17[]",
+        "internalType": "struct Tuple6[]",
         "name": "",
         "type": "tuple[]"
       }
@@ -180,7 +250,7 @@
           { "internalType": "address", "name": "field_0", "type": "address" },
           { "internalType": "uint256", "name": "field_1", "type": "uint256" }
         ],
-        "internalType": "struct Tuple17",
+        "internalType": "struct Tuple6",
         "name": "",
         "type": "tuple"
       }
@@ -204,7 +274,7 @@
           { "internalType": "address", "name": "field_0", "type": "address" },
           { "internalType": "uint256", "name": "field_1", "type": "uint256" }
         ],
-        "internalType": "struct Tuple17",
+        "internalType": "struct Tuple6",
         "name": "",
         "type": "tuple"
       }
@@ -286,6 +356,23 @@
     "type": "function"
   },
   {
+    "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "user",
+        "type": "tuple"
+      }
+    ],
+    "name": "isOwnerOrAdminCross",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
     "inputs": [{ "internalType": "address", "name": "to", "type": "address" }],
     "name": "mint",
     "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
@@ -352,6 +439,23 @@
     "type": "function"
   },
   {
+    "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "admin",
+        "type": "tuple"
+      }
+    ],
+    "name": "removeCollectionAdminCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
     "inputs": [],
     "name": "removeCollectionSponsor",
     "outputs": [],
@@ -369,6 +473,23 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "user",
+        "type": "tuple"
+      }
+    ],
+    "name": "removeFromCollectionAllowListCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "from", "type": "address" },
       { "internalType": "address", "name": "to", "type": "address" },
       { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
@@ -476,6 +597,40 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "sponsor",
+        "type": "tuple"
+      }
+    ],
+    "name": "setCollectionSponsorCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "newOwner",
+        "type": "tuple"
+      }
+    ],
+    "name": "setOwnerCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "uint256", "name": "tokenId", "type": "uint256" },
       { "internalType": "string", "name": "key", "type": "string" },
       { "internalType": "bytes", "name": "value", "type": "bytes" }
@@ -570,6 +725,33 @@
     "type": "function"
   },
   {
+    "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "from",
+        "type": "tuple"
+      },
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "to",
+        "type": "tuple"
+      },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "transferFromCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
     "inputs": [],
     "name": "uniqueCollectionType",
     "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
modifiedtests/src/eth/reFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -227,6 +227,108 @@
     }
   });
 
+  itEth('Can perform burnFrom()', async ({helper, privateKey}) => {
+    const alice = await privateKey('//Alice');
+    const collection = await helper.rft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+    const owner = await helper.eth.createAccountWithBalance(alice, 100n);
+    const spender = await helper.eth.createAccountWithBalance(alice, 100n);
+
+    const token = await collection.mintToken(alice, 100n, {Ethereum: owner});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'rft');
+
+    const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, token.tokenId);
+    const tokenContract = helper.ethNativeContract.rftToken(tokenAddress, owner);
+    await tokenContract.methods.repartition(15).send();
+    await tokenContract.methods.approve(spender, 15).send();
+
+    {
+      const result = await contract.methods.burnFrom(owner, token.tokenId).send({from: spender});
+      const event = result.events.Transfer;
+      expect(event).to.be.like({
+        address: helper.ethAddress.fromCollectionId(collection.collectionId),
+        event: 'Transfer',
+        returnValues: {
+          from: owner,
+          to: '0x0000000000000000000000000000000000000000',
+          tokenId: token.tokenId.toString(),
+        },
+      });
+    }
+
+    expect(await collection.getTokenBalance(token.tokenId, {Ethereum: owner})).to.be.eq(0n);
+  });
+
+  itEth('Can perform burnFromCross()', async ({helper, privateKey}) => {
+    const alice = await privateKey('//Alice');
+    const collection = await helper.rft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+    const owner = await privateKey('//Bob');
+    const spender = await helper.eth.createAccountWithBalance(alice, 100n);
+
+    const token = await collection.mintToken(alice, 100n, {Substrate: owner.address});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'rft');
+
+    await token.repartition(owner, 15n);
+    await token.approve(owner, {Ethereum: spender}, 15n);
+
+    {
+      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);
+      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender});
+      const event = result.events.Transfer;
+      expect(event).to.be.like({
+        address: helper.ethAddress.fromCollectionId(collection.collectionId),
+        event: 'Transfer',
+        returnValues: {
+          from: helper.address.substrateToEth(owner.address),
+          to: '0x0000000000000000000000000000000000000000',
+          tokenId: token.tokenId.toString(),
+        },
+      });
+    }
+
+    expect(await collection.getTokenBalance(token.tokenId, {Substrate: owner.address})).to.be.eq(0n);
+  });
+
+  itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {
+    const alice = await privateKey('//Alice');
+    const collection = await helper.rft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+    const owner = await privateKey('//Bob');
+    const spender = await helper.eth.createAccountWithBalance(alice, 100n);
+    const receiver = await privateKey('//Charlie');
+
+    const token = await collection.mintToken(alice, 100n, {Substrate: owner.address});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'rft');
+
+    await token.repartition(owner, 15n);
+    await token.approve(owner, {Ethereum: spender}, 15n);
+
+    {
+      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);
+      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);
+      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});
+      const event = result.events.Transfer;
+      expect(event).to.be.like({
+        address: helper.ethAddress.fromCollectionId(collection.collectionId),
+        event: 'Transfer',
+        returnValues: {
+          from: helper.address.substrateToEth(owner.address),
+          to: helper.address.substrateToEth(receiver.address),
+          tokenId: token.tokenId.toString(),
+        },
+      });
+    }
+
+    expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);
+  });
+
   itEth('Can perform transfer()', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
modifiedtests/src/eth/reFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/reFungibleAbi.json
+++ b/tests/src/eth/reFungibleAbi.json
@@ -91,6 +91,23 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "newAdmin",
+        "type": "tuple"
+      }
+    ],
+    "name": "addCollectionAdminCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "user", "type": "address" }
     ],
     "name": "addToCollectionAllowList",
@@ -100,6 +117,23 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "user",
+        "type": "tuple"
+      }
+    ],
+    "name": "addToCollectionAllowListCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "user", "type": "address" }
     ],
     "name": "allowed",
@@ -147,6 +181,24 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "from",
+        "type": "tuple"
+      },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "burnFromCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "newOwner", "type": "address" }
     ],
     "name": "changeCollectionOwner",
@@ -163,7 +215,7 @@
           { "internalType": "address", "name": "field_0", "type": "address" },
           { "internalType": "uint256", "name": "field_1", "type": "uint256" }
         ],
-        "internalType": "struct Tuple17[]",
+        "internalType": "struct Tuple6[]",
         "name": "",
         "type": "tuple[]"
       }
@@ -180,7 +232,7 @@
           { "internalType": "address", "name": "field_0", "type": "address" },
           { "internalType": "uint256", "name": "field_1", "type": "uint256" }
         ],
-        "internalType": "struct Tuple17",
+        "internalType": "struct Tuple6",
         "name": "",
         "type": "tuple"
       }
@@ -204,7 +256,7 @@
           { "internalType": "address", "name": "field_0", "type": "address" },
           { "internalType": "uint256", "name": "field_1", "type": "uint256" }
         ],
-        "internalType": "struct Tuple17",
+        "internalType": "struct Tuple6",
         "name": "",
         "type": "tuple"
       }
@@ -286,6 +338,23 @@
     "type": "function"
   },
   {
+    "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "user",
+        "type": "tuple"
+      }
+    ],
+    "name": "isOwnerOrAdminCross",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
     "inputs": [{ "internalType": "address", "name": "to", "type": "address" }],
     "name": "mint",
     "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
@@ -352,6 +421,23 @@
     "type": "function"
   },
   {
+    "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "admin",
+        "type": "tuple"
+      }
+    ],
+    "name": "removeCollectionAdminCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
     "inputs": [],
     "name": "removeCollectionSponsor",
     "outputs": [],
@@ -369,6 +455,23 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "user",
+        "type": "tuple"
+      }
+    ],
+    "name": "removeFromCollectionAllowListCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "from", "type": "address" },
       { "internalType": "address", "name": "to", "type": "address" },
       { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
@@ -476,6 +579,40 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "sponsor",
+        "type": "tuple"
+      }
+    ],
+    "name": "setCollectionSponsorCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "newOwner",
+        "type": "tuple"
+      }
+    ],
+    "name": "setOwnerCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "uint256", "name": "tokenId", "type": "uint256" },
       { "internalType": "string", "name": "key", "type": "string" },
       { "internalType": "bytes", "name": "value", "type": "bytes" }
@@ -579,6 +716,33 @@
     "type": "function"
   },
   {
+    "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "from",
+        "type": "tuple"
+      },
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6",
+        "name": "to",
+        "type": "tuple"
+      },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "transferFromCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
     "inputs": [],
     "name": "uniqueCollectionType",
     "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
modifiedtests/src/eth/util/playgrounds/types.tsdiffbeforeafterboth
--- a/tests/src/eth/util/playgrounds/types.ts
+++ b/tests/src/eth/util/playgrounds/types.ts
@@ -13,3 +13,9 @@
   event: string,
   args: { [key: string]: string }
 };
+export interface TEthCrossAccount {
+  readonly 0: string,
+  readonly 1: string | Uint8Array,
+  readonly field_0: string,
+  readonly field_1: string | Uint8Array,
+}
modifiedtests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -18,7 +18,7 @@
 
 import {DevUniqueHelper} from '../../../util/playgrounds/unique.dev';
 
-import {ContractImports, CompiledContract, NormalizedEvent} from './types';
+import {ContractImports, CompiledContract, TEthCrossAccount, NormalizedEvent} from './types';
 
 // Native contracts ABI
 import collectionHelpersAbi from '../../collectionHelpersAbi.json';
@@ -343,6 +343,26 @@
 
 export type EthUniqueHelperConstructor = new (...args: any[]) => EthUniqueHelper;
 
+export class EthCrossAccountGroup extends EthGroupBase {
+  fromAddress(address: TEthereumAccount): TEthCrossAccount {
+    return {
+      0: address,
+      1: '0',
+      field_0: address,
+      field_1: '0',
+    };
+  }
+
+  fromKeyringPair(keyring: IKeyringPair): TEthCrossAccount {
+    return {
+      0: '0x0000000000000000000000000000000000000000',
+      1: keyring.addressRaw,
+      field_0: '0x0000000000000000000000000000000000000000',
+      field_1: keyring.addressRaw,
+    };
+  }
+}
+
 export class EthUniqueHelper extends DevUniqueHelper {
   web3: Web3 | null = null;
   web3Provider: WebsocketProvider | null = null;
@@ -351,6 +371,7 @@
   ethAddress: EthAddressGroup;
   ethNativeContract: NativeContractGroup;
   ethContract: ContractGroup;
+  ethCrossAccount: EthCrossAccountGroup;
 
   constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
     options.helperBase = options.helperBase ?? EthUniqueHelper;
@@ -358,6 +379,7 @@
     super(logger, options);
     this.eth = new EthGroup(this);
     this.ethAddress = new EthAddressGroup(this);
+    this.ethCrossAccount = new EthCrossAccountGroup(this);
     this.ethNativeContract = new NativeContractGroup(this);
     this.ethContract = new ContractGroup(this);
   }