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

difftreelog

feature: Added `transfer_cross` in eth functions.

PraetorP2022-11-14parent: #20c695f.patch.diff
in: master

22 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6121,7 +6121,7 @@
 
 [[package]]
 name = "pallet-fungible"
-version = "0.1.6"
+version = "0.1.7"
 dependencies = [
  "ethereum",
  "evm-coder",
@@ -6376,7 +6376,7 @@
 
 [[package]]
 name = "pallet-nonfungible"
-version = "0.1.8"
+version = "0.1.9"
 dependencies = [
  "ethereum",
  "evm-coder",
@@ -6498,7 +6498,7 @@
 
 [[package]]
 name = "pallet-refungible"
-version = "0.2.7"
+version = "0.2.8"
 dependencies = [
  "derivative",
  "ethereum",
modifiedpallets/fungible/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/fungible/CHANGELOG.md
+++ b/pallets/fungible/CHANGELOG.md
@@ -2,22 +2,32 @@
 
 All notable changes to this project will be documented in this file.
 
+<!-- bureaucrate goes here -->
+
+## [0.1.7] - 2022-11-14
+
+### Changed
+
+- Added `transfer_cross` in eth functions.
+
 ## [0.1.6] - 2022-11-02
+
 ### Changed
- - Use named structure `EthCrossAccount` in eth functions.
 
+- Use named structure `EthCrossAccount` in eth functions.
+
 ## [0.1.5] - 2022-08-29
 
 ### Added
 
- - Implementation of `mint` and `mint_bulk` methods for ERC20 API.
+- Implementation of `mint` and `mint_bulk` methods for ERC20 API.
 
 ## [v0.1.4] - 2022-08-24
 
 ### Change
- - Add bound `AsRef<[u8; 32]>` to `T::CrossAccountId`.
 
-<!-- bureaucrate goes here -->
+- Add bound `AsRef<[u8; 32]>` to `T::CrossAccountId`.
+
 ## [v0.1.3] 2022-08-16
 
 ### Other changes
@@ -41,11 +51,11 @@
 
 ### Fixed
 
- - Issue with ItemCreated event containing total supply of tokens instead minted amount
+- Issue with ItemCreated event containing total supply of tokens instead minted amount
 
 ## [0.1.1] - 2022-07-14
 
 ### Added
 
- - Implementation of RPC method `token_owners` returning 10 owners in no particular order.
-    This was an internal request to improve the web interface and support fractionalization event.
+- Implementation of RPC method `token_owners` returning 10 owners in no particular order.
+  This was an internal request to improve the web interface and support fractionalization event.
modifiedpallets/fungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/fungible/Cargo.toml
+++ b/pallets/fungible/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pallet-fungible"
-version = "0.1.6"
+version = "0.1.7"
 license = "GPLv3"
 edition = "2021"
 
modifiedpallets/fungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -93,6 +93,7 @@
 		<Pallet<T>>::transfer(self, &caller, &to, amount, &budget).map_err(|_| "transfer error")?;
 		Ok(true)
 	}
+
 	#[weight(<SelfWeightOf<T>>::transfer_from())]
 	fn transfer_from(
 		&mut self,
@@ -238,6 +239,24 @@
 		Ok(true)
 	}
 
+	#[weight(<SelfWeightOf<T>>::transfer())]
+	fn transfer_cross(
+		&mut self,
+		caller: caller,
+		to: EthCrossAccount,
+		amount: uint256,
+	) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = to.into_sub_cross_account::<T>()?;
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		<Pallet<T>>::transfer(self, &caller, &to, amount, &budget).map_err(|_| "transfer error")?;
+		Ok(true)
+	}
+
 	#[weight(<SelfWeightOf<T>>::transfer_from())]
 	fn transfer_from_cross(
 		&mut self,
modifiedpallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth
--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -38,7 +38,7 @@
 	/// @param properties Vector of properties key/value pair.
 	/// @dev EVM selector for this function is: 0x50b26b2a,
 	///  or in textual repr: setCollectionProperties((string,bytes)[])
-	function setCollectionProperties(Tuple14[] memory properties) public {
+	function setCollectionProperties(Tuple15[] memory properties) public {
 		require(false, stub_error);
 		properties;
 		dummy = 0;
@@ -87,11 +87,11 @@
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
-	function collectionProperties(string[] memory keys) public view returns (Tuple14[] memory) {
+	function collectionProperties(string[] memory keys) public view returns (Tuple15[] memory) {
 		require(false, stub_error);
 		keys;
 		dummy;
-		return new Tuple14[](0);
+		return new Tuple15[](0);
 	}
 
 	/// Set the sponsor of the collection.
@@ -439,12 +439,12 @@
 }
 
 /// @dev anonymous struct
-struct Tuple14 {
+struct Tuple15 {
 	string field_0;
 	bytes field_1;
 }
 
-/// @dev the ERC-165 identifier for this interface is 0x032e5926
+/// @dev the ERC-165 identifier for this interface is 0x29f4dcd9
 contract ERC20UniqueExtensions is Dummy, ERC165 {
 	/// @dev EVM selector for this function is: 0x0ecd0ab0,
 	///  or in textual repr: approveCross((address,uint256),uint256)
@@ -497,6 +497,16 @@
 		return false;
 	}
 
+	/// @dev EVM selector for this function is: 0x2ada85ff,
+	///  or in textual repr: transferCross((address,uint256),uint256)
+	function transferCross(EthCrossAccount memory to, uint256 amount) public returns (bool) {
+		require(false, stub_error);
+		to;
+		amount;
+		dummy = 0;
+		return false;
+	}
+
 	/// @dev EVM selector for this function is: 0xd5cf430b,
 	///  or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
 	function transferFromCross(
modifiedpallets/nonfungible/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/nonfungible/CHANGELOG.md
+++ b/pallets/nonfungible/CHANGELOG.md
@@ -4,6 +4,12 @@
 
 <!-- bureaucrate goes here -->
 
+## [0.1.9] - 2022-11-14
+
+### Changed
+
+- Added `transfer_cross` in eth functions.
+
 ## [v0.1.8] - 2022-11-11
 
 ### Changed
modifiedpallets/nonfungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/nonfungible/Cargo.toml
+++ b/pallets/nonfungible/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pallet-nonfungible"
-version = "0.1.8"
+version = "0.1.9"
 license = "GPLv3"
 edition = "2021"
 
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -738,7 +738,25 @@
 		<Pallet<T>>::transfer(self, &caller, &to, token, &budget).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.
+	/// @param to The new owner
+	/// @param tokenId The NFT to transfer
+	#[weight(<SelfWeightOf<T>>::transfer())]
+	fn transfer_cross(&mut self, caller: caller, to: EthCrossAccount, token_id: uint256) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = to.into_sub_cross_account::<T>()?;
+		let token = token_id.try_into()?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
+		<Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;
+		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.
modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -67,7 +67,7 @@
 	/// @param properties settable properties
 	/// @dev EVM selector for this function is: 0x14ed3a6e,
 	///  or in textual repr: setProperties(uint256,(string,bytes)[])
-	function setProperties(uint256 tokenId, Tuple21[] memory properties) public {
+	function setProperties(uint256 tokenId, Tuple22[] memory properties) public {
 		require(false, stub_error);
 		tokenId;
 		properties;
@@ -137,7 +137,7 @@
 	/// @param properties Vector of properties key/value pair.
 	/// @dev EVM selector for this function is: 0x50b26b2a,
 	///  or in textual repr: setCollectionProperties((string,bytes)[])
-	function setCollectionProperties(Tuple21[] memory properties) public {
+	function setCollectionProperties(Tuple22[] memory properties) public {
 		require(false, stub_error);
 		properties;
 		dummy = 0;
@@ -186,11 +186,11 @@
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
-	function collectionProperties(string[] memory keys) public view returns (Tuple21[] memory) {
+	function collectionProperties(string[] memory keys) public view returns (Tuple22[] memory) {
 		require(false, stub_error);
 		keys;
 		dummy;
-		return new Tuple21[](0);
+		return new Tuple22[](0);
 	}
 
 	/// Set the sponsor of the collection.
@@ -251,10 +251,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 (Tuple24 memory) {
+	function collectionSponsor() public view returns (Tuple25 memory) {
 		require(false, stub_error);
 		dummy;
-		return Tuple24(0x0000000000000000000000000000000000000000, 0);
+		return Tuple25(0x0000000000000000000000000000000000000000, 0);
 	}
 
 	/// Set limits for the collection.
@@ -538,13 +538,13 @@
 }
 
 /// @dev anonymous struct
-struct Tuple24 {
+struct Tuple25 {
 	address field_0;
 	uint256 field_1;
 }
 
 /// @dev anonymous struct
-struct Tuple21 {
+struct Tuple22 {
 	string field_0;
 	bytes field_1;
 }
@@ -693,7 +693,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x244543ee
+/// @dev the ERC-165 identifier for this interface is 0x0e9fc611
 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,
@@ -742,6 +742,20 @@
 		dummy = 0;
 	}
 
+	/// @notice Transfer ownership of an NFT
+	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+	///  is the zero address. Throws if `tokenId` is not a valid NFT.
+	/// @param to The new owner
+	/// @param tokenId The NFT to transfer
+	/// @dev EVM selector for this function is: 0x2ada85ff,
+	///  or in textual repr: transferCross((address,uint256),uint256)
+	function transferCross(EthCrossAccount memory to, uint256 tokenId) public {
+		require(false, stub_error);
+		to;
+		tokenId;
+		dummy = 0;
+	}
+
 	/// @notice Transfer ownership of an NFT from cross account address to cross account address
 	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
 	///  is the zero address. Throws if `tokenId` is not a valid NFT.
@@ -822,7 +836,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, Tuple10[] memory tokens) public returns (bool) {
+	// function mintBulkWithTokenURI(address to, Tuple11[] memory tokens) public returns (bool) {
 	// 	require(false, stub_error);
 	// 	to;
 	// 	tokens;
@@ -833,7 +847,7 @@
 }
 
 /// @dev anonymous struct
-struct Tuple10 {
+struct Tuple11 {
 	uint256 field_0;
 	string field_1;
 }
modifiedpallets/refungible/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/refungible/CHANGELOG.md
+++ b/pallets/refungible/CHANGELOG.md
@@ -4,6 +4,12 @@
 
 <!-- bureaucrate goes here -->
 
+## [0.2.8] - 2022-11-14
+
+### Changed
+
+- Added `transfer_cross` in eth functions.
+
 ## [v0.2.7] - 2022-11-11
 
 ### Changed
modifiedpallets/refungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/refungible/Cargo.toml
+++ b/pallets/refungible/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pallet-refungible"
-version = "0.2.7"
+version = "0.2.8"
 license = "GPLv3"
 edition = "2021"
 
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -756,6 +756,29 @@
 	/// @param to The new owner
 	/// @param tokenId The RFT to transfer
 	#[weight(<SelfWeightOf<T>>::transfer_creating_removing())]
+	fn transfer_cross(&mut self, caller: caller, to: EthCrossAccount, token_id: uint256) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = to.into_sub_cross_account::<T>()?;
+		let token = token_id.try_into()?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		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,
modifiedpallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth
--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -67,7 +67,7 @@
 	/// @param properties settable properties
 	/// @dev EVM selector for this function is: 0x14ed3a6e,
 	///  or in textual repr: setProperties(uint256,(string,bytes)[])
-	function setProperties(uint256 tokenId, Tuple20[] memory properties) public {
+	function setProperties(uint256 tokenId, Tuple21[] memory properties) public {
 		require(false, stub_error);
 		tokenId;
 		properties;
@@ -137,7 +137,7 @@
 	/// @param properties Vector of properties key/value pair.
 	/// @dev EVM selector for this function is: 0x50b26b2a,
 	///  or in textual repr: setCollectionProperties((string,bytes)[])
-	function setCollectionProperties(Tuple20[] memory properties) public {
+	function setCollectionProperties(Tuple21[] memory properties) public {
 		require(false, stub_error);
 		properties;
 		dummy = 0;
@@ -186,11 +186,11 @@
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
-	function collectionProperties(string[] memory keys) public view returns (Tuple20[] memory) {
+	function collectionProperties(string[] memory keys) public view returns (Tuple21[] memory) {
 		require(false, stub_error);
 		keys;
 		dummy;
-		return new Tuple20[](0);
+		return new Tuple21[](0);
 	}
 
 	/// Set the sponsor of the collection.
@@ -251,10 +251,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 (Tuple23 memory) {
+	function collectionSponsor() public view returns (Tuple24 memory) {
 		require(false, stub_error);
 		dummy;
-		return Tuple23(0x0000000000000000000000000000000000000000, 0);
+		return Tuple24(0x0000000000000000000000000000000000000000, 0);
 	}
 
 	/// Set limits for the collection.
@@ -538,13 +538,13 @@
 }
 
 /// @dev anonymous struct
-struct Tuple23 {
+struct Tuple24 {
 	address field_0;
 	uint256 field_1;
 }
 
 /// @dev anonymous struct
-struct Tuple20 {
+struct Tuple21 {
 	string field_0;
 	bytes field_1;
 }
@@ -691,7 +691,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x81feb398
+/// @dev the ERC-165 identifier for this interface is 0xab243667
 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,
@@ -732,6 +732,21 @@
 	///  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: 0x2ada85ff,
+	///  or in textual repr: transferCross((address,uint256),uint256)
+	function transferCross(EthCrossAccount memory to, uint256 tokenId) public {
+		require(false, stub_error);
+		to;
+		tokenId;
+		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(
@@ -809,7 +824,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, Tuple9[] memory tokens) public returns (bool) {
+	// function mintBulkWithTokenURI(address to, Tuple10[] memory tokens) public returns (bool) {
 	// 	require(false, stub_error);
 	// 	to;
 	// 	tokens;
@@ -831,7 +846,7 @@
 }
 
 /// @dev anonymous struct
-struct Tuple9 {
+struct Tuple10 {
 	uint256 field_0;
 	string field_1;
 }
modifiedtests/src/eth/api/UniqueFungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -28,7 +28,7 @@
 	/// @param properties Vector of properties key/value pair.
 	/// @dev EVM selector for this function is: 0x50b26b2a,
 	///  or in textual repr: setCollectionProperties((string,bytes)[])
-	function setCollectionProperties(Tuple14[] memory properties) external;
+	function setCollectionProperties(Tuple15[] memory properties) external;
 
 	/// Delete collection property.
 	///
@@ -60,7 +60,7 @@
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
-	function collectionProperties(string[] memory keys) external view returns (Tuple14[] memory);
+	function collectionProperties(string[] memory keys) external view returns (Tuple15[] memory);
 
 	/// Set the sponsor of the collection.
 	///
@@ -287,12 +287,12 @@
 }
 
 /// @dev anonymous struct
-struct Tuple14 {
+struct Tuple15 {
 	string field_0;
 	bytes field_1;
 }
 
-/// @dev the ERC-165 identifier for this interface is 0x032e5926
+/// @dev the ERC-165 identifier for this interface is 0x29f4dcd9
 interface ERC20UniqueExtensions is Dummy, ERC165 {
 	/// @dev EVM selector for this function is: 0x0ecd0ab0,
 	///  or in textual repr: approveCross((address,uint256),uint256)
@@ -322,6 +322,10 @@
 	///  or in textual repr: mintBulk((address,uint256)[])
 	function mintBulk(Tuple8[] memory amounts) external returns (bool);
 
+	/// @dev EVM selector for this function is: 0x2ada85ff,
+	///  or in textual repr: transferCross((address,uint256),uint256)
+	function transferCross(EthCrossAccount memory to, uint256 amount) external returns (bool);
+
 	/// @dev EVM selector for this function is: 0xd5cf430b,
 	///  or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
 	function transferFromCross(
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -49,7 +49,7 @@
 	/// @param properties settable properties
 	/// @dev EVM selector for this function is: 0x14ed3a6e,
 	///  or in textual repr: setProperties(uint256,(string,bytes)[])
-	function setProperties(uint256 tokenId, Tuple21[] memory properties) external;
+	function setProperties(uint256 tokenId, Tuple22[] memory properties) external;
 
 	// /// @notice Delete token property value.
 	// /// @dev Throws error if `msg.sender` has no permission to edit the property.
@@ -93,7 +93,7 @@
 	/// @param properties Vector of properties key/value pair.
 	/// @dev EVM selector for this function is: 0x50b26b2a,
 	///  or in textual repr: setCollectionProperties((string,bytes)[])
-	function setCollectionProperties(Tuple21[] memory properties) external;
+	function setCollectionProperties(Tuple22[] memory properties) external;
 
 	/// Delete collection property.
 	///
@@ -125,7 +125,7 @@
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
-	function collectionProperties(string[] memory keys) external view returns (Tuple21[] memory);
+	function collectionProperties(string[] memory keys) external view returns (Tuple22[] memory);
 
 	/// Set the sponsor of the collection.
 	///
@@ -167,7 +167,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 (Tuple24 memory);
+	function collectionSponsor() external view returns (Tuple25 memory);
 
 	/// Set limits for the collection.
 	/// @dev Throws error if limit not found.
@@ -352,13 +352,13 @@
 }
 
 /// @dev anonymous struct
-struct Tuple24 {
+struct Tuple25 {
 	address field_0;
 	uint256 field_1;
 }
 
 /// @dev anonymous struct
-struct Tuple21 {
+struct Tuple22 {
 	string field_0;
 	bytes field_1;
 }
@@ -458,7 +458,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x244543ee
+/// @dev the ERC-165 identifier for this interface is 0x0e9fc611
 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,
@@ -489,6 +489,15 @@
 	///  or in textual repr: transfer(address,uint256)
 	function transfer(address to, 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.
+	/// @param to The new owner
+	/// @param tokenId The NFT to transfer
+	/// @dev EVM selector for this function is: 0x2ada85ff,
+	///  or in textual repr: transferCross((address,uint256),uint256)
+	function transferCross(EthCrossAccount memory 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.
@@ -543,12 +552,12 @@
 	// /// @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, Tuple10[] memory tokens) external returns (bool);
+	// function mintBulkWithTokenURI(address to, Tuple11[] memory tokens) external returns (bool);
 
 }
 
 /// @dev anonymous struct
-struct Tuple10 {
+struct Tuple11 {
 	uint256 field_0;
 	string field_1;
 }
modifiedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -49,7 +49,7 @@
 	/// @param properties settable properties
 	/// @dev EVM selector for this function is: 0x14ed3a6e,
 	///  or in textual repr: setProperties(uint256,(string,bytes)[])
-	function setProperties(uint256 tokenId, Tuple20[] memory properties) external;
+	function setProperties(uint256 tokenId, Tuple21[] memory properties) external;
 
 	// /// @notice Delete token property value.
 	// /// @dev Throws error if `msg.sender` has no permission to edit the property.
@@ -93,7 +93,7 @@
 	/// @param properties Vector of properties key/value pair.
 	/// @dev EVM selector for this function is: 0x50b26b2a,
 	///  or in textual repr: setCollectionProperties((string,bytes)[])
-	function setCollectionProperties(Tuple20[] memory properties) external;
+	function setCollectionProperties(Tuple21[] memory properties) external;
 
 	/// Delete collection property.
 	///
@@ -125,7 +125,7 @@
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
-	function collectionProperties(string[] memory keys) external view returns (Tuple20[] memory);
+	function collectionProperties(string[] memory keys) external view returns (Tuple21[] memory);
 
 	/// Set the sponsor of the collection.
 	///
@@ -167,7 +167,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 (Tuple23 memory);
+	function collectionSponsor() external view returns (Tuple24 memory);
 
 	/// Set limits for the collection.
 	/// @dev Throws error if limit not found.
@@ -352,13 +352,13 @@
 }
 
 /// @dev anonymous struct
-struct Tuple23 {
+struct Tuple24 {
 	address field_0;
 	uint256 field_1;
 }
 
 /// @dev anonymous struct
-struct Tuple20 {
+struct Tuple21 {
 	string field_0;
 	bytes field_1;
 }
@@ -456,7 +456,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x81feb398
+/// @dev the ERC-165 identifier for this interface is 0xab243667
 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,
@@ -484,6 +484,16 @@
 	///  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: 0x2ada85ff,
+	///  or in textual repr: transferCross((address,uint256),uint256)
+	function transferCross(EthCrossAccount memory 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(
@@ -535,7 +545,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, Tuple9[] memory tokens) external returns (bool);
+	// function mintBulkWithTokenURI(address to, Tuple10[] memory tokens) external returns (bool);
 
 	/// Returns EVM address for refungible token
 	///
@@ -546,7 +556,7 @@
 }
 
 /// @dev anonymous struct
-struct Tuple9 {
+struct Tuple10 {
 	uint256 field_0;
 	string field_1;
 }
modifiedtests/src/eth/fungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/fungible.test.ts
+++ b/tests/src/eth/fungible.test.ts
@@ -230,6 +230,37 @@
     }
   });
 
+  itEth('Can perform transferCross()', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const receiver = await helper.eth.createAccountWithBalance(donor);
+    const to = helper.ethCrossAccount.fromAddress(receiver);
+    const collection = await helper.ft.mintCollection(alice);
+    await collection.mint(alice, 200n, {Ethereum: owner});
+
+    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+    {
+      const result = await contract.methods.transferCross(to, 50).send({from: owner});
+      
+      const event = result.events.Transfer;
+      expect(event.address).to.be.equal(collectionAddress);
+      expect(event.returnValues.from).to.be.equal(owner);
+      expect(event.returnValues.to).to.be.equal(receiver);
+      expect(event.returnValues.value).to.be.equal('50');
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(owner).call();
+      expect(+balance).to.equal(150);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(receiver).call();
+      expect(+balance).to.equal(50);
+    }
+  });
+  
   itEth('Can perform transfer()', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const receiver = await helper.eth.createAccountWithBalance(donor);
modifiedtests/src/eth/fungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/fungibleAbi.json
+++ b/tests/src/eth/fungibleAbi.json
@@ -239,7 +239,7 @@
           { "internalType": "string", "name": "field_0", "type": "string" },
           { "internalType": "bytes", "name": "field_1", "type": "bytes" }
         ],
-        "internalType": "struct Tuple14[]",
+        "internalType": "struct Tuple15[]",
         "name": "",
         "type": "tuple[]"
       }
@@ -496,7 +496,7 @@
           { "internalType": "string", "name": "field_0", "type": "string" },
           { "internalType": "bytes", "name": "field_1", "type": "bytes" }
         ],
-        "internalType": "struct Tuple14[]",
+        "internalType": "struct Tuple15[]",
         "name": "properties",
         "type": "tuple[]"
       }
@@ -594,6 +594,24 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "eth", "type": "address" },
+          { "internalType": "uint256", "name": "sub", "type": "uint256" }
+        ],
+        "internalType": "struct EthCrossAccount",
+        "name": "to",
+        "type": "tuple"
+      },
+      { "internalType": "uint256", "name": "amount", "type": "uint256" }
+    ],
+    "name": "transferCross",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "from", "type": "address" },
       { "internalType": "address", "name": "to", "type": "address" },
       { "internalType": "uint256", "name": "amount", "type": "uint256" }
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
before · tests/src/eth/nonFungible.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util';18import {IKeyringPair} from '@polkadot/types/types';19import {Contract} from 'web3-eth-contract';202122describe('NFT: Information getting', () => {23  let donor: IKeyringPair;24  let alice: IKeyringPair;2526  before(async function() {27    await usingEthPlaygrounds(async (helper, privateKey) => {28      donor = await privateKey({filename: __filename});29      [alice] = await helper.arrange.createAccounts([10n], donor);30    });31  });3233  itEth('totalSupply', async ({helper}) => {34    const collection = await helper.nft.mintCollection(alice, {});35    await collection.mintToken(alice);3637    const caller = await helper.eth.createAccountWithBalance(donor);3839    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);40    const totalSupply = await contract.methods.totalSupply().call();4142    expect(totalSupply).to.equal('1');43  });4445  itEth('balanceOf', async ({helper}) => {46    const collection = await helper.nft.mintCollection(alice, {});47    const caller = await helper.eth.createAccountWithBalance(donor);4849    await collection.mintToken(alice, {Ethereum: caller});50    await collection.mintToken(alice, {Ethereum: caller});51    await collection.mintToken(alice, {Ethereum: caller});5253    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);54    const balance = await contract.methods.balanceOf(caller).call();5556    expect(balance).to.equal('3');57  });5859  itEth('ownerOf', async ({helper}) => {60    const collection = await helper.nft.mintCollection(alice, {});61    const caller = await helper.eth.createAccountWithBalance(donor);6263    const token = await collection.mintToken(alice, {Ethereum: caller});6465    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);6667    const owner = await contract.methods.ownerOf(token.tokenId).call();6869    expect(owner).to.equal(caller);70  });7172  itEth('name/symbol is available regardless of ERC721Metadata support', async ({helper}) => {73    const collection = await helper.nft.mintCollection(alice, {name: 'test', tokenPrefix: 'TEST'});74    const caller = helper.eth.createAccount();7576    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);7778    expect(await contract.methods.name().call()).to.equal('test');79    expect(await contract.methods.symbol().call()).to.equal('TEST');80  });81});8283describe('Check ERC721 token URI for NFT', () => {84  let donor: IKeyringPair;8586  before(async function() {87    await usingEthPlaygrounds(async (_helper, privateKey) => {88      donor = await privateKey({filename: __filename});89    });90  });9192  async function setup(helper: EthUniqueHelper, baseUri: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> {93    const owner = await helper.eth.createAccountWithBalance(donor);94    const receiver = helper.eth.createAccount();9596    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', 'a', 'b', baseUri);97    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);9899    const result = await contract.methods.mint(receiver).send();100    const tokenId = result.events.Transfer.returnValues.tokenId;101    expect(tokenId).to.be.equal('1');102103    if (propertyKey && propertyValue) {104      // Set URL or suffix105      await contract.methods.setProperty(tokenId, propertyKey, Buffer.from(propertyValue)).send();106    }107108    const event = result.events.Transfer;109    expect(event.address).to.be.equal(collectionAddress);110    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');111    expect(event.returnValues.to).to.be.equal(receiver);112    expect(event.returnValues.tokenId).to.be.equal(tokenId);113114    return {contract, nextTokenId: tokenId};115  }116117  itEth('Empty tokenURI', async ({helper}) => {118    const {contract, nextTokenId} = await setup(helper, '');119    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('');120  });121122  itEth('TokenURI from url', async ({helper}) => {123    const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URI', 'Token URI');124    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI');125  });126127  itEth('TokenURI from baseURI', async ({helper}) => {128    const {contract, nextTokenId} = await setup(helper, 'BaseURI_');129    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_');130  });131132  itEth('TokenURI from baseURI + suffix', async ({helper}) => {133    const suffix = '/some/suffix';134    const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URISuffix', suffix);135    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix);136  });137});138139describe('NFT: Plain calls', () => {140  let donor: IKeyringPair;141  let minter: IKeyringPair;142  let bob: IKeyringPair;143  let charlie: IKeyringPair;144145  before(async function() {146    await usingEthPlaygrounds(async (helper, privateKey) => {147      donor = await privateKey({filename: __filename});148      [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);149    });150  });151152  itEth('Can perform mint()', async ({helper}) => {153    const owner = await helper.eth.createAccountWithBalance(donor);154    const receiver = helper.eth.createAccount();155156    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', '6', '6', '');157    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);158159    const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();160    const tokenId = result.events.Transfer.returnValues.tokenId;161    expect(tokenId).to.be.equal('1');162163    const event = result.events.Transfer;164    expect(event.address).to.be.equal(collectionAddress);165    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');166    expect(event.returnValues.to).to.be.equal(receiver);167168    expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');169170    // TODO: this wont work right now, need release 919000 first171    // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send();172    // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();173    // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);174  });175176  //TODO: CORE-302 add eth methods177  itEth.skip('Can perform mintBulk()', async ({helper}) => {178    const caller = await helper.eth.createAccountWithBalance(donor);179    const receiver = helper.eth.createAccount();180181    const collection = await helper.nft.mintCollection(minter);182    await collection.addAdmin(minter, {Ethereum: caller});183184    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);185    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller);186    {187      const bulkSize = 3;188      const nextTokenId = await contract.methods.nextTokenId().call();189      expect(nextTokenId).to.be.equal('1');190      const result = await contract.methods.mintBulkWithTokenURI(191        receiver,192        Array.from({length: bulkSize}, (_, i) => (193          [+nextTokenId + i, `Test URI ${i}`]194        )),195      ).send({from: caller});196197      const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);198      for (let i = 0; i < bulkSize; i++) {199        const event = events[i];200        expect(event.address).to.equal(collectionAddress);201        expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');202        expect(event.returnValues.to).to.equal(receiver);203        expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`);204205        expect(await contract.methods.tokenURI(+nextTokenId + i).call()).to.be.equal(`Test URI ${i}`);206      }207    }208  });209210  itEth('Can perform burn()', async ({helper}) => {211    const caller = await helper.eth.createAccountWithBalance(donor);212213    const collection = await helper.nft.mintCollection(minter, {});214    const {tokenId} = await collection.mintToken(minter, {Ethereum: caller});215216    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);217    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller);218219    {220      const result = await contract.methods.burn(tokenId).send({from: caller});221222      const event = result.events.Transfer;223      expect(event.address).to.be.equal(collectionAddress);224      expect(event.returnValues.from).to.be.equal(caller);225      expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');226      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);227    }228  });229230  itEth('Can perform approve()', async ({helper}) => {231    const owner = await helper.eth.createAccountWithBalance(donor);232    const spender = helper.eth.createAccount();233234    const collection = await helper.nft.mintCollection(minter, {});235    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});236237    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);238    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);239240    {241      const result = await contract.methods.approve(spender, tokenId).send({from: owner});242243      const event = result.events.Approval;244      expect(event.address).to.be.equal(collectionAddress);245      expect(event.returnValues.owner).to.be.equal(owner);246      expect(event.returnValues.approved).to.be.equal(spender);247      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);248    }249  });250251  itEth('Can perform burnFromCross()', async ({helper}) => {252    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});253254    const owner = bob;255    const spender = await helper.eth.createAccountWithBalance(donor, 100n);256257    const token = await collection.mintToken(minter, {Substrate: owner.address});258259    const address = helper.ethAddress.fromCollectionId(collection.collectionId);260    const contract = helper.ethNativeContract.collection(address, 'nft');261262    {263      await token.approve(owner, {Ethereum: spender});264      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);265      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender});266      const events = result.events.Transfer;267268      expect(events).to.be.like({269        address,270        event: 'Transfer',271        returnValues: {272          from: helper.address.substrateToEth(owner.address),273          to: '0x0000000000000000000000000000000000000000',274          tokenId: token.tokenId.toString(),275        },276      });277    }278  });279280  itEth('Can perform approveCross()', async ({helper}) => {281    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});282283    const owner = await helper.eth.createAccountWithBalance(donor, 100n);284    const receiver = charlie;285286    const token = await collection.mintToken(minter, {Ethereum: owner});287288    const address = helper.ethAddress.fromCollectionId(collection.collectionId);289    const contract = helper.ethNativeContract.collection(address, 'nft');290291    {292      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);293      const result = await contract.methods.approveCross(recieverCross, token.tokenId).send({from: owner});294      const event = result.events.Approval;295      expect(event).to.be.like({296        address: helper.ethAddress.fromCollectionId(collection.collectionId),297        event: 'Approval',298        returnValues: {299          owner,300          approved: helper.address.substrateToEth(receiver.address),301          tokenId: token.tokenId.toString(),302        },303      });304    }305  });306307  itEth('Can perform transferFrom()', async ({helper}) => {308    const owner = await helper.eth.createAccountWithBalance(donor);309    const spender = await helper.eth.createAccountWithBalance(donor);310    const receiver = helper.eth.createAccount();311312    const collection = await helper.nft.mintCollection(minter, {});313    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});314315    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);316    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);317318    await contract.methods.approve(spender, tokenId).send({from: owner});319320    {321      const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender});322323      const event = result.events.Transfer;324      expect(event.address).to.be.equal(collectionAddress);325      expect(event.returnValues.from).to.be.equal(owner);326      expect(event.returnValues.to).to.be.equal(receiver);327      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);328    }329330    {331      const balance = await contract.methods.balanceOf(receiver).call();332      expect(+balance).to.equal(1);333    }334335    {336      const balance = await contract.methods.balanceOf(owner).call();337      expect(+balance).to.equal(0);338    }339  });340341  itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {342    const minter = await privateKey('//Alice');343    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});344345    const owner = await privateKey('//Bob');346    const spender = await helper.eth.createAccountWithBalance(donor);347    const receiver = await privateKey('//Charlie');348349    const token = await collection.mintToken(minter, {Substrate: owner.address});350351    const address = helper.ethAddress.fromCollectionId(collection.collectionId);352    const contract = helper.ethNativeContract.collection(address, 'nft');353354    await token.approve(owner, {Ethereum: spender});355356    {357      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);358      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);359      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});360      const event = result.events.Transfer;361      expect(event).to.be.like({362        address: helper.ethAddress.fromCollectionId(collection.collectionId),363        event: 'Transfer',364        returnValues: {365          from: helper.address.substrateToEth(owner.address),366          to: helper.address.substrateToEth(receiver.address),367          tokenId: token.tokenId.toString(),368        },369      });370    }371372    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});373  });374375  itEth('Can perform transfer()', async ({helper}) => {376    const collection = await helper.nft.mintCollection(minter, {});377    const owner = await helper.eth.createAccountWithBalance(donor);378    const receiver = helper.eth.createAccount();379380    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});381382    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);383    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);384385    {386      const result = await contract.methods.transfer(receiver, tokenId).send({from: owner});387388      const event = result.events.Transfer;389      expect(event.address).to.be.equal(collectionAddress);390      expect(event.returnValues.from).to.be.equal(owner);391      expect(event.returnValues.to).to.be.equal(receiver);392      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);393    }394395    {396      const balance = await contract.methods.balanceOf(owner).call();397      expect(+balance).to.equal(0);398    }399400    {401      const balance = await contract.methods.balanceOf(receiver).call();402      expect(+balance).to.equal(1);403    }404  });405});406407describe('NFT: Fees', () => {408  let donor: IKeyringPair;409  let alice: IKeyringPair;410  let bob: IKeyringPair;411  let charlie: IKeyringPair;412413  before(async function() {414    await usingEthPlaygrounds(async (helper, privateKey) => {415      donor = await privateKey({filename: __filename});416      [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);417    });418  });419420  itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {421    const owner = await helper.eth.createAccountWithBalance(donor);422    const spender = helper.eth.createAccount();423424    const collection = await helper.nft.mintCollection(alice, {});425    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});426427    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);428429    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, tokenId).send({from: owner}));430    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));431  });432433  itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {434    const owner = await helper.eth.createAccountWithBalance(donor);435    const spender = await helper.eth.createAccountWithBalance(donor);436437    const collection = await helper.nft.mintCollection(alice, {});438    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});439440    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);441442    await contract.methods.approve(spender, tokenId).send({from: owner});443444    const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender}));445    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));446  });447448  itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {449    const collectionMinter = alice;450    const owner = bob;451    const receiver = charlie;452    const collection = await helper.nft.mintCollection(collectionMinter, {name: 'A', description: 'B', tokenPrefix: 'C'});453454    const spender = await helper.eth.createAccountWithBalance(donor, 100n);455456    const token = await collection.mintToken(collectionMinter, {Substrate: owner.address});457458    const address = helper.ethAddress.fromCollectionId(collection.collectionId);459    const contract = helper.ethNativeContract.collection(address, 'nft');460461    await token.approve(owner, {Ethereum: spender});462463    {464      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);465      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);466      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});467      const event = result.events.Transfer;468      expect(event).to.be.like({469        address: helper.ethAddress.fromCollectionId(collection.collectionId),470        event: 'Transfer',471        returnValues: {472          from: helper.address.substrateToEth(owner.address),473          to: helper.address.substrateToEth(receiver.address),474          tokenId: token.tokenId.toString(),475        },476      });477    }478479    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});480  });481482  itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {483    const owner = await helper.eth.createAccountWithBalance(donor);484    const receiver = helper.eth.createAccount();485486    const collection = await helper.nft.mintCollection(alice, {});487    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});488489    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);490491    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner}));492    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));493  });494});495496describe('NFT: Substrate calls', () => {497  let donor: IKeyringPair;498  let alice: IKeyringPair;499500  before(async function() {501    await usingEthPlaygrounds(async (helper, privateKey) => {502      donor = await privateKey({filename: __filename});503      [alice] = await helper.arrange.createAccounts([20n], donor);504    });505  });506507  itEth('Events emitted for mint()', async ({helper}) => {508    const collection = await helper.nft.mintCollection(alice, {});509    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);510    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');511512    const events: any = [];513    contract.events.allEvents((_: any, event: any) => {514      events.push(event);515    });516517    const {tokenId} = await collection.mintToken(alice);518    if (events.length == 0) await helper.wait.newBlocks(1);519    const event = events[0];520521    expect(event.event).to.be.equal('Transfer');522    expect(event.address).to.be.equal(collectionAddress);523    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');524    expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(alice.address));525    expect(event.returnValues.tokenId).to.be.equal(tokenId.toString());526  });527528  itEth('Events emitted for burn()', async ({helper}) => {529    const collection = await helper.nft.mintCollection(alice, {});530    const token = await collection.mintToken(alice);531532    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);533    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');534535    const events: any = [];536    contract.events.allEvents((_: any, event: any) => {537      events.push(event);538    });539540    await token.burn(alice);541    if (events.length == 0) await helper.wait.newBlocks(1);542    const event = events[0];543544    expect(event.event).to.be.equal('Transfer');545    expect(event.address).to.be.equal(collectionAddress);546    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));547    expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');548    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());549  });550551  itEth('Events emitted for approve()', async ({helper}) => {552    const receiver = helper.eth.createAccount();553554    const collection = await helper.nft.mintCollection(alice, {});555    const token = await collection.mintToken(alice);556557    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);558    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');559560    const events: any = [];561    contract.events.allEvents((_: any, event: any) => {562      events.push(event);563    });564565    await token.approve(alice, {Ethereum: receiver});566    if (events.length == 0) await helper.wait.newBlocks(1);567    const event = events[0];568569    expect(event.event).to.be.equal('Approval');570    expect(event.address).to.be.equal(collectionAddress);571    expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));572    expect(event.returnValues.approved).to.be.equal(receiver);573    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());574  });575576  itEth('Events emitted for transferFrom()', async ({helper}) => {577    const [bob] = await helper.arrange.createAccounts([10n], donor);578    const receiver = helper.eth.createAccount();579580    const collection = await helper.nft.mintCollection(alice, {});581    const token = await collection.mintToken(alice);582    await token.approve(alice, {Substrate: bob.address});583584    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);585    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');586587    const events: any = [];588    contract.events.allEvents((_: any, event: any) => {589      events.push(event);590    });591592    await token.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver});593594    if (events.length == 0) await helper.wait.newBlocks(1);595    const event = events[0];596597    expect(event.address).to.be.equal(collectionAddress);598    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));599    expect(event.returnValues.to).to.be.equal(receiver);600    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);601  });602603  itEth('Events emitted for transfer()', async ({helper}) => {604    const receiver = helper.eth.createAccount();605606    const collection = await helper.nft.mintCollection(alice, {});607    const token = await collection.mintToken(alice);608609    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);610    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');611612    const events: any = [];613    contract.events.allEvents((_: any, event: any) => {614      events.push(event);615    });616617    await token.transfer(alice, {Ethereum: receiver});618619    if (events.length == 0) await helper.wait.newBlocks(1);620    const event = events[0];621622    expect(event.address).to.be.equal(collectionAddress);623    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));624    expect(event.returnValues.to).to.be.equal(receiver);625    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);626  });627});628629describe('Common metadata', () => {630  let donor: IKeyringPair;631  let alice: IKeyringPair;632633  before(async function() {634    await usingEthPlaygrounds(async (helper, privateKey) => {635      donor = await privateKey({filename: __filename});636      [alice] = await helper.arrange.createAccounts([20n], donor);637    });638  });639640  itEth('Returns collection name', async ({helper}) => {641    const caller = await helper.eth.createAccountWithBalance(donor);642    const tokenPropertyPermissions = [{643      key: 'URI',644      permission: {645        mutable: true,646        collectionAdmin: true,647        tokenOwner: false,648      },649    }];650    const collection = await helper.nft.mintCollection(651      alice,652      {653        name: 'oh River',654        tokenPrefix: 'CHANGE',655        properties: [{key: 'ERC721Metadata', value: '1'}],656        tokenPropertyPermissions,657      },658    );659660    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);661    const name = await contract.methods.name().call();662    expect(name).to.equal('oh River');663  });664665  itEth('Returns symbol name', async ({helper}) => {666    const caller = await helper.eth.createAccountWithBalance(donor);667    const tokenPropertyPermissions = [{668      key: 'URI',669      permission: {670        mutable: true,671        collectionAdmin: true,672        tokenOwner: false,673      },674    }];675    const collection = await helper.nft.mintCollection(676      alice,677      {678        name: 'oh River',679        tokenPrefix: 'CHANGE',680        properties: [{key: 'ERC721Metadata', value: '1'}],681        tokenPropertyPermissions,682      },683    );684685    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);686    const symbol = await contract.methods.symbol().call();687    expect(symbol).to.equal('CHANGE');688  });689});
after · tests/src/eth/nonFungible.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util';18import {IKeyringPair} from '@polkadot/types/types';19import {Contract} from 'web3-eth-contract';202122describe('NFT: Information getting', () => {23  let donor: IKeyringPair;24  let alice: IKeyringPair;2526  before(async function() {27    await usingEthPlaygrounds(async (helper, privateKey) => {28      donor = await privateKey({filename: __filename});29      [alice] = await helper.arrange.createAccounts([10n], donor);30    });31  });3233  itEth('totalSupply', async ({helper}) => {34    const collection = await helper.nft.mintCollection(alice, {});35    await collection.mintToken(alice);3637    const caller = await helper.eth.createAccountWithBalance(donor);3839    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);40    const totalSupply = await contract.methods.totalSupply().call();4142    expect(totalSupply).to.equal('1');43  });4445  itEth('balanceOf', async ({helper}) => {46    const collection = await helper.nft.mintCollection(alice, {});47    const caller = await helper.eth.createAccountWithBalance(donor);4849    await collection.mintToken(alice, {Ethereum: caller});50    await collection.mintToken(alice, {Ethereum: caller});51    await collection.mintToken(alice, {Ethereum: caller});5253    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);54    const balance = await contract.methods.balanceOf(caller).call();5556    expect(balance).to.equal('3');57  });5859  itEth('ownerOf', async ({helper}) => {60    const collection = await helper.nft.mintCollection(alice, {});61    const caller = await helper.eth.createAccountWithBalance(donor);6263    const token = await collection.mintToken(alice, {Ethereum: caller});6465    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);6667    const owner = await contract.methods.ownerOf(token.tokenId).call();6869    expect(owner).to.equal(caller);70  });7172  itEth('name/symbol is available regardless of ERC721Metadata support', async ({helper}) => {73    const collection = await helper.nft.mintCollection(alice, {name: 'test', tokenPrefix: 'TEST'});74    const caller = helper.eth.createAccount();7576    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);7778    expect(await contract.methods.name().call()).to.equal('test');79    expect(await contract.methods.symbol().call()).to.equal('TEST');80  });81});8283describe('Check ERC721 token URI for NFT', () => {84  let donor: IKeyringPair;8586  before(async function() {87    await usingEthPlaygrounds(async (_helper, privateKey) => {88      donor = await privateKey({filename: __filename});89    });90  });9192  async function setup(helper: EthUniqueHelper, baseUri: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> {93    const owner = await helper.eth.createAccountWithBalance(donor);94    const receiver = helper.eth.createAccount();9596    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', 'a', 'b', baseUri);97    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);9899    const result = await contract.methods.mint(receiver).send();100    const tokenId = result.events.Transfer.returnValues.tokenId;101    expect(tokenId).to.be.equal('1');102103    if (propertyKey && propertyValue) {104      // Set URL or suffix105      await contract.methods.setProperty(tokenId, propertyKey, Buffer.from(propertyValue)).send();106    }107108    const event = result.events.Transfer;109    expect(event.address).to.be.equal(collectionAddress);110    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');111    expect(event.returnValues.to).to.be.equal(receiver);112    expect(event.returnValues.tokenId).to.be.equal(tokenId);113114    return {contract, nextTokenId: tokenId};115  }116117  itEth('Empty tokenURI', async ({helper}) => {118    const {contract, nextTokenId} = await setup(helper, '');119    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('');120  });121122  itEth('TokenURI from url', async ({helper}) => {123    const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URI', 'Token URI');124    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI');125  });126127  itEth('TokenURI from baseURI', async ({helper}) => {128    const {contract, nextTokenId} = await setup(helper, 'BaseURI_');129    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_');130  });131132  itEth('TokenURI from baseURI + suffix', async ({helper}) => {133    const suffix = '/some/suffix';134    const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URISuffix', suffix);135    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix);136  });137});138139describe('NFT: Plain calls', () => {140  let donor: IKeyringPair;141  let minter: IKeyringPair;142  let bob: IKeyringPair;143  let charlie: IKeyringPair;144145  before(async function() {146    await usingEthPlaygrounds(async (helper, privateKey) => {147      donor = await privateKey({filename: __filename});148      [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);149    });150  });151152  itEth('Can perform mint()', async ({helper}) => {153    const owner = await helper.eth.createAccountWithBalance(donor);154    const receiver = helper.eth.createAccount();155156    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', '6', '6', '');157    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);158159    const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();160    const tokenId = result.events.Transfer.returnValues.tokenId;161    expect(tokenId).to.be.equal('1');162163    const event = result.events.Transfer;164    expect(event.address).to.be.equal(collectionAddress);165    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');166    expect(event.returnValues.to).to.be.equal(receiver);167168    expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');169170    // TODO: this wont work right now, need release 919000 first171    // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send();172    // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();173    // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);174  });175176  //TODO: CORE-302 add eth methods177  itEth.skip('Can perform mintBulk()', async ({helper}) => {178    const caller = await helper.eth.createAccountWithBalance(donor);179    const receiver = helper.eth.createAccount();180181    const collection = await helper.nft.mintCollection(minter);182    await collection.addAdmin(minter, {Ethereum: caller});183184    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);185    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller);186    {187      const bulkSize = 3;188      const nextTokenId = await contract.methods.nextTokenId().call();189      expect(nextTokenId).to.be.equal('1');190      const result = await contract.methods.mintBulkWithTokenURI(191        receiver,192        Array.from({length: bulkSize}, (_, i) => (193          [+nextTokenId + i, `Test URI ${i}`]194        )),195      ).send({from: caller});196197      const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);198      for (let i = 0; i < bulkSize; i++) {199        const event = events[i];200        expect(event.address).to.equal(collectionAddress);201        expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');202        expect(event.returnValues.to).to.equal(receiver);203        expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`);204205        expect(await contract.methods.tokenURI(+nextTokenId + i).call()).to.be.equal(`Test URI ${i}`);206      }207    }208  });209210  itEth('Can perform burn()', async ({helper}) => {211    const caller = await helper.eth.createAccountWithBalance(donor);212213    const collection = await helper.nft.mintCollection(minter, {});214    const {tokenId} = await collection.mintToken(minter, {Ethereum: caller});215216    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);217    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller);218219    {220      const result = await contract.methods.burn(tokenId).send({from: caller});221222      const event = result.events.Transfer;223      expect(event.address).to.be.equal(collectionAddress);224      expect(event.returnValues.from).to.be.equal(caller);225      expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');226      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);227    }228  });229230  itEth('Can perform approve()', async ({helper}) => {231    const owner = await helper.eth.createAccountWithBalance(donor);232    const spender = helper.eth.createAccount();233234    const collection = await helper.nft.mintCollection(minter, {});235    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});236237    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);238    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);239240    {241      const result = await contract.methods.approve(spender, tokenId).send({from: owner});242243      const event = result.events.Approval;244      expect(event.address).to.be.equal(collectionAddress);245      expect(event.returnValues.owner).to.be.equal(owner);246      expect(event.returnValues.approved).to.be.equal(spender);247      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);248    }249  });250251  itEth('Can perform burnFromCross()', async ({helper}) => {252    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});253254    const owner = bob;255    const spender = await helper.eth.createAccountWithBalance(donor, 100n);256257    const token = await collection.mintToken(minter, {Substrate: owner.address});258259    const address = helper.ethAddress.fromCollectionId(collection.collectionId);260    const contract = helper.ethNativeContract.collection(address, 'nft');261262    {263      await token.approve(owner, {Ethereum: spender});264      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);265      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender});266      const events = result.events.Transfer;267268      expect(events).to.be.like({269        address,270        event: 'Transfer',271        returnValues: {272          from: helper.address.substrateToEth(owner.address),273          to: '0x0000000000000000000000000000000000000000',274          tokenId: token.tokenId.toString(),275        },276      });277    }278  });279280  itEth('Can perform approveCross()', async ({helper}) => {281    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});282283    const owner = await helper.eth.createAccountWithBalance(donor, 100n);284    const receiver = charlie;285286    const token = await collection.mintToken(minter, {Ethereum: owner});287288    const address = helper.ethAddress.fromCollectionId(collection.collectionId);289    const contract = helper.ethNativeContract.collection(address, 'nft');290291    {292      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);293      const result = await contract.methods.approveCross(recieverCross, token.tokenId).send({from: owner});294      const event = result.events.Approval;295      expect(event).to.be.like({296        address: helper.ethAddress.fromCollectionId(collection.collectionId),297        event: 'Approval',298        returnValues: {299          owner,300          approved: helper.address.substrateToEth(receiver.address),301          tokenId: token.tokenId.toString(),302        },303      });304    }305  });306307  itEth('Can perform transferFrom()', async ({helper}) => {308    const owner = await helper.eth.createAccountWithBalance(donor);309    const spender = await helper.eth.createAccountWithBalance(donor);310    const receiver = helper.eth.createAccount();311312    const collection = await helper.nft.mintCollection(minter, {});313    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});314315    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);316    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);317318    await contract.methods.approve(spender, tokenId).send({from: owner});319320    {321      const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender});322323      const event = result.events.Transfer;324      expect(event.address).to.be.equal(collectionAddress);325      expect(event.returnValues.from).to.be.equal(owner);326      expect(event.returnValues.to).to.be.equal(receiver);327      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);328    }329330    {331      const balance = await contract.methods.balanceOf(receiver).call();332      expect(+balance).to.equal(1);333    }334335    {336      const balance = await contract.methods.balanceOf(owner).call();337      expect(+balance).to.equal(0);338    }339  });340341  itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {342    const minter = await privateKey('//Alice');343    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});344345    const owner = await privateKey('//Bob');346    const spender = await helper.eth.createAccountWithBalance(donor);347    const receiver = await privateKey('//Charlie');348349    const token = await collection.mintToken(minter, {Substrate: owner.address});350351    const address = helper.ethAddress.fromCollectionId(collection.collectionId);352    const contract = helper.ethNativeContract.collection(address, 'nft');353354    await token.approve(owner, {Ethereum: spender});355356    {357      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);358      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);359      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});360      const event = result.events.Transfer;361      expect(event).to.be.like({362        address: helper.ethAddress.fromCollectionId(collection.collectionId),363        event: 'Transfer',364        returnValues: {365          from: helper.address.substrateToEth(owner.address),366          to: helper.address.substrateToEth(receiver.address),367          tokenId: token.tokenId.toString(),368        },369      });370    }371372    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});373  });374375  itEth('Can perform transfer()', async ({helper}) => {376    const collection = await helper.nft.mintCollection(minter, {});377    const owner = await helper.eth.createAccountWithBalance(donor);378    const receiver = helper.eth.createAccount();379380    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});381382    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);383    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);384385    {386      const result = await contract.methods.transfer(receiver, tokenId).send({from: owner});387388      const event = result.events.Transfer;389      expect(event.address).to.be.equal(collectionAddress);390      expect(event.returnValues.from).to.be.equal(owner);391      expect(event.returnValues.to).to.be.equal(receiver);392      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);393    }394395    {396      const balance = await contract.methods.balanceOf(owner).call();397      expect(+balance).to.equal(0);398    }399400    {401      const balance = await contract.methods.balanceOf(receiver).call();402      expect(+balance).to.equal(1);403    }404  });405  406  itEth('Can perform transferCross()', async ({helper}) => {407    const collection = await helper.nft.mintCollection(minter, {});408    const owner = await helper.eth.createAccountWithBalance(donor);409    const receiver = helper.eth.createAccount();410    const to = helper.ethCrossAccount.fromAddress(receiver);411    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});412413    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);414    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);415416    {417      const result = await contract.methods.transferCross(to, tokenId).send({from: owner});418419      const event = result.events.Transfer;420      expect(event.address).to.be.equal(collectionAddress);421      expect(event.returnValues.from).to.be.equal(owner);422      expect(event.returnValues.to).to.be.equal(receiver);423      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);424    }425426    {427      const balance = await contract.methods.balanceOf(owner).call();428      expect(+balance).to.equal(0);429    }430431    {432      const balance = await contract.methods.balanceOf(receiver).call();433      expect(+balance).to.equal(1);434    }435  });436});437438describe('NFT: Fees', () => {439  let donor: IKeyringPair;440  let alice: IKeyringPair;441  let bob: IKeyringPair;442  let charlie: IKeyringPair;443444  before(async function() {445    await usingEthPlaygrounds(async (helper, privateKey) => {446      donor = await privateKey({filename: __filename});447      [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);448    });449  });450451  itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {452    const owner = await helper.eth.createAccountWithBalance(donor);453    const spender = helper.eth.createAccount();454455    const collection = await helper.nft.mintCollection(alice, {});456    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});457458    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);459460    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, tokenId).send({from: owner}));461    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));462  });463464  itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {465    const owner = await helper.eth.createAccountWithBalance(donor);466    const spender = await helper.eth.createAccountWithBalance(donor);467468    const collection = await helper.nft.mintCollection(alice, {});469    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});470471    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);472473    await contract.methods.approve(spender, tokenId).send({from: owner});474475    const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender}));476    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));477  });478479  itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {480    const collectionMinter = alice;481    const owner = bob;482    const receiver = charlie;483    const collection = await helper.nft.mintCollection(collectionMinter, {name: 'A', description: 'B', tokenPrefix: 'C'});484485    const spender = await helper.eth.createAccountWithBalance(donor, 100n);486487    const token = await collection.mintToken(collectionMinter, {Substrate: owner.address});488489    const address = helper.ethAddress.fromCollectionId(collection.collectionId);490    const contract = helper.ethNativeContract.collection(address, 'nft');491492    await token.approve(owner, {Ethereum: spender});493494    {495      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);496      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);497      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});498      const event = result.events.Transfer;499      expect(event).to.be.like({500        address: helper.ethAddress.fromCollectionId(collection.collectionId),501        event: 'Transfer',502        returnValues: {503          from: helper.address.substrateToEth(owner.address),504          to: helper.address.substrateToEth(receiver.address),505          tokenId: token.tokenId.toString(),506        },507      });508    }509510    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});511  });512513  itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {514    const owner = await helper.eth.createAccountWithBalance(donor);515    const receiver = helper.eth.createAccount();516517    const collection = await helper.nft.mintCollection(alice, {});518    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});519520    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);521522    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner}));523    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));524  });525});526527describe('NFT: Substrate calls', () => {528  let donor: IKeyringPair;529  let alice: IKeyringPair;530531  before(async function() {532    await usingEthPlaygrounds(async (helper, privateKey) => {533      donor = await privateKey({filename: __filename});534      [alice] = await helper.arrange.createAccounts([20n], donor);535    });536  });537538  itEth('Events emitted for mint()', async ({helper}) => {539    const collection = await helper.nft.mintCollection(alice, {});540    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);541    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');542543    const events: any = [];544    contract.events.allEvents((_: any, event: any) => {545      events.push(event);546    });547548    const {tokenId} = await collection.mintToken(alice);549    if (events.length == 0) await helper.wait.newBlocks(1);550    const event = events[0];551552    expect(event.event).to.be.equal('Transfer');553    expect(event.address).to.be.equal(collectionAddress);554    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');555    expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(alice.address));556    expect(event.returnValues.tokenId).to.be.equal(tokenId.toString());557  });558559  itEth('Events emitted for burn()', async ({helper}) => {560    const collection = await helper.nft.mintCollection(alice, {});561    const token = await collection.mintToken(alice);562563    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);564    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');565566    const events: any = [];567    contract.events.allEvents((_: any, event: any) => {568      events.push(event);569    });570571    await token.burn(alice);572    if (events.length == 0) await helper.wait.newBlocks(1);573    const event = events[0];574575    expect(event.event).to.be.equal('Transfer');576    expect(event.address).to.be.equal(collectionAddress);577    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));578    expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');579    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());580  });581582  itEth('Events emitted for approve()', async ({helper}) => {583    const receiver = helper.eth.createAccount();584585    const collection = await helper.nft.mintCollection(alice, {});586    const token = await collection.mintToken(alice);587588    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);589    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');590591    const events: any = [];592    contract.events.allEvents((_: any, event: any) => {593      events.push(event);594    });595596    await token.approve(alice, {Ethereum: receiver});597    if (events.length == 0) await helper.wait.newBlocks(1);598    const event = events[0];599600    expect(event.event).to.be.equal('Approval');601    expect(event.address).to.be.equal(collectionAddress);602    expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));603    expect(event.returnValues.approved).to.be.equal(receiver);604    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());605  });606607  itEth('Events emitted for transferFrom()', async ({helper}) => {608    const [bob] = await helper.arrange.createAccounts([10n], donor);609    const receiver = helper.eth.createAccount();610611    const collection = await helper.nft.mintCollection(alice, {});612    const token = await collection.mintToken(alice);613    await token.approve(alice, {Substrate: bob.address});614615    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);616    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');617618    const events: any = [];619    contract.events.allEvents((_: any, event: any) => {620      events.push(event);621    });622623    await token.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver});624625    if (events.length == 0) await helper.wait.newBlocks(1);626    const event = events[0];627628    expect(event.address).to.be.equal(collectionAddress);629    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));630    expect(event.returnValues.to).to.be.equal(receiver);631    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);632  });633634  itEth('Events emitted for transfer()', async ({helper}) => {635    const receiver = helper.eth.createAccount();636637    const collection = await helper.nft.mintCollection(alice, {});638    const token = await collection.mintToken(alice);639640    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);641    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');642643    const events: any = [];644    contract.events.allEvents((_: any, event: any) => {645      events.push(event);646    });647648    await token.transfer(alice, {Ethereum: receiver});649650    if (events.length == 0) await helper.wait.newBlocks(1);651    const event = events[0];652653    expect(event.address).to.be.equal(collectionAddress);654    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));655    expect(event.returnValues.to).to.be.equal(receiver);656    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);657  });658});659660describe('Common metadata', () => {661  let donor: IKeyringPair;662  let alice: IKeyringPair;663664  before(async function() {665    await usingEthPlaygrounds(async (helper, privateKey) => {666      donor = await privateKey({filename: __filename});667      [alice] = await helper.arrange.createAccounts([20n], donor);668    });669  });670671  itEth('Returns collection name', async ({helper}) => {672    const caller = await helper.eth.createAccountWithBalance(donor);673    const tokenPropertyPermissions = [{674      key: 'URI',675      permission: {676        mutable: true,677        collectionAdmin: true,678        tokenOwner: false,679      },680    }];681    const collection = await helper.nft.mintCollection(682      alice,683      {684        name: 'oh River',685        tokenPrefix: 'CHANGE',686        properties: [{key: 'ERC721Metadata', value: '1'}],687        tokenPropertyPermissions,688      },689    );690691    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);692    const name = await contract.methods.name().call();693    expect(name).to.equal('oh River');694  });695696  itEth('Returns symbol name', async ({helper}) => {697    const caller = await helper.eth.createAccountWithBalance(donor);698    const tokenPropertyPermissions = [{699      key: 'URI',700      permission: {701        mutable: true,702        collectionAdmin: true,703        tokenOwner: false,704      },705    }];706    const collection = await helper.nft.mintCollection(707      alice,708      {709        name: 'oh River',710        tokenPrefix: 'CHANGE',711        properties: [{key: 'ERC721Metadata', value: '1'}],712        tokenPropertyPermissions,713      },714    );715716    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);717    const symbol = await contract.methods.symbol().call();718    expect(symbol).to.equal('CHANGE');719  });720});
modifiedtests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -269,7 +269,7 @@
           { "internalType": "string", "name": "field_0", "type": "string" },
           { "internalType": "bytes", "name": "field_1", "type": "bytes" }
         ],
-        "internalType": "struct Tuple21[]",
+        "internalType": "struct Tuple22[]",
         "name": "",
         "type": "tuple[]"
       }
@@ -293,7 +293,7 @@
           { "internalType": "address", "name": "field_0", "type": "address" },
           { "internalType": "uint256", "name": "field_1", "type": "uint256" }
         ],
-        "internalType": "struct Tuple24",
+        "internalType": "struct Tuple25",
         "name": "",
         "type": "tuple"
       }
@@ -611,7 +611,7 @@
           { "internalType": "string", "name": "field_0", "type": "string" },
           { "internalType": "bytes", "name": "field_1", "type": "bytes" }
         ],
-        "internalType": "struct Tuple21[]",
+        "internalType": "struct Tuple22[]",
         "name": "properties",
         "type": "tuple[]"
       }
@@ -682,7 +682,7 @@
           { "internalType": "string", "name": "field_0", "type": "string" },
           { "internalType": "bytes", "name": "field_1", "type": "bytes" }
         ],
-        "internalType": "struct Tuple21[]",
+        "internalType": "struct Tuple22[]",
         "name": "properties",
         "type": "tuple[]"
       }
@@ -778,6 +778,24 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "eth", "type": "address" },
+          { "internalType": "uint256", "name": "sub", "type": "uint256" }
+        ],
+        "internalType": "struct EthCrossAccount",
+        "name": "to",
+        "type": "tuple"
+      },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "transferCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "from", "type": "address" },
       { "internalType": "address", "name": "to", "type": "address" },
       { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
modifiedtests/src/eth/reFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -359,6 +359,37 @@
       expect(+balance).to.equal(1);
     }
   });
+  
+  itEth('Can perform transferCross()', async ({helper}) => {
+    const caller = await helper.eth.createAccountWithBalance(donor);
+    const receiver = helper.eth.createAccount();
+    const to = helper.ethCrossAccount.fromAddress(receiver);
+    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry', '6', '6');
+    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
+
+    const result = await contract.methods.mint(caller).send();
+    const tokenId = result.events.Transfer.returnValues.tokenId;
+
+    {
+      const result = await contract.methods.transferCross(to, tokenId).send({from: caller});
+
+      const event = result.events.Transfer;
+      expect(event.address).to.equal(collectionAddress);
+      expect(event.returnValues.from).to.equal(caller);
+      expect(event.returnValues.to).to.equal(receiver);
+      expect(event.returnValues.tokenId).to.equal(tokenId.toString());
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(caller).call();
+      expect(+balance).to.equal(0);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(receiver).call();
+      expect(+balance).to.equal(1);
+    }
+  });
 
   itEth('transfer event on transfer from partial ownership to full ownership', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
modifiedtests/src/eth/reFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/reFungibleAbi.json
+++ b/tests/src/eth/reFungibleAbi.json
@@ -251,7 +251,7 @@
           { "internalType": "string", "name": "field_0", "type": "string" },
           { "internalType": "bytes", "name": "field_1", "type": "bytes" }
         ],
-        "internalType": "struct Tuple20[]",
+        "internalType": "struct Tuple21[]",
         "name": "",
         "type": "tuple[]"
       }
@@ -275,7 +275,7 @@
           { "internalType": "address", "name": "field_0", "type": "address" },
           { "internalType": "uint256", "name": "field_1", "type": "uint256" }
         ],
-        "internalType": "struct Tuple23",
+        "internalType": "struct Tuple24",
         "name": "",
         "type": "tuple"
       }
@@ -593,7 +593,7 @@
           { "internalType": "string", "name": "field_0", "type": "string" },
           { "internalType": "bytes", "name": "field_1", "type": "bytes" }
         ],
-        "internalType": "struct Tuple20[]",
+        "internalType": "struct Tuple21[]",
         "name": "properties",
         "type": "tuple[]"
       }
@@ -664,7 +664,7 @@
           { "internalType": "string", "name": "field_0", "type": "string" },
           { "internalType": "bytes", "name": "field_1", "type": "bytes" }
         ],
-        "internalType": "struct Tuple20[]",
+        "internalType": "struct Tuple21[]",
         "name": "properties",
         "type": "tuple[]"
       }
@@ -769,6 +769,24 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "eth", "type": "address" },
+          { "internalType": "uint256", "name": "sub", "type": "uint256" }
+        ],
+        "internalType": "struct EthCrossAccount",
+        "name": "to",
+        "type": "tuple"
+      },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "transferCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "from", "type": "address" },
       { "internalType": "address", "name": "to", "type": "address" },
       { "internalType": "uint256", "name": "tokenId", "type": "uint256" }