git.delta.rocks / unique-network / refs/commits / 3903c8aaf16e

difftreelog

CORE-386 Add admin tests

Trubnikov Sergey2022-06-07parent: #b484237.patch.diff
in: master

5 files changed

modifiedpallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -330,57 +330,7 @@
 	}
 }
 
-// Selector: d74d154f
-contract ERC721UniqueExtensions is Dummy, ERC165 {
-	// Selector: transfer(address,uint256) a9059cbb
-	function transfer(address to, uint256 tokenId) public {
-		require(false, stub_error);
-		to;
-		tokenId;
-		dummy = 0;
-	}
-
-	// Selector: burnFrom(address,uint256) 79cc6790
-	function burnFrom(address from, uint256 tokenId) public {
-		require(false, stub_error);
-		from;
-		tokenId;
-		dummy = 0;
-	}
-
-	// Selector: nextTokenId() 75794a3c
-	function nextTokenId() public view returns (uint256) {
-		require(false, stub_error);
-		dummy;
-		return 0;
-	}
-
-	// Selector: mintBulk(address,uint256[]) 44a9945e
-	function mintBulk(address to, uint256[] memory tokenIds)
-		public
-		returns (bool)
-	{
-		require(false, stub_error);
-		to;
-		tokenIds;
-		dummy = 0;
-		return false;
-	}
-
-	// Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006
-	function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)
-		public
-		returns (bool)
-	{
-		require(false, stub_error);
-		to;
-		tokens;
-		dummy = 0;
-		return false;
-	}
-}
-
-// Selector: f56cd7fa
+// Selector: 7d9262e6
 contract Collection is Dummy, ERC165 {
 	// Selector: setCollectionProperty(string,bytes) 2f073f66
 	function setCollectionProperty(string memory key, bytes memory value)
@@ -449,8 +399,22 @@
 		return 0x0000000000000000000000000000000000000000;
 	}
 
-	// Selector: addAdmin(address) 70480275
-	function addAdmin(address newAdmin) public view {
+	// Selector: addCollectionAdminSubstrate(uint256) 5730062b
+	function addCollectionAdminSubstrate(uint256 newAdmin) public view {
+		require(false, stub_error);
+		newAdmin;
+		dummy;
+	}
+
+	// Selector: removeCollectionAdminSubstrate(uint256) 4048fcf9
+	function removeCollectionAdminSubstrate(uint256 newAdmin) public view {
+		require(false, stub_error);
+		newAdmin;
+		dummy;
+	}
+
+	// Selector: addCollectionAdmin(address) 92e462c7
+	function addCollectionAdmin(address newAdmin) public view {
 		require(false, stub_error);
 		newAdmin;
 		dummy;
@@ -507,6 +471,56 @@
 	}
 }
 
+// Selector: d74d154f
+contract ERC721UniqueExtensions is Dummy, ERC165 {
+	// Selector: transfer(address,uint256) a9059cbb
+	function transfer(address to, uint256 tokenId) public {
+		require(false, stub_error);
+		to;
+		tokenId;
+		dummy = 0;
+	}
+
+	// Selector: burnFrom(address,uint256) 79cc6790
+	function burnFrom(address from, uint256 tokenId) public {
+		require(false, stub_error);
+		from;
+		tokenId;
+		dummy = 0;
+	}
+
+	// Selector: nextTokenId() 75794a3c
+	function nextTokenId() public view returns (uint256) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
+
+	// Selector: mintBulk(address,uint256[]) 44a9945e
+	function mintBulk(address to, uint256[] memory tokenIds)
+		public
+		returns (bool)
+	{
+		require(false, stub_error);
+		to;
+		tokenIds;
+		dummy = 0;
+		return false;
+	}
+
+	// Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006
+	function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)
+		public
+		returns (bool)
+	{
+		require(false, stub_error);
+		to;
+		tokens;
+		dummy = 0;
+		return false;
+	}
+}
+
 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
@@ -191,6 +191,70 @@
 	function totalSupply() external view returns (uint256);
 }
 
+// Selector: 7d9262e6
+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: setCollectionSponsor(address) 7623402e
+	function setCollectionSponsor(address sponsor) external;
+
+	// Selector: confirmCollectionSponsorship() 3c50e97a
+	function confirmCollectionSponsorship() external;
+
+	// Selector: setCollectionLimit(string,uint32) 6a3841db
+	function setCollectionLimit(string memory limit, uint32 value) external;
+
+	// Selector: setCollectionLimit(string,bool) 993b7fba
+	function setCollectionLimit(string memory limit, bool value) external;
+
+	// Selector: contractAddress() f6b4dfb4
+	function contractAddress() external view returns (address);
+
+	// Selector: addCollectionAdminSubstrate(uint256) 5730062b
+	function addCollectionAdminSubstrate(uint256 newAdmin) external view;
+
+	// Selector: removeCollectionAdminSubstrate(uint256) 4048fcf9
+	function removeCollectionAdminSubstrate(uint256 newAdmin) external view;
+
+	// Selector: addCollectionAdmin(address) 92e462c7
+	function addCollectionAdmin(address newAdmin) external view;
+
+	// Selector: removeCollectionAdmin(address) fafd7b42
+	function removeCollectionAdmin(address admin) external view;
+
+	// Selector: setCollectionNesting(bool) 112d4586
+	function setCollectionNesting(bool enable) external;
+
+	// Selector: setCollectionNesting(bool,address[]) 64872396
+	function setCollectionNesting(bool enable, address[] memory collections)
+		external;
+
+	// Selector: setCollectionAccess(uint8) 41835d4c
+	function setCollectionAccess(uint8 mode) external;
+
+	// Selector: addToCollectionAllowList(address) 67844fe6
+	function addToCollectionAllowList(address user) external view;
+
+	// Selector: removeFromCollectionAllowList(address) 85c51acb
+	function removeFromCollectionAllowList(address user) external view;
+
+	// Selector: setCollectionMintMode(bool) 00018e84
+	function setCollectionMintMode(bool mode) external;
+}
+
 // Selector: d74d154f
 interface ERC721UniqueExtensions is Dummy, ERC165 {
 	// Selector: transfer(address,uint256) a9059cbb
addedtests/src/eth/collectionAdmin.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/collectionAdmin.test.ts
@@ -0,0 +1,88 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+import {expect} from 'chai';
+import privateKey from '../substrate/privateKey';
+import {
+  createEthAccount,
+  createEthAccountWithBalance, 
+  evmCollection, 
+  evmCollectionHelpers, 
+  getCollectionAddressFromResult, 
+  itWeb3,
+} from './util/helpers';
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+describe.only('Add collection admins', () => {
+  itWeb3('Add admin by owner', async ({api, web3}) => {
+    const owner = await createEthAccountWithBalance(api, web3);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
+        
+    const result = await collectionHelper.methods
+      .createNonfungibleCollection('A', 'B', 'C')
+      .send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+
+    const newAdmin = await createEthAccount(web3);
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    await collectionEvm.methods.addCollectionAdmin(newAdmin).send();
+    const adminList = await api.rpc.unique.adminlist(collectionId);
+    expect(adminList[0].asEthereum.toString().toLocaleLowerCase())
+      .to.be.eq(newAdmin.toLocaleLowerCase());
+  });
+
+  itWeb3('Add substrate admin by owner', async ({api, web3}) => {
+    const owner = await createEthAccountWithBalance(api, web3);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
+        
+    const result = await collectionHelper.methods
+      .createNonfungibleCollection('A', 'B', 'C')
+      .send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+
+    const newAdmin = privateKey('//Alice');
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    await collectionEvm.methods.addCollectionAdminSubstrate(newAdmin.addressRaw).send();
+    const adminList = await api.rpc.unique.adminlist(collectionId);
+    expect(adminList[0].asSubstrate.toString().toLocaleLowerCase())
+      .to.be.eq(newAdmin.address.toLocaleLowerCase());
+  });
+
+  itWeb3('(!negative tests!) Add admin by admin is not allowed', async ({api, web3}) => {
+    const owner = await createEthAccountWithBalance(api, web3);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
+        
+    const result = await collectionHelper.methods
+      .createNonfungibleCollection('A', 'B', 'C')
+      .send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+
+    const admin = await createEthAccountWithBalance(api, web3);
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    await collectionEvm.methods.addCollectionAdmin(admin).send();
+    {
+      const adminList = await api.rpc.unique.adminlist(collectionId);
+      expect(adminList[0].asEthereum.toString().toLocaleLowerCase())
+        .to.be.eq(admin.toLocaleLowerCase());
+    }
+    
+    const user = await createEthAccount(web3);
+    await collectionEvm.methods.addCollectionAdmin(user).send({from: admin});
+    const adminList = await api.rpc.unique.adminlist(collectionId);
+    expect(adminList[0].asEthereum.toString().toLocaleLowerCase())
+      .to.be.eq(admin.toLocaleLowerCase());
+    expect(adminList.length).to.be.eq(1);
+  });
+});
\ No newline at end of file
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": "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]