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
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -35,6 +35,7 @@
 use pallet_common::{
 	erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},
 	CollectionHandle, CollectionPropertyPermissions,
+	eth::convert_tuple_to_cross_account,
 };
 use pallet_evm::{account::CrossAccountId, PrecompileHandle};
 use pallet_evm_coder_substrate::call;
@@ -198,7 +199,10 @@
 /// @title ERC-721 Non-Fungible Token Standard, optional metadata extension
 /// @dev See https://eips.ethereum.org/EIPS/eip-721
 #[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]
-impl<T: Config> NonfungibleHandle<T> {
+impl<T: Config> NonfungibleHandle<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")]
@@ -603,7 +607,10 @@
 
 /// @title Unique extensions for ERC721.
 #[solidity_interface(name = ERC721UniqueExtensions)]
-impl<T: Config> NonfungibleHandle<T> {
+impl<T: Config> NonfungibleHandle<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())
@@ -616,6 +623,28 @@
 		Ok(string::from_utf8_lossy(&self.token_prefix).into())
 	}
 
+	/// @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
+	#[weight(<SelfWeightOf<T>>::approve())]
+	fn approve_cross(
+		&mut self,
+		caller: caller,
+		approved: (address, uint256),
+		token_id: uint256,
+	) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let approved = convert_tuple_to_cross_account::<T>(approved)?;
+		let token = token_id.try_into()?;
+
+		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
 	/// @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.
@@ -634,6 +663,32 @@
 		Ok(())
 	}
 
+	/// @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
+	#[weight(<SelfWeightOf<T>>::transfer())]
+	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());
+		Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &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 NFT. Throws if `from` is not the current owner. Throws
@@ -654,6 +709,31 @@
 		Ok(())
 	}
 
+	/// @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
+	#[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());
+
+		<Pallet<T>>::burn_from(self, &caller, &from, token, &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
 	/// @notice Returns next free NFT ID.
 	fn next_token_id(&self) -> Result<uint256> {
 		self.consume_store_reads(1)?;
modifiedpallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
before · pallets/nonfungible/src/stubs/UniqueNFT.sol
1// SPDX-License-Identifier: OTHER2// This code is automatically generated34pragma solidity >=0.8.0 <0.9.0;56/// @dev common stubs holder7contract Dummy {8	uint8 dummy;9	string stub_error = "this contract is implemented in native";10}1112contract ERC165 is Dummy {13	function supportsInterface(bytes4 interfaceID) external view returns (bool) {14		require(false, stub_error);15		interfaceID;16		return true;17	}18}1920/// @title A contract that allows to set and delete token properties and change token property permissions.21/// @dev the ERC-165 identifier for this interface is 0x4136937722contract TokenProperties is Dummy, ERC165 {23	/// @notice Set permissions for token property.24	/// @dev Throws error if `msg.sender` is not admin or owner of the collection.25	/// @param key Property key.26	/// @param isMutable Permission to mutate property.27	/// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.28	/// @param tokenOwner Permission to mutate property by token owner if property is mutable.29	/// @dev EVM selector for this function is: 0x222d97fa,30	///  or in textual repr: setTokenPropertyPermission(string,bool,bool,bool)31	function setTokenPropertyPermission(32		string memory key,33		bool isMutable,34		bool collectionAdmin,35		bool tokenOwner36	) public {37		require(false, stub_error);38		key;39		isMutable;40		collectionAdmin;41		tokenOwner;42		dummy = 0;43	}4445	/// @notice Set token property value.46	/// @dev Throws error if `msg.sender` has no permission to edit the property.47	/// @param tokenId ID of the token.48	/// @param key Property key.49	/// @param value Property value.50	/// @dev EVM selector for this function is: 0x1752d67b,51	///  or in textual repr: setProperty(uint256,string,bytes)52	function setProperty(53		uint256 tokenId,54		string memory key,55		bytes memory value56	) public {57		require(false, stub_error);58		tokenId;59		key;60		value;61		dummy = 0;62	}6364	/// @notice Delete token property value.65	/// @dev Throws error if `msg.sender` has no permission to edit the property.66	/// @param tokenId ID of the token.67	/// @param key Property key.68	/// @dev EVM selector for this function is: 0x066111d1,69	///  or in textual repr: deleteProperty(uint256,string)70	function deleteProperty(uint256 tokenId, string memory key) public {71		require(false, stub_error);72		tokenId;73		key;74		dummy = 0;75	}7677	/// @notice Get token property value.78	/// @dev Throws error if key not found79	/// @param tokenId ID of the token.80	/// @param key Property key.81	/// @return Property value bytes82	/// @dev EVM selector for this function is: 0x7228c327,83	///  or in textual repr: property(uint256,string)84	function property(uint256 tokenId, string memory key) public view returns (bytes memory) {85		require(false, stub_error);86		tokenId;87		key;88		dummy;89		return hex"";90	}91}9293/// @title A contract that allows you to work with collections.94/// @dev the ERC-165 identifier for this interface is 0x3af103fb95contract Collection is Dummy, ERC165 {96	/// Set collection property.97	///98	/// @param key Property key.99	/// @param value Propery value.100	/// @dev EVM selector for this function is: 0x2f073f66,101	///  or in textual repr: setCollectionProperty(string,bytes)102	function setCollectionProperty(string memory key, bytes memory value) public {103		require(false, stub_error);104		key;105		value;106		dummy = 0;107	}108109	/// Delete collection property.110	///111	/// @param key Property key.112	/// @dev EVM selector for this function is: 0x7b7debce,113	///  or in textual repr: deleteCollectionProperty(string)114	function deleteCollectionProperty(string memory key) public {115		require(false, stub_error);116		key;117		dummy = 0;118	}119120	/// Get collection property.121	///122	/// @dev Throws error if key not found.123	///124	/// @param key Property key.125	/// @return bytes The property corresponding to the key.126	/// @dev EVM selector for this function is: 0xcf24fd6d,127	///  or in textual repr: collectionProperty(string)128	function collectionProperty(string memory key) public view returns (bytes memory) {129		require(false, stub_error);130		key;131		dummy;132		return hex"";133	}134135	/// Set the sponsor of the collection.136	///137	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.138	///139	/// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.140	/// @dev EVM selector for this function is: 0x7623402e,141	///  or in textual repr: setCollectionSponsor(address)142	function setCollectionSponsor(address sponsor) public {143		require(false, stub_error);144		sponsor;145		dummy = 0;146	}147148	/// Whether there is a pending sponsor.149	/// @dev EVM selector for this function is: 0x058ac185,150	///  or in textual repr: hasCollectionPendingSponsor()151	function hasCollectionPendingSponsor() public view returns (bool) {152		require(false, stub_error);153		dummy;154		return false;155	}156157	/// Collection sponsorship confirmation.158	///159	/// @dev After setting the sponsor for the collection, it must be confirmed with this function.160	/// @dev EVM selector for this function is: 0x3c50e97a,161	///  or in textual repr: confirmCollectionSponsorship()162	function confirmCollectionSponsorship() public {163		require(false, stub_error);164		dummy = 0;165	}166167	/// Remove collection sponsor.168	/// @dev EVM selector for this function is: 0x6e0326a3,169	///  or in textual repr: removeCollectionSponsor()170	function removeCollectionSponsor() public {171		require(false, stub_error);172		dummy = 0;173	}174175	/// Get current sponsor.176	///177	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.178	/// @dev EVM selector for this function is: 0x6ec0a9f1,179	///  or in textual repr: collectionSponsor()180	function collectionSponsor() public view returns (Tuple17 memory) {181		require(false, stub_error);182		dummy;183		return Tuple17(0x0000000000000000000000000000000000000000, 0);184	}185186	/// Set limits for the collection.187	/// @dev Throws error if limit not found.188	/// @param limit Name of the limit. Valid names:189	/// 	"accountTokenOwnershipLimit",190	/// 	"sponsoredDataSize",191	/// 	"sponsoredDataRateLimit",192	/// 	"tokenLimit",193	/// 	"sponsorTransferTimeout",194	/// 	"sponsorApproveTimeout"195	/// @param value Value of the limit.196	/// @dev EVM selector for this function is: 0x6a3841db,197	///  or in textual repr: setCollectionLimit(string,uint32)198	function setCollectionLimit(string memory limit, uint32 value) public {199		require(false, stub_error);200		limit;201		value;202		dummy = 0;203	}204205	/// Set limits for the collection.206	/// @dev Throws error if limit not found.207	/// @param limit Name of the limit. Valid names:208	/// 	"ownerCanTransfer",209	/// 	"ownerCanDestroy",210	/// 	"transfersEnabled"211	/// @param value Value of the limit.212	/// @dev EVM selector for this function is: 0x993b7fba,213	///  or in textual repr: setCollectionLimit(string,bool)214	function setCollectionLimit(string memory limit, bool value) public {215		require(false, stub_error);216		limit;217		value;218		dummy = 0;219	}220221	/// Get contract address.222	/// @dev EVM selector for this function is: 0xf6b4dfb4,223	///  or in textual repr: contractAddress()224	function contractAddress() public view returns (address) {225		require(false, stub_error);226		dummy;227		return 0x0000000000000000000000000000000000000000;228	}229230	/// Add collection admin.231	/// @param newAdmin Address of the added administrator.232	/// @dev EVM selector for this function is: 0x92e462c7,233	///  or in textual repr: addCollectionAdmin(address)234	function addCollectionAdmin(address newAdmin) public {235		require(false, stub_error);236		newAdmin;237		dummy = 0;238	}239240	/// Remove collection admin.241	///242	/// @param admin Address of the removed administrator.243	/// @dev EVM selector for this function is: 0xfafd7b42,244	///  or in textual repr: removeCollectionAdmin(address)245	function removeCollectionAdmin(address admin) public {246		require(false, stub_error);247		admin;248		dummy = 0;249	}250251	/// Toggle accessibility of collection nesting.252	///253	/// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'254	/// @dev EVM selector for this function is: 0x112d4586,255	///  or in textual repr: setCollectionNesting(bool)256	function setCollectionNesting(bool enable) public {257		require(false, stub_error);258		enable;259		dummy = 0;260	}261262	/// Toggle accessibility of collection nesting.263	///264	/// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'265	/// @param collections Addresses of collections that will be available for nesting.266	/// @dev EVM selector for this function is: 0x64872396,267	///  or in textual repr: setCollectionNesting(bool,address[])268	function setCollectionNesting(bool enable, address[] memory collections) public {269		require(false, stub_error);270		enable;271		collections;272		dummy = 0;273	}274275	/// Set the collection access method.276	/// @param mode Access mode277	/// 	0 for Normal278	/// 	1 for AllowList279	/// @dev EVM selector for this function is: 0x41835d4c,280	///  or in textual repr: setCollectionAccess(uint8)281	function setCollectionAccess(uint8 mode) public {282		require(false, stub_error);283		mode;284		dummy = 0;285	}286287	/// Checks that user allowed to operate with collection.288	///289	/// @param user User address to check.290	/// @dev EVM selector for this function is: 0xd63a8e11,291	///  or in textual repr: allowed(address)292	function allowed(address user) public view returns (bool) {293		require(false, stub_error);294		user;295		dummy;296		return false;297	}298299	/// Add the user to the allowed list.300	///301	/// @param user Address of a trusted user.302	/// @dev EVM selector for this function is: 0x67844fe6,303	///  or in textual repr: addToCollectionAllowList(address)304	function addToCollectionAllowList(address user) public {305		require(false, stub_error);306		user;307		dummy = 0;308	}309310	/// Remove the user from the allowed list.311	///312	/// @param user Address of a removed user.313	/// @dev EVM selector for this function is: 0x85c51acb,314	///  or in textual repr: removeFromCollectionAllowList(address)315	function removeFromCollectionAllowList(address user) public {316		require(false, stub_error);317		user;318		dummy = 0;319	}320321	/// Switch permission for minting.322	///323	/// @param mode Enable if "true".324	/// @dev EVM selector for this function is: 0x00018e84,325	///  or in textual repr: setCollectionMintMode(bool)326	function setCollectionMintMode(bool mode) public {327		require(false, stub_error);328		mode;329		dummy = 0;330	}331332	/// Check that account is the owner or admin of the collection333	///334	/// @param user account to verify335	/// @return "true" if account is the owner or admin336	/// @dev EVM selector for this function is: 0x9811b0c7,337	///  or in textual repr: isOwnerOrAdmin(address)338	function isOwnerOrAdmin(address user) public view returns (bool) {339		require(false, stub_error);340		user;341		dummy;342		return false;343	}344345	/// Returns collection type346	///347	/// @return `Fungible` or `NFT` or `ReFungible`348	/// @dev EVM selector for this function is: 0xd34b55b8,349	///  or in textual repr: uniqueCollectionType()350	function uniqueCollectionType() public view returns (string memory) {351		require(false, stub_error);352		dummy;353		return "";354	}355356	/// Get collection owner.357	///358	/// @return Tuple with sponsor address and his substrate mirror.359	/// If address is canonical then substrate mirror is zero and vice versa.360	/// @dev EVM selector for this function is: 0xdf727d3b,361	///  or in textual repr: collectionOwner()362	function collectionOwner() public view returns (Tuple17 memory) {363		require(false, stub_error);364		dummy;365		return Tuple17(0x0000000000000000000000000000000000000000, 0);366	}367368	/// Changes collection owner to another account369	///370	/// @dev Owner can be changed only by current owner371	/// @param newOwner new owner account372	/// @dev EVM selector for this function is: 0x4f53e226,373	///  or in textual repr: changeCollectionOwner(address)374	function changeCollectionOwner(address newOwner) public {375		require(false, stub_error);376		newOwner;377		dummy = 0;378	}379380	/// Get collection administrators381	///382	/// @return Vector of tuples with admins address and his substrate mirror.383	/// If address is canonical then substrate mirror is zero and vice versa.384	/// @dev EVM selector for this function is: 0x5813216b,385	///  or in textual repr: collectionAdmins()386	function collectionAdmins() public view returns (Tuple17[] memory) {387		require(false, stub_error);388		dummy;389		return new Tuple17[](0);390	}391}392393/// @dev anonymous struct394struct Tuple17 {395	address field_0;396	uint256 field_1;397}398399/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension400/// @dev See https://eips.ethereum.org/EIPS/eip-721401/// @dev the ERC-165 identifier for this interface is 0x5b5e139f402contract ERC721Metadata is Dummy, ERC165 {403	// /// @notice A descriptive name for a collection of NFTs in this contract404	// /// @dev real implementation of this function lies in `ERC721UniqueExtensions`405	// /// @dev EVM selector for this function is: 0x06fdde03,406	// ///  or in textual repr: name()407	// function name() public view returns (string memory) {408	// 	require(false, stub_error);409	// 	dummy;410	// 	return "";411	// }412413	// /// @notice An abbreviated name for NFTs in this contract414	// /// @dev real implementation of this function lies in `ERC721UniqueExtensions`415	// /// @dev EVM selector for this function is: 0x95d89b41,416	// ///  or in textual repr: symbol()417	// function symbol() public view returns (string memory) {418	// 	require(false, stub_error);419	// 	dummy;420	// 	return "";421	// }422423	/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.424	///425	/// @dev If the token has a `url` property and it is not empty, it is returned.426	///  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`.427	///  If the collection property `baseURI` is empty or absent, return "" (empty string)428	///  otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix429	///  otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).430	///431	/// @return token's const_metadata432	/// @dev EVM selector for this function is: 0xc87b56dd,433	///  or in textual repr: tokenURI(uint256)434	function tokenURI(uint256 tokenId) public view returns (string memory) {435		require(false, stub_error);436		tokenId;437		dummy;438		return "";439	}440}441442/// @title ERC721 Token that can be irreversibly burned (destroyed).443/// @dev the ERC-165 identifier for this interface is 0x42966c68444contract ERC721Burnable is Dummy, ERC165 {445	/// @notice Burns a specific ERC721 token.446	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized447	///  operator of the current owner.448	/// @param tokenId The NFT to approve449	/// @dev EVM selector for this function is: 0x42966c68,450	///  or in textual repr: burn(uint256)451	function burn(uint256 tokenId) public {452		require(false, stub_error);453		tokenId;454		dummy = 0;455	}456}457458/// @dev inlined interface459contract ERC721UniqueMintableEvents {460	event MintingFinished();461}462463/// @title ERC721 minting logic.464/// @dev the ERC-165 identifier for this interface is 0x476ff149465contract ERC721UniqueMintable is Dummy, ERC165, ERC721UniqueMintableEvents {466	/// @dev EVM selector for this function is: 0x05d2035b,467	///  or in textual repr: mintingFinished()468	function mintingFinished() public view returns (bool) {469		require(false, stub_error);470		dummy;471		return false;472	}473474	/// @notice Function to mint token.475	/// @param to The new owner476	/// @return uint256 The id of the newly minted token477	/// @dev EVM selector for this function is: 0x6a627842,478	///  or in textual repr: mint(address)479	function mint(address to) public returns (uint256) {480		require(false, stub_error);481		to;482		dummy = 0;483		return 0;484	}485486	// /// @notice Function to mint token.487	// /// @dev `tokenId` should be obtained with `nextTokenId` method,488	// ///  unlike standard, you can't specify it manually489	// /// @param to The new owner490	// /// @param tokenId ID of the minted NFT491	// /// @dev EVM selector for this function is: 0x40c10f19,492	// ///  or in textual repr: mint(address,uint256)493	// function mint(address to, uint256 tokenId) public returns (bool) {494	// 	require(false, stub_error);495	// 	to;496	// 	tokenId;497	// 	dummy = 0;498	// 	return false;499	// }500501	/// @notice Function to mint token with the given tokenUri.502	/// @param to The new owner503	/// @param tokenUri Token URI that would be stored in the NFT properties504	/// @return uint256 The id of the newly minted token505	/// @dev EVM selector for this function is: 0x45c17782,506	///  or in textual repr: mintWithTokenURI(address,string)507	function mintWithTokenURI(address to, string memory tokenUri) public returns (uint256) {508		require(false, stub_error);509		to;510		tokenUri;511		dummy = 0;512		return 0;513	}514515	// /// @notice Function to mint token with the given tokenUri.516	// /// @dev `tokenId` should be obtained with `nextTokenId` method,517	// ///  unlike standard, you can't specify it manually518	// /// @param to The new owner519	// /// @param tokenId ID of the minted NFT520	// /// @param tokenUri Token URI that would be stored in the NFT properties521	// /// @dev EVM selector for this function is: 0x50bb4e7f,522	// ///  or in textual repr: mintWithTokenURI(address,uint256,string)523	// function mintWithTokenURI(address to, uint256 tokenId, string memory tokenUri) public returns (bool) {524	// 	require(false, stub_error);525	// 	to;526	// 	tokenId;527	// 	tokenUri;528	// 	dummy = 0;529	// 	return false;530	// }531532	/// @dev Not implemented533	/// @dev EVM selector for this function is: 0x7d64bcb4,534	///  or in textual repr: finishMinting()535	function finishMinting() public returns (bool) {536		require(false, stub_error);537		dummy = 0;538		return false;539	}540}541542/// @title Unique extensions for ERC721.543/// @dev the ERC-165 identifier for this interface is 0x4468500d544contract ERC721UniqueExtensions is Dummy, ERC165 {545	/// @notice A descriptive name for a collection of NFTs in this contract546	/// @dev EVM selector for this function is: 0x06fdde03,547	///  or in textual repr: name()548	function name() public view returns (string memory) {549		require(false, stub_error);550		dummy;551		return "";552	}553554	/// @notice An abbreviated name for NFTs in this contract555	/// @dev EVM selector for this function is: 0x95d89b41,556	///  or in textual repr: symbol()557	function symbol() public view returns (string memory) {558		require(false, stub_error);559		dummy;560		return "";561	}562563	/// @notice Transfer ownership of an NFT564	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`565	///  is the zero address. Throws if `tokenId` is not a valid NFT.566	/// @param to The new owner567	/// @param tokenId The NFT to transfer568	/// @dev EVM selector for this function is: 0xa9059cbb,569	///  or in textual repr: transfer(address,uint256)570	function transfer(address to, uint256 tokenId) public {571		require(false, stub_error);572		to;573		tokenId;574		dummy = 0;575	}576577	/// @notice Burns a specific ERC721 token.578	/// @dev Throws unless `msg.sender` is the current owner or an authorized579	///  operator for this NFT. Throws if `from` is not the current owner. Throws580	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.581	/// @param from The current owner of the NFT582	/// @param tokenId The NFT to transfer583	/// @dev EVM selector for this function is: 0x79cc6790,584	///  or in textual repr: burnFrom(address,uint256)585	function burnFrom(address from, uint256 tokenId) public {586		require(false, stub_error);587		from;588		tokenId;589		dummy = 0;590	}591592	/// @notice Returns next free NFT ID.593	/// @dev EVM selector for this function is: 0x75794a3c,594	///  or in textual repr: nextTokenId()595	function nextTokenId() public view returns (uint256) {596		require(false, stub_error);597		dummy;598		return 0;599	}600	// /// @notice Function to mint multiple tokens.601	// /// @dev `tokenIds` should be an array of consecutive numbers and first number602	// ///  should be obtained with `nextTokenId` method603	// /// @param to The new owner604	// /// @param tokenIds IDs of the minted NFTs605	// /// @dev EVM selector for this function is: 0x44a9945e,606	// ///  or in textual repr: mintBulk(address,uint256[])607	// function mintBulk(address to, uint256[] memory tokenIds) public returns (bool) {608	// 	require(false, stub_error);609	// 	to;610	// 	tokenIds;611	// 	dummy = 0;612	// 	return false;613	// }614615	// /// @notice Function to mint multiple tokens with the given tokenUris.616	// /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive617	// ///  numbers and first number should be obtained with `nextTokenId` method618	// /// @param to The new owner619	// /// @param tokens array of pairs of token ID and token URI for minted tokens620	// /// @dev EVM selector for this function is: 0x36543006,621	// ///  or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])622	// function mintBulkWithTokenURI(address to, Tuple6[] memory tokens) public returns (bool) {623	// 	require(false, stub_error);624	// 	to;625	// 	tokens;626	// 	dummy = 0;627	// 	return false;628	// }629630}631632/// @dev anonymous struct633struct Tuple6 {634	uint256 field_0;635	string field_1;636}637638/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension639/// @dev See https://eips.ethereum.org/EIPS/eip-721640/// @dev the ERC-165 identifier for this interface is 0x780e9d63641contract ERC721Enumerable is Dummy, ERC165 {642	/// @notice Enumerate valid NFTs643	/// @param index A counter less than `totalSupply()`644	/// @return The token identifier for the `index`th NFT,645	///  (sort order not specified)646	/// @dev EVM selector for this function is: 0x4f6ccce7,647	///  or in textual repr: tokenByIndex(uint256)648	function tokenByIndex(uint256 index) public view returns (uint256) {649		require(false, stub_error);650		index;651		dummy;652		return 0;653	}654655	/// @dev Not implemented656	/// @dev EVM selector for this function is: 0x2f745c59,657	///  or in textual repr: tokenOfOwnerByIndex(address,uint256)658	function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256) {659		require(false, stub_error);660		owner;661		index;662		dummy;663		return 0;664	}665666	/// @notice Count NFTs tracked by this contract667	/// @return A count of valid NFTs tracked by this contract, where each one of668	///  them has an assigned and queryable owner not equal to the zero address669	/// @dev EVM selector for this function is: 0x18160ddd,670	///  or in textual repr: totalSupply()671	function totalSupply() public view returns (uint256) {672		require(false, stub_error);673		dummy;674		return 0;675	}676}677678/// @dev inlined interface679contract ERC721Events {680	event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);681	event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);682	event ApprovalForAll(address indexed owner, address indexed operator, bool approved);683}684685/// @title ERC-721 Non-Fungible Token Standard686/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md687/// @dev the ERC-165 identifier for this interface is 0x80ac58cd688contract ERC721 is Dummy, ERC165, ERC721Events {689	/// @notice Count all NFTs assigned to an owner690	/// @dev NFTs assigned to the zero address are considered invalid, and this691	///  function throws for queries about the zero address.692	/// @param owner An address for whom to query the balance693	/// @return The number of NFTs owned by `owner`, possibly zero694	/// @dev EVM selector for this function is: 0x70a08231,695	///  or in textual repr: balanceOf(address)696	function balanceOf(address owner) public view returns (uint256) {697		require(false, stub_error);698		owner;699		dummy;700		return 0;701	}702703	/// @notice Find the owner of an NFT704	/// @dev NFTs assigned to zero address are considered invalid, and queries705	///  about them do throw.706	/// @param tokenId The identifier for an NFT707	/// @return The address of the owner of the NFT708	/// @dev EVM selector for this function is: 0x6352211e,709	///  or in textual repr: ownerOf(uint256)710	function ownerOf(uint256 tokenId) public view returns (address) {711		require(false, stub_error);712		tokenId;713		dummy;714		return 0x0000000000000000000000000000000000000000;715	}716717	/// @dev Not implemented718	/// @dev EVM selector for this function is: 0xb88d4fde,719	///  or in textual repr: safeTransferFrom(address,address,uint256,bytes)720	function safeTransferFrom(721		address from,722		address to,723		uint256 tokenId,724		bytes memory data725	) public {726		require(false, stub_error);727		from;728		to;729		tokenId;730		data;731		dummy = 0;732	}733734	/// @dev Not implemented735	/// @dev EVM selector for this function is: 0x42842e0e,736	///  or in textual repr: safeTransferFrom(address,address,uint256)737	function safeTransferFrom(738		address from,739		address to,740		uint256 tokenId741	) public {742		require(false, stub_error);743		from;744		to;745		tokenId;746		dummy = 0;747	}748749	/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE750	///  TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE751	///  THEY MAY BE PERMANENTLY LOST752	/// @dev Throws unless `msg.sender` is the current owner or an authorized753	///  operator for this NFT. Throws if `from` is not the current owner. Throws754	///  if `to` is the zero address. Throws if `tokenId` is not a valid NFT.755	/// @param from The current owner of the NFT756	/// @param to The new owner757	/// @param tokenId The NFT to transfer758	/// @dev EVM selector for this function is: 0x23b872dd,759	///  or in textual repr: transferFrom(address,address,uint256)760	function transferFrom(761		address from,762		address to,763		uint256 tokenId764	) public {765		require(false, stub_error);766		from;767		to;768		tokenId;769		dummy = 0;770	}771772	/// @notice Set or reaffirm the approved address for an NFT773	/// @dev The zero address indicates there is no approved address.774	/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized775	///  operator of the current owner.776	/// @param approved The new approved NFT controller777	/// @param tokenId The NFT to approve778	/// @dev EVM selector for this function is: 0x095ea7b3,779	///  or in textual repr: approve(address,uint256)780	function approve(address approved, uint256 tokenId) public {781		require(false, stub_error);782		approved;783		tokenId;784		dummy = 0;785	}786787	/// @dev Not implemented788	/// @dev EVM selector for this function is: 0xa22cb465,789	///  or in textual repr: setApprovalForAll(address,bool)790	function setApprovalForAll(address operator, bool approved) public {791		require(false, stub_error);792		operator;793		approved;794		dummy = 0;795	}796797	/// @dev Not implemented798	/// @dev EVM selector for this function is: 0x081812fc,799	///  or in textual repr: getApproved(uint256)800	function getApproved(uint256 tokenId) public view returns (address) {801		require(false, stub_error);802		tokenId;803		dummy;804		return 0x0000000000000000000000000000000000000000;805	}806807	/// @dev Not implemented808	/// @dev EVM selector for this function is: 0xe985e9c5,809	///  or in textual repr: isApprovedForAll(address,address)810	function isApprovedForAll(address owner, address operator) public view returns (address) {811		require(false, stub_error);812		owner;813		operator;814		dummy;815		return 0x0000000000000000000000000000000000000000;816	}817}818819contract UniqueNFT is820	Dummy,821	ERC165,822	ERC721,823	ERC721Enumerable,824	ERC721UniqueExtensions,825	ERC721UniqueMintable,826	ERC721Burnable,827	ERC721Metadata,828	Collection,829	TokenProperties830{}
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);
   }