git.delta.rocks / unique-network / refs/commits / 984c76e4f44f

difftreelog

CORE-386 Implement eth methods

Trubnikov Sergey2022-06-01parent: #b83ad95.patch.diff
in: master

5 files changed

modifiedpallets/common/src/erc.rsdiffbeforeafterboth
--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -253,6 +253,42 @@
 		save(self);
 		Ok(())
 	}
+
+	fn set_access(&mut self, caller: caller, mode: string) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		self.check_is_owner_or_admin(&caller)
+			.map_err(dispatch_to_evm::<T>)?;
+		self.collection.permissions.access = Some(match mode.as_str() {
+			"Normal" => AccessMode::Normal,
+			"AllowList" => AccessMode::AllowList,
+			_ => return Err("Not supported access mode".into()),
+		});
+		save(self);
+		Ok(())
+	}
+
+	fn add_to_allow_list(&self, caller: caller, user: address) -> Result<void> {
+		let caller = check_is_owner_or_admin(caller, self)?;
+		let user = T::CrossAccountId::from_eth(user);
+		<Pallet<T>>::toggle_allowlist(self, &caller, &user, true)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
+	fn remove_from_allow_list(&self, caller: caller, user: address) -> Result<void> {
+		let caller = check_is_owner_or_admin(caller, self)?;
+		let user = T::CrossAccountId::from_eth(user);
+		<Pallet<T>>::toggle_allowlist(self, &caller, &user, false)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
+	fn set_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {
+		check_is_owner_or_admin(caller, self)?;
+		self.collection.permissions.mint_mode = Some(mode);
+		save(self);
+		Ok(())
+	}
 }
 
 fn check_is_owner<T: Config>(caller: caller, collection: &CollectionHandle<T>) -> Result<void> {
@@ -263,10 +299,7 @@
 	Ok(())
 }
 
-fn check_is_owner_or_admin<T: Config>(
-	caller: caller,
-	collection: &CollectionHandle<T>,
-) -> Result<T::CrossAccountId> {
+fn check_is_owner_or_admin<T: Config>(caller: caller, collection: &CollectionHandle<T>) -> Result<T::CrossAccountId> {
 	let caller = T::CrossAccountId::from_eth(caller);
 	collection
 		.check_is_owner_or_admin(&caller)
@@ -274,11 +307,7 @@
 	Ok(caller)
 }
 
-fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {
-	// TODO possibly delete for the lack of transaction
-	collection
-		.check_is_internal()
-		.map_err(dispatch_to_evm::<T>)?;
+fn save<T: Config>(collection: &CollectionHandle<T>) {
 	<crate::CollectionById<T>>::insert(collection.id, collection.collection.clone());
 	Ok(())
 }
modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -51,105 +51,6 @@
 	event MintingFinished();
 }
 
-// Selector: 3a54513b
-contract Collection is Dummy, ERC165 {
-	// Selector: setCollectionProperty(string,bytes) 2f073f66
-	function setCollectionProperty(string memory key, bytes memory value)
-		public
-	{
-		require(false, stub_error);
-		key;
-		value;
-		dummy = 0;
-	}
-
-	// Selector: deleteCollectionProperty(string) 7b7debce
-	function deleteCollectionProperty(string memory key) public {
-		require(false, stub_error);
-		key;
-		dummy = 0;
-	}
-
-	// Throws error if key not found
-	//
-	// Selector: collectionProperty(string) cf24fd6d
-	function collectionProperty(string memory key)
-		public
-		view
-		returns (bytes memory)
-	{
-		require(false, stub_error);
-		key;
-		dummy;
-		return hex"";
-	}
-
-	// Selector: ethSetSponsor(address) 8f9af356
-	function ethSetSponsor(address sponsor) public {
-		require(false, stub_error);
-		sponsor;
-		dummy = 0;
-	}
-
-	// Selector: ethConfirmSponsorship() a8580d1a
-	function ethConfirmSponsorship() public {
-		require(false, stub_error);
-		dummy = 0;
-	}
-
-	// Selector: setLimit(string,uint32) 68db30ca
-	function setLimit(string memory limit, uint32 value) public {
-		require(false, stub_error);
-		limit;
-		value;
-		dummy = 0;
-	}
-
-	// Selector: setLimit(string,bool) ea67e4c2
-	function setLimit(string memory limit, bool value) public {
-		require(false, stub_error);
-		limit;
-		value;
-		dummy = 0;
-	}
-
-	// Selector: contractAddress() f6b4dfb4
-	function contractAddress() public view returns (address) {
-		require(false, stub_error);
-		dummy;
-		return 0x0000000000000000000000000000000000000000;
-	}
-
-	// Selector: addAdmin(address) 70480275
-	function addAdmin(address newAdmin) public view {
-		require(false, stub_error);
-		newAdmin;
-		dummy;
-	}
-
-	// Selector: removeAdmin(address) 1785f53c
-	function removeAdmin(address admin) public view {
-		require(false, stub_error);
-		admin;
-		dummy;
-	}
-
-	// Selector: setNesting(bool) e8fc50dd
-	function setNesting(bool enable) public {
-		require(false, stub_error);
-		enable;
-		dummy = 0;
-	}
-
-	// Selector: setNesting(bool,address[]) 7df12a9a
-	function setNesting(bool enable, address[] memory collections) public {
-		require(false, stub_error);
-		enable;
-		collections;
-		dummy = 0;
-	}
-}
-
 // Selector: 41369377
 contract TokenProperties is Dummy, ERC165 {
 	// Selector: setTokenPropertyPermission(string,bool,bool,bool) 222d97fa
@@ -479,6 +380,133 @@
 	}
 }
 
+// Selector: f56cd7fa
+contract Collection is Dummy, ERC165 {
+	// Selector: setCollectionProperty(string,bytes) 2f073f66
+	function setCollectionProperty(string memory key, bytes memory value)
+		public
+	{
+		require(false, stub_error);
+		key;
+		value;
+		dummy = 0;
+	}
+
+	// Selector: deleteCollectionProperty(string) 7b7debce
+	function deleteCollectionProperty(string memory key) public {
+		require(false, stub_error);
+		key;
+		dummy = 0;
+	}
+
+	// Throws error if key not found
+	//
+	// Selector: collectionProperty(string) cf24fd6d
+	function collectionProperty(string memory key)
+		public
+		view
+		returns (bytes memory)
+	{
+		require(false, stub_error);
+		key;
+		dummy;
+		return hex"";
+	}
+
+	// Selector: ethSetSponsor(address) 8f9af356
+	function ethSetSponsor(address sponsor) public {
+		require(false, stub_error);
+		sponsor;
+		dummy = 0;
+	}
+
+	// Selector: ethConfirmSponsorship() a8580d1a
+	function ethConfirmSponsorship() public {
+		require(false, stub_error);
+		dummy = 0;
+	}
+
+	// Selector: setLimit(string,uint32) 68db30ca
+	function setLimit(string memory limit, uint32 value) public {
+		require(false, stub_error);
+		limit;
+		value;
+		dummy = 0;
+	}
+
+	// Selector: setLimit(string,bool) ea67e4c2
+	function setLimit(string memory limit, bool value) public {
+		require(false, stub_error);
+		limit;
+		value;
+		dummy = 0;
+	}
+
+	// Selector: contractAddress() f6b4dfb4
+	function contractAddress() public view returns (address) {
+		require(false, stub_error);
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
+
+	// Selector: addAdmin(address) 70480275
+	function addAdmin(address newAdmin) public view {
+		require(false, stub_error);
+		newAdmin;
+		dummy;
+	}
+
+	// Selector: removeAdmin(address) 1785f53c
+	function removeAdmin(address admin) public view {
+		require(false, stub_error);
+		admin;
+		dummy;
+	}
+
+	// Selector: setNesting(bool) e8fc50dd
+	function setNesting(bool enable) public {
+		require(false, stub_error);
+		enable;
+		dummy = 0;
+	}
+
+	// Selector: setNesting(bool,address[]) 7df12a9a
+	function setNesting(bool enable, address[] memory collections) public {
+		require(false, stub_error);
+		enable;
+		collections;
+		dummy = 0;
+	}
+
+	// Selector: setAccess(string) 488f56aa
+	function setAccess(string memory mode) public {
+		require(false, stub_error);
+		mode;
+		dummy = 0;
+	}
+
+	// Selector: addToAllowList(address) 31f59102
+	function addToAllowList(address user) public view {
+		require(false, stub_error);
+		user;
+		dummy;
+	}
+
+	// Selector: removeFromAllowList(address) eba8dabc
+	function removeFromAllowList(address user) public view {
+		require(false, stub_error);
+		user;
+		dummy;
+	}
+
+	// Selector: setMintMode(bool) 5dea9bd5
+	function setMintMode(bool mode) public {
+		require(false, stub_error);
+		mode;
+		dummy = 0;
+	}
+}
+
 contract UniqueNFT is
 	Dummy,
 	ERC165,
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -42,51 +42,6 @@
 	event MintingFinished();
 }
 
-// Selector: 3a54513b
-interface Collection is Dummy, ERC165 {
-	// Selector: setCollectionProperty(string,bytes) 2f073f66
-	function setCollectionProperty(string memory key, bytes memory value)
-		external;
-
-	// Selector: deleteCollectionProperty(string) 7b7debce
-	function deleteCollectionProperty(string memory key) external;
-
-	// Throws error if key not found
-	//
-	// Selector: collectionProperty(string) cf24fd6d
-	function collectionProperty(string memory key)
-		external
-		view
-		returns (bytes memory);
-
-	// Selector: ethSetSponsor(address) 8f9af356
-	function ethSetSponsor(address sponsor) external;
-
-	// Selector: ethConfirmSponsorship() a8580d1a
-	function ethConfirmSponsorship() external;
-
-	// Selector: setLimit(string,uint32) 68db30ca
-	function setLimit(string memory limit, uint32 value) external;
-
-	// Selector: setLimit(string,bool) ea67e4c2
-	function setLimit(string memory limit, bool value) external;
-
-	// Selector: contractAddress() f6b4dfb4
-	function contractAddress() external view returns (address);
-
-	// Selector: addAdmin(address) 70480275
-	function addAdmin(address newAdmin) external view;
-
-	// Selector: removeAdmin(address) 1785f53c
-	function removeAdmin(address admin) external view;
-
-	// Selector: setNesting(bool) e8fc50dd
-	function setNesting(bool enable) external;
-
-	// Selector: setNesting(bool,address[]) 7df12a9a
-	function setNesting(bool enable, address[] memory collections) external;
-}
-
 // Selector: 41369377
 interface TokenProperties is Dummy, ERC165 {
 	// Selector: setTokenPropertyPermission(string,bool,bool,bool) 222d97fa
@@ -258,6 +213,63 @@
 		returns (bool);
 }
 
+// Selector: f56cd7fa
+interface Collection is Dummy, ERC165 {
+	// Selector: setCollectionProperty(string,bytes) 2f073f66
+	function setCollectionProperty(string memory key, bytes memory value)
+		external;
+
+	// Selector: deleteCollectionProperty(string) 7b7debce
+	function deleteCollectionProperty(string memory key) external;
+
+	// Throws error if key not found
+	//
+	// Selector: collectionProperty(string) cf24fd6d
+	function collectionProperty(string memory key)
+		external
+		view
+		returns (bytes memory);
+
+	// Selector: ethSetSponsor(address) 8f9af356
+	function ethSetSponsor(address sponsor) external;
+
+	// Selector: ethConfirmSponsorship() a8580d1a
+	function ethConfirmSponsorship() external;
+
+	// Selector: setLimit(string,uint32) 68db30ca
+	function setLimit(string memory limit, uint32 value) external;
+
+	// Selector: setLimit(string,bool) ea67e4c2
+	function setLimit(string memory limit, bool value) external;
+
+	// Selector: contractAddress() f6b4dfb4
+	function contractAddress() external view returns (address);
+
+	// Selector: addAdmin(address) 70480275
+	function addAdmin(address newAdmin) external view;
+
+	// Selector: removeAdmin(address) 1785f53c
+	function removeAdmin(address admin) external view;
+
+	// Selector: setNesting(bool) e8fc50dd
+	function setNesting(bool enable) external;
+
+	// Selector: setNesting(bool,address[]) 7df12a9a
+	function setNesting(bool enable, address[] memory collections) external;
+
+	// Selector: setAccess(string) 488f56aa
+	function setAccess(string memory mode) external;
+
+	// Selector: addToAllowList(address) 31f59102
+	function addToAllowList(address user) external view;
+
+	// Selector: removeFromAllowList(address) eba8dabc
+	function removeFromAllowList(address user) external view;
+
+	// Selector: setMintMode(bool) 5dea9bd5
+	function setMintMode(bool mode) external;
+}
+
 interface UniqueNFT is
 	Dummy,
 	ERC165,
modifiedtests/src/eth/contractSponsoring.test.tsdiffbeforeafterboth
--- a/tests/src/eth/contractSponsoring.test.ts
+++ b/tests/src/eth/contractSponsoring.test.ts
@@ -221,6 +221,7 @@
     expect(await helpers.methods.getSponsoringRateLimit(flipper.options.address).call()).to.be.equals('7200');
   });
 
+  //TODO: CORE-302 add eth methods
   itWeb3('Sponsoring collection from evm address via access list', async ({api, web3}) => {
     const owner = await createEthAccountWithBalance(api, web3);
     const collectionHelpers = evmCollectionHelpers(web3, owner);
@@ -228,65 +229,60 @@
     const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
     const sponsor = await createEthAccountWithBalance(api, web3);
     const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
-    result = await collectionEvm.methods.setCollectionSponsor(sponsor).send({from: owner});
+    result = await collectionEvm.methods.ethSetSponsor(sponsor).send({from: owner});
     let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
     expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
     expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));
     await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('Caller is not set as sponsor');
 
-    await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});
+    await collectionEvm.methods.ethConfirmSponsorship().send({from: sponsor});
     collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
     expect(collectionSub.sponsorship.isConfirmed).to.be.true;
     expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));
 
     const user = createEthAccount(web3);
-    let nextTokenId = await collectionEvm.methods.nextTokenId().call();
+    const nextTokenId = await collectionEvm.methods.nextTokenId().call();
     expect(nextTokenId).to.be.equal('1');
 
     const oldPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();
     expect(oldPermissions.mintMode).to.be.false;
     expect(oldPermissions.access).to.be.equal('Normal');
 
-    //TODO: change value, when enum generated
-    await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});
-    await collectionEvm.methods.addToCollectionAllowList(user).send({from: owner});
-    await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});
+    await collectionEvm.methods.setAccess('AllowList').send({from: owner});
+    await collectionEvm.methods.addToAllowList(user).send({from: owner});
+    await collectionEvm.methods.setMintMode(true).send({from: owner});
 
     const newPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();
     expect(newPermissions.mintMode).to.be.true;
     expect(newPermissions.access).to.be.equal('AllowList');
 
-    const ownerBalanceBefore = await ethBalanceViaSub(api, owner);
-    const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);
+    // const [alicesBalanceBefore] = await getBalance(api, [alicesPublicKey]);
 
-    nextTokenId = await collectionEvm.methods.nextTokenId().call({from: user});
-    expect(nextTokenId).to.be.equal('1');
-    result = await collectionEvm.methods.mintWithTokenURI(
-      user,
-      nextTokenId,
-      'Test URI',
-    ).send({from: user});
-    const events = normalizeEvents(result.events);
-    events[0].address = events[0].address.toLocaleLowerCase();
+    {
+      const nextTokenId = await collectionEvm.methods.nextTokenId().call();
+      expect(nextTokenId).to.be.equal('1');
+      const result = await collectionEvm.methods.mintWithTokenURI(
+        user,
+        nextTokenId,
+        'Test URI',
+      ).call({from: user});
+      console.log(result);
+      const events = normalizeEvents(result.events);
 
-    expect(events).to.be.deep.equal([
-      {
-        address: collectionIdAddress.toLocaleLowerCase(),
-        event: 'Transfer',
-        args: {
-          from: '0x0000000000000000000000000000000000000000',
-          to: user,
-          tokenId: nextTokenId,
+      expect(events).to.be.deep.equal([
+        {
+          collectionIdAddress,
+          event: 'Transfer',
+          args: {
+            from: '0x0000000000000000000000000000000000000000',
+            to: user,
+            tokenId: nextTokenId,
+          },
         },
-      },
-    ]);
-
-    expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+      ]);
 
-    const ownerBalanceAfter = await ethBalanceViaSub(api, owner);
-    expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);
-    const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);
-    expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;
+      expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+    }
   });
 
   itWeb3('Check that transaction via EVM spend money from sponsor address', async ({api, web3}) => {
@@ -296,22 +292,23 @@
     const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
     const sponsor = await createEthAccountWithBalance(api, web3);
     const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
-    result = await collectionEvm.methods.setCollectionSponsor(sponsor).send();
+    result = await collectionEvm.methods.ethSetSponsor(sponsor).send();
     let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
     expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
     expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));
-    await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('Caller is not set as sponsor');
+    await expect(collectionEvm.methods.ethConfirmSponsorship().call()).to.be.rejectedWith('Caller is not set as sponsor');
     const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);
-    await sponsorCollection.methods.confirmCollectionSponsorship().send();
+    await sponsorCollection.methods.ethConfirmSponsorship().send();
     collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
     expect(collectionSub.sponsorship.isConfirmed).to.be.true;
     expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));
 
     const user = createEthAccount(web3);
-    await collectionEvm.methods.addCollectionAdmin(user).send();
+    await collectionEvm.methods.addAdmin(user).send();
     
     const ownerBalanceBefore = await ethBalanceViaSub(api, owner);
     const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);
+    
   
     const userCollectionEvm = evmCollection(web3, user, collectionIdAddress);
     const nextTokenId = await userCollectionEvm.methods.nextTokenId().call();
modifiedtests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth
before · tests/src/eth/nonFungibleAbi.json
1[2  {3    "anonymous": false,4    "inputs": [5      {6        "indexed": true,7        "internalType": "address",8        "name": "owner",9        "type": "address"10      },11      {12        "indexed": true,13        "internalType": "address",14        "name": "approved",15        "type": "address"16      },17      {18        "indexed": true,19        "internalType": "uint256",20        "name": "tokenId",21        "type": "uint256"22      }23    ],24    "name": "Approval",25    "type": "event"26  },27  {28    "anonymous": false,29    "inputs": [30      {31        "indexed": true,32        "internalType": "address",33        "name": "owner",34        "type": "address"35      },36      {37        "indexed": true,38        "internalType": "address",39        "name": "operator",40        "type": "address"41      },42      {43        "indexed": false,44        "internalType": "bool",45        "name": "approved",46        "type": "bool"47      }48    ],49    "name": "ApprovalForAll",50    "type": "event"51  },52  {53    "anonymous": false,54    "inputs": [],55    "name": "MintingFinished",56    "type": "event"57  },58  {59    "anonymous": false,60    "inputs": [61      {62        "indexed": true,63        "internalType": "address",64        "name": "from",65        "type": "address"66      },67      {68        "indexed": true,69        "internalType": "address",70        "name": "to",71        "type": "address"72      },73      {74        "indexed": true,75        "internalType": "uint256",76        "name": "tokenId",77        "type": "uint256"78      }79    ],80    "name": "Transfer",81    "type": "event"82  },83  {84    "inputs": [85      { "internalType": "address", "name": "newAdmin", "type": "address" }86    ],87    "name": "addAdmin",88    "outputs": [],89    "stateMutability": "view",90    "type": "function"91  },92  {93    "inputs": [94      { "internalType": "address", "name": "approved", "type": "address" },95      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }96    ],97    "name": "approve",98    "outputs": [],99    "stateMutability": "nonpayable",100    "type": "function"101  },102  {103    "inputs": [104      { "internalType": "address", "name": "owner", "type": "address" }105    ],106    "name": "balanceOf",107    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],108    "stateMutability": "view",109    "type": "function"110  },111  {112    "inputs": [113      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }114    ],115    "name": "burn",116    "outputs": [],117    "stateMutability": "nonpayable",118    "type": "function"119  },120  {121    "inputs": [122      { "internalType": "address", "name": "from", "type": "address" },123      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }124    ],125    "name": "burnFrom",126    "outputs": [],127    "stateMutability": "nonpayable",128    "type": "function"129  },130  {131    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],132    "name": "collectionProperty",133    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],134    "stateMutability": "view",135    "type": "function"136  },137  {138    "inputs": [],139    "name": "confirmCollectionSponsorship",140    "outputs": [],141    "stateMutability": "nonpayable",142    "type": "function"143  },144  {145    "inputs": [],146    "name": "contractAddress",147    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],148    "stateMutability": "view",149    "type": "function"150  },151  {152    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],153    "name": "deleteCollectionProperty",154    "outputs": [],155    "stateMutability": "nonpayable",156    "type": "function"157  },158  {159    "inputs": [160      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },161      { "internalType": "string", "name": "key", "type": "string" }162    ],163    "name": "deleteProperty",164    "outputs": [],165    "stateMutability": "nonpayable",166    "type": "function"167  },168  {169    "inputs": [],170    "name": "finishMinting",171    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],172    "stateMutability": "nonpayable",173    "type": "function"174  },175  {176    "inputs": [177      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }178    ],179    "name": "getApproved",180    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],181    "stateMutability": "view",182    "type": "function"183  },184  {185    "inputs": [186      { "internalType": "address", "name": "owner", "type": "address" },187      { "internalType": "address", "name": "operator", "type": "address" }188    ],189    "name": "isApprovedForAll",190    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],191    "stateMutability": "view",192    "type": "function"193  },194  {195    "inputs": [196      { "internalType": "address", "name": "to", "type": "address" },197      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }198    ],199    "name": "mint",200    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],201    "stateMutability": "nonpayable",202    "type": "function"203  },204  {205    "inputs": [206      { "internalType": "address", "name": "to", "type": "address" },207      { "internalType": "uint256[]", "name": "tokenIds", "type": "uint256[]" }208    ],209    "name": "mintBulk",210    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],211    "stateMutability": "nonpayable",212    "type": "function"213  },214  {215    "inputs": [216      { "internalType": "address", "name": "to", "type": "address" },217      {218        "components": [219          { "internalType": "uint256", "name": "field_0", "type": "uint256" },220          { "internalType": "string", "name": "field_1", "type": "string" }221        ],222        "internalType": "struct Tuple0[]",223        "name": "tokens",224        "type": "tuple[]"225      }226    ],227    "name": "mintBulkWithTokenURI",228    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],229    "stateMutability": "nonpayable",230    "type": "function"231  },232  {233    "inputs": [234      { "internalType": "address", "name": "to", "type": "address" },235      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },236      { "internalType": "string", "name": "tokenUri", "type": "string" }237    ],238    "name": "mintWithTokenURI",239    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],240    "stateMutability": "nonpayable",241    "type": "function"242  },243  {244    "inputs": [],245    "name": "mintingFinished",246    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],247    "stateMutability": "view",248    "type": "function"249  },250  {251    "inputs": [],252    "name": "name",253    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],254    "stateMutability": "view",255    "type": "function"256  },257  {258    "inputs": [],259    "name": "nextTokenId",260    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],261    "stateMutability": "view",262    "type": "function"263  },264  {265    "inputs": [266      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }267    ],268    "name": "ownerOf",269    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],270    "stateMutability": "view",271    "type": "function"272  },273  {274    "inputs": [275      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },276      { "internalType": "string", "name": "key", "type": "string" }277    ],278    "name": "property",279    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],280    "stateMutability": "view",281    "type": "function"282  },283  {284    "inputs": [285      { "internalType": "address", "name": "admin", "type": "address" }286    ],287    "name": "removeAdmin",288    "outputs": [],289    "stateMutability": "view",290    "type": "function"291  },292  {293    "inputs": [294      { "internalType": "address", "name": "from", "type": "address" },295      { "internalType": "address", "name": "to", "type": "address" },296      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }297    ],298    "name": "safeTransferFrom",299    "outputs": [],300    "stateMutability": "nonpayable",301    "type": "function"302  },303  {304    "inputs": [305      { "internalType": "address", "name": "from", "type": "address" },306      { "internalType": "address", "name": "to", "type": "address" },307      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },308      { "internalType": "bytes", "name": "data", "type": "bytes" }309    ],310    "name": "safeTransferFromWithData",311    "outputs": [],312    "stateMutability": "nonpayable",313    "type": "function"314  },315  {316    "inputs": [317      { "internalType": "address", "name": "operator", "type": "address" },318      { "internalType": "bool", "name": "approved", "type": "bool" }319    ],320    "name": "setApprovalForAll",321    "outputs": [],322    "stateMutability": "nonpayable",323    "type": "function"324  },325  {326    "inputs": [{ "internalType": "uint8", "name": "mode", "type": "uint8" }],327    "name": "setCollectionAccess",328    "outputs": [],329    "stateMutability": "nonpayable",330    "type": "function"331  },332  {333    "inputs": [334      { "internalType": "string", "name": "limit", "type": "string" },335      { "internalType": "uint32", "name": "value", "type": "uint32" }336    ],337    "name": "setCollectionLimit",338    "outputs": [],339    "stateMutability": "nonpayable",340    "type": "function"341  },342  {343    "inputs": [344      { "internalType": "string", "name": "limit", "type": "string" },345      { "internalType": "bool", "name": "value", "type": "bool" }346    ],347    "name": "setCollectionLimit",348    "outputs": [],349    "stateMutability": "nonpayable",350    "type": "function"351  },352  {353    "inputs": [{ "internalType": "bool", "name": "mode", "type": "bool" }],354    "name": "setCollectionMintMode",355    "outputs": [],356    "stateMutability": "nonpayable",357    "type": "function"358  },359  {360    "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }],361    "name": "setCollectionNesting",362    "outputs": [],363    "stateMutability": "nonpayable",364    "type": "function"365  },366  {367    "inputs": [368      { "internalType": "bool", "name": "enable", "type": "bool" },369      {370        "internalType": "address[]",371        "name": "collections",372        "type": "address[]"373      }374    ],375    "name": "setCollectionNesting",376    "outputs": [],377    "stateMutability": "nonpayable",378    "type": "function"379  },380  {381    "inputs": [382      { "internalType": "string", "name": "key", "type": "string" },383      { "internalType": "bytes", "name": "value", "type": "bytes" }384    ],385    "name": "setCollectionProperty",386    "outputs": [],387    "stateMutability": "nonpayable",388    "type": "function"389  },390  {391    "inputs": [392      { "internalType": "address", "name": "sponsor", "type": "address" }393    ],394    "name": "setCollectionSponsor",395    "outputs": [],396    "stateMutability": "nonpayable",397    "type": "function"398  },399  {400    "inputs": [401      { "internalType": "bool", "name": "enable", "type": "bool" },402      {403        "internalType": "address[]",404        "name": "collections",405        "type": "address[]"406      }407    ],408    "name": "setNesting",409    "outputs": [],410    "stateMutability": "nonpayable",411    "type": "function"412  },413  {414    "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }],415    "name": "setNesting",416    "outputs": [],417    "stateMutability": "nonpayable",418    "type": "function"419  },420  {421    "inputs": [422      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },423      { "internalType": "string", "name": "key", "type": "string" },424      { "internalType": "bytes", "name": "value", "type": "bytes" }425    ],426    "name": "setProperty",427    "outputs": [],428    "stateMutability": "nonpayable",429    "type": "function"430  },431  {432    "inputs": [433      { "internalType": "string", "name": "key", "type": "string" },434      { "internalType": "bool", "name": "isMutable", "type": "bool" },435      { "internalType": "bool", "name": "collectionAdmin", "type": "bool" },436      { "internalType": "bool", "name": "tokenOwner", "type": "bool" }437    ],438    "name": "setTokenPropertyPermission",439    "outputs": [],440    "stateMutability": "nonpayable",441    "type": "function"442  },443  {444    "inputs": [445      { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }446    ],447    "name": "supportsInterface",448    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],449    "stateMutability": "view",450    "type": "function"451  },452  {453    "inputs": [],454    "name": "symbol",455    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],456    "stateMutability": "view",457    "type": "function"458  },459  {460    "inputs": [461      { "internalType": "uint256", "name": "index", "type": "uint256" }462    ],463    "name": "tokenByIndex",464    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],465    "stateMutability": "view",466    "type": "function"467  },468  {469    "inputs": [470      { "internalType": "address", "name": "owner", "type": "address" },471      { "internalType": "uint256", "name": "index", "type": "uint256" }472    ],473    "name": "tokenOfOwnerByIndex",474    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],475    "stateMutability": "view",476    "type": "function"477  },478  {479    "inputs": [480      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }481    ],482    "name": "tokenURI",483    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],484    "stateMutability": "view",485    "type": "function"486  },487  {488    "inputs": [],489    "name": "totalSupply",490    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],491    "stateMutability": "view",492    "type": "function"493  },494  {495    "inputs": [496      { "internalType": "address", "name": "to", "type": "address" },497      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }498    ],499    "name": "transfer",500    "outputs": [],501    "stateMutability": "nonpayable",502    "type": "function"503  },504  {505    "inputs": [506      { "internalType": "address", "name": "from", "type": "address" },507      { "internalType": "address", "name": "to", "type": "address" },508      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }509    ],510    "name": "transferFrom",511    "outputs": [],512    "stateMutability": "nonpayable",513    "type": "function"514  }515]
after · tests/src/eth/nonFungibleAbi.json
1[2  {3    "anonymous": false,4    "inputs": [5      {6        "indexed": true,7        "internalType": "address",8        "name": "owner",9        "type": "address"10      },11      {12        "indexed": true,13        "internalType": "address",14        "name": "approved",15        "type": "address"16      },17      {18        "indexed": true,19        "internalType": "uint256",20        "name": "tokenId",21        "type": "uint256"22      }23    ],24    "name": "Approval",25    "type": "event"26  },27  {28    "anonymous": false,29    "inputs": [30      {31        "indexed": true,32        "internalType": "address",33        "name": "owner",34        "type": "address"35      },36      {37        "indexed": true,38        "internalType": "address",39        "name": "operator",40        "type": "address"41      },42      {43        "indexed": false,44        "internalType": "bool",45        "name": "approved",46        "type": "bool"47      }48    ],49    "name": "ApprovalForAll",50    "type": "event"51  },52  {53    "anonymous": false,54    "inputs": [],55    "name": "MintingFinished",56    "type": "event"57  },58  {59    "anonymous": false,60    "inputs": [61      {62        "indexed": true,63        "internalType": "address",64        "name": "from",65        "type": "address"66      },67      {68        "indexed": true,69        "internalType": "address",70        "name": "to",71        "type": "address"72      },73      {74        "indexed": true,75        "internalType": "uint256",76        "name": "tokenId",77        "type": "uint256"78      }79    ],80    "name": "Transfer",81    "type": "event"82  },83  {84    "inputs": [85      { "internalType": "address", "name": "newAdmin", "type": "address" }86    ],87    "name": "addAdmin",88    "outputs": [],89    "stateMutability": "view",90    "type": "function"91  },92  {93    "inputs": [94      { "internalType": "address", "name": "user", "type": "address" }95    ],96    "name": "addToAllowList",97    "outputs": [],98    "stateMutability": "view",99    "type": "function"100  },101  {102    "inputs": [103      { "internalType": "address", "name": "approved", "type": "address" },104      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }105    ],106    "name": "approve",107    "outputs": [],108    "stateMutability": "nonpayable",109    "type": "function"110  },111  {112    "inputs": [113      { "internalType": "address", "name": "owner", "type": "address" }114    ],115    "name": "balanceOf",116    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],117    "stateMutability": "view",118    "type": "function"119  },120  {121    "inputs": [122      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }123    ],124    "name": "burn",125    "outputs": [],126    "stateMutability": "nonpayable",127    "type": "function"128  },129  {130    "inputs": [131      { "internalType": "address", "name": "from", "type": "address" },132      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }133    ],134    "name": "burnFrom",135    "outputs": [],136    "stateMutability": "nonpayable",137    "type": "function"138  },139  {140    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],141    "name": "collectionProperty",142    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],143    "stateMutability": "view",144    "type": "function"145  },146  {147    "inputs": [],148    "name": "confirmCollectionSponsorship",149    "outputs": [],150    "stateMutability": "nonpayable",151    "type": "function"152  },153  {154    "inputs": [],155    "name": "contractAddress",156    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],157    "stateMutability": "view",158    "type": "function"159  },160  {161    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],162    "name": "deleteCollectionProperty",163    "outputs": [],164    "stateMutability": "nonpayable",165    "type": "function"166  },167  {168    "inputs": [169      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },170      { "internalType": "string", "name": "key", "type": "string" }171    ],172    "name": "deleteProperty",173    "outputs": [],174    "stateMutability": "nonpayable",175    "type": "function"176  },177  {178    "inputs": [],179    "name": "finishMinting",180    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],181    "stateMutability": "nonpayable",182    "type": "function"183  },184  {185    "inputs": [186      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }187    ],188    "name": "getApproved",189    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],190    "stateMutability": "view",191    "type": "function"192  },193  {194    "inputs": [195      { "internalType": "address", "name": "owner", "type": "address" },196      { "internalType": "address", "name": "operator", "type": "address" }197    ],198    "name": "isApprovedForAll",199    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],200    "stateMutability": "view",201    "type": "function"202  },203  {204    "inputs": [205      { "internalType": "address", "name": "to", "type": "address" },206      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }207    ],208    "name": "mint",209    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],210    "stateMutability": "nonpayable",211    "type": "function"212  },213  {214    "inputs": [215      { "internalType": "address", "name": "to", "type": "address" },216      { "internalType": "uint256[]", "name": "tokenIds", "type": "uint256[]" }217    ],218    "name": "mintBulk",219    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],220    "stateMutability": "nonpayable",221    "type": "function"222  },223  {224    "inputs": [225      { "internalType": "address", "name": "to", "type": "address" },226      {227        "components": [228          { "internalType": "uint256", "name": "field_0", "type": "uint256" },229          { "internalType": "string", "name": "field_1", "type": "string" }230        ],231        "internalType": "struct Tuple0[]",232        "name": "tokens",233        "type": "tuple[]"234      }235    ],236    "name": "mintBulkWithTokenURI",237    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],238    "stateMutability": "nonpayable",239    "type": "function"240  },241  {242    "inputs": [243      { "internalType": "address", "name": "to", "type": "address" },244      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },245      { "internalType": "string", "name": "tokenUri", "type": "string" }246    ],247    "name": "mintWithTokenURI",248    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],249    "stateMutability": "nonpayable",250    "type": "function"251  },252  {253    "inputs": [],254    "name": "mintingFinished",255    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],256    "stateMutability": "view",257    "type": "function"258  },259  {260    "inputs": [],261    "name": "name",262    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],263    "stateMutability": "view",264    "type": "function"265  },266  {267    "inputs": [],268    "name": "nextTokenId",269    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],270    "stateMutability": "view",271    "type": "function"272  },273  {274    "inputs": [275      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }276    ],277    "name": "ownerOf",278    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],279    "stateMutability": "view",280    "type": "function"281  },282  {283    "inputs": [284      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },285      { "internalType": "string", "name": "key", "type": "string" }286    ],287    "name": "property",288    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],289    "stateMutability": "view",290    "type": "function"291  },292  {293    "inputs": [294      { "internalType": "address", "name": "admin", "type": "address" }295    ],296    "name": "removeAdmin",297    "outputs": [],298    "stateMutability": "view",299    "type": "function"300  },301  {302    "inputs": [303      { "internalType": "address", "name": "user", "type": "address" }304    ],305    "name": "removeFromAllowList",306    "outputs": [],307    "stateMutability": "view",308    "type": "function"309  },310  {311    "inputs": [312      { "internalType": "address", "name": "from", "type": "address" },313      { "internalType": "address", "name": "to", "type": "address" },314      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }315    ],316    "name": "safeTransferFrom",317    "outputs": [],318    "stateMutability": "nonpayable",319    "type": "function"320  },321  {322    "inputs": [323      { "internalType": "address", "name": "from", "type": "address" },324      { "internalType": "address", "name": "to", "type": "address" },325      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },326      { "internalType": "bytes", "name": "data", "type": "bytes" }327    ],328    "name": "safeTransferFromWithData",329    "outputs": [],330    "stateMutability": "nonpayable",331    "type": "function"332  },333  {334    "inputs": [{ "internalType": "string", "name": "mode", "type": "string" }],335    "name": "setAccess",336    "outputs": [],337    "stateMutability": "nonpayable",338    "type": "function"339  },340  {341    "inputs": [342      { "internalType": "address", "name": "operator", "type": "address" },343      { "internalType": "bool", "name": "approved", "type": "bool" }344    ],345    "name": "setApprovalForAll",346    "outputs": [],347    "stateMutability": "nonpayable",348    "type": "function"349  },350  {351    "inputs": [{ "internalType": "uint8", "name": "mode", "type": "uint8" }],352    "name": "setCollectionAccess",353    "outputs": [],354    "stateMutability": "nonpayable",355    "type": "function"356  },357  {358    "inputs": [359      { "internalType": "string", "name": "limit", "type": "string" },360      { "internalType": "uint32", "name": "value", "type": "uint32" }361    ],362    "name": "setCollectionLimit",363    "outputs": [],364    "stateMutability": "nonpayable",365    "type": "function"366  },367  {368    "inputs": [369      { "internalType": "string", "name": "limit", "type": "string" },370      { "internalType": "bool", "name": "value", "type": "bool" }371    ],372    "name": "setCollectionLimit",373    "outputs": [],374    "stateMutability": "nonpayable",375    "type": "function"376  },377  {378    "inputs": [{ "internalType": "bool", "name": "mode", "type": "bool" }],379    "name": "setCollectionMintMode",380    "outputs": [],381    "stateMutability": "nonpayable",382    "type": "function"383  },384  {385    "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }],386    "name": "setCollectionNesting",387    "outputs": [],388    "stateMutability": "nonpayable",389    "type": "function"390  },391  {392    "inputs": [393      { "internalType": "bool", "name": "enable", "type": "bool" },394      {395        "internalType": "address[]",396        "name": "collections",397        "type": "address[]"398      }399    ],400    "name": "setCollectionNesting",401    "outputs": [],402    "stateMutability": "nonpayable",403    "type": "function"404  },405  {406    "inputs": [407      { "internalType": "string", "name": "key", "type": "string" },408      { "internalType": "bytes", "name": "value", "type": "bytes" }409    ],410    "name": "setCollectionProperty",411    "outputs": [],412    "stateMutability": "nonpayable",413    "type": "function"414  },415  {416    "inputs": [417      { "internalType": "address", "name": "sponsor", "type": "address" }418    ],419    "name": "setCollectionSponsor",420    "outputs": [],421    "stateMutability": "nonpayable",422    "type": "function"423  },424  {425    "inputs": [{ "internalType": "bool", "name": "mode", "type": "bool" }],426    "name": "setMintMode",427    "outputs": [],428    "stateMutability": "nonpayable",429    "type": "function"430  },431  {432    "inputs": [433      { "internalType": "bool", "name": "enable", "type": "bool" },434      {435        "internalType": "address[]",436        "name": "collections",437        "type": "address[]"438      }439    ],440    "name": "setNesting",441    "outputs": [],442    "stateMutability": "nonpayable",443    "type": "function"444  },445  {446    "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }],447    "name": "setNesting",448    "outputs": [],449    "stateMutability": "nonpayable",450    "type": "function"451  },452  {453    "inputs": [454      { "internalType": "uint256", "name": "tokenId", "type": "uint256" },455      { "internalType": "string", "name": "key", "type": "string" },456      { "internalType": "bytes", "name": "value", "type": "bytes" }457    ],458    "name": "setProperty",459    "outputs": [],460    "stateMutability": "nonpayable",461    "type": "function"462  },463  {464    "inputs": [465      { "internalType": "string", "name": "key", "type": "string" },466      { "internalType": "bool", "name": "isMutable", "type": "bool" },467      { "internalType": "bool", "name": "collectionAdmin", "type": "bool" },468      { "internalType": "bool", "name": "tokenOwner", "type": "bool" }469    ],470    "name": "setTokenPropertyPermission",471    "outputs": [],472    "stateMutability": "nonpayable",473    "type": "function"474  },475  {476    "inputs": [477      { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }478    ],479    "name": "supportsInterface",480    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],481    "stateMutability": "view",482    "type": "function"483  },484  {485    "inputs": [],486    "name": "symbol",487    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],488    "stateMutability": "view",489    "type": "function"490  },491  {492    "inputs": [493      { "internalType": "uint256", "name": "index", "type": "uint256" }494    ],495    "name": "tokenByIndex",496    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],497    "stateMutability": "view",498    "type": "function"499  },500  {501    "inputs": [502      { "internalType": "address", "name": "owner", "type": "address" },503      { "internalType": "uint256", "name": "index", "type": "uint256" }504    ],505    "name": "tokenOfOwnerByIndex",506    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],507    "stateMutability": "view",508    "type": "function"509  },510  {511    "inputs": [512      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }513    ],514    "name": "tokenURI",515    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],516    "stateMutability": "view",517    "type": "function"518  },519  {520    "inputs": [],521    "name": "totalSupply",522    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],523    "stateMutability": "view",524    "type": "function"525  },526  {527    "inputs": [528      { "internalType": "address", "name": "to", "type": "address" },529      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }530    ],531    "name": "transfer",532    "outputs": [],533    "stateMutability": "nonpayable",534    "type": "function"535  },536  {537    "inputs": [538      { "internalType": "address", "name": "from", "type": "address" },539      { "internalType": "address", "name": "to", "type": "address" },540      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }541    ],542    "name": "transferFrom",543    "outputs": [],544    "stateMutability": "nonpayable",545    "type": "function"546  }547]