One highly controversial and challenging problem in Ethereum development is the tracking of all existing tokens. It is impossible for a dApp to do this efficiently without relying on intermediary servers, forcing it to program its frontend—the most centralized and difficult to govern part—with echelons of hardcoded addresses. This puts it in the uncomfortable and untenable position of arbitrarily prioritizing some projects over others in order to improve their user experience.
To resolve this, the architecture for ITEMs (native and wrapped) is based on a general-purpose factory standard that generates all of the standardized methodology for every token.
This means that dApps only need to follow these steps:
//First of all, let's suppose you have a 'configuration.json' file in your dapp, containing all the configuration parameters you need (e.g. ABIs, custom parameters)
var configuration =require("configuration.json");//Get the Web3 enginevar Web3 =require('web3');//Initialize your connection with a Ethereum nodevar web3 =newWeb3(configuration.web3URLConnectionString);//Retrieve the last version of the EthItem Orchestrator Smart Contractvar ethItemOrchestrator = new web3.eth.Contract(configuration.ethItemOrchestratorABI, configuration.ethItemOrchestratorAddress);
//Prepare a var to collect all the ITEMsvar allCollections = [];
Phase 1) Retrieve all Wrapped ERC20 Collections directly by calling the Orchestrator. Each ERC20 Wrapped Collection is a Singleton, so there will always be just one active at a time, and its address is well-known. But the Orchestrator, of course, also collects all previous inactive versions (the last one is the currently active one).
//Call the Orchestrator and then the KnowledgeBase to retrieve all the ERC20Wrappersvar knowledgeBase = new web3.eth.Contract(configuration.EthItemKnowledgeBaseABI , await ethItemOrchestrator.knowledgeBase().call());
var ethItemERC20WrapperAddresses =awaitknowledgeBase.methods.erc20Wrappers().call();for(var collectionAddress of ethItemERC20WrapperAddresses) {//Normalize the address for eventual search by address purposescollectionAddress =web3.utils.toChecksumAddress(collectionAddress);//Collection category to distinguish all collection types, "W20" means that this Collection is a Wrapper of ERC20 Tokens
var collectionCategory ="W20";var collectionABI =configuration.W20ABI;//The needed basic info to operate are of course the Collection address, category and Smart Contract. They can be of course enriched
allCollections.push({ address : collectionAddress, category : collectionCategory, contract :newweb3.eth.Contract(collectionABI, collectionAddress)});}
Phase 2) Retrieve all the other Collections passing by their factories. There will always be just one active EthItem Factory at a time, but the Orchestrator, of course, also collects all previous, inactive versions (the last one is the currently active one).
var factoryAddresses =awaitethItemOrchestrator.methods.factories().call();
Factories emit a specific event for every Collection type (including ERC20). First param is the Collection model address, second param is the Collection model version, third param is the Collection address, fourth param is the original Factory caller (the Orchestrator).
//First three params are indexed to make specific researches//A key-value approach is used to keep track of the respective Collection ABI and Categoryvar ethItemFactoryEventsAndABIValues = {"NewNativeCreated(address,uint256,address,address)":"NativeABI","NewWrappedERC1155Created(address,uint256,address,address)":"W1155ABI","NewWrappedERC721Created(address,uint256,address,address)":"W721ABI"/*, "NewWrappedERC20Created(address,uint256,address,address)" : "W20ABI"*/};
The last event is of course commented because we already retrieved all the ERC20 Wrappers in Phase 1. You can choose of course to receive the Collections of all Categories or just the ones you're interested in just by removing the irrelevant events in the ethItemFactoryEventsAndABIValues object above.
//Let's convert plain events in keccak256 topics so they can be used for the web3.eth.getPastLogs callvar ethItemFactoryTopicsAndCollectionABIValues = {};Object.entries(ethItemFactoryEventsAndABIValues).forEach(it => ethItemFactoryTopicsAndCollectionABIValues[web3.utils.sha3(it[0])] = it[1]);
//First argument of the topics array is an array of three hash elements. This means that first log topic can be one of the passed arguments (like the "OR" operator in Web2 DB queries)
var topics = [Object.keys(ethItemFactoryTopicsAndCollectionABIValues)];//Call the blockchain.//Of course this is a generic-purpose code, it can be more efficient by using just the topics you need (e.g. give me only the Wrapped ERC721 Collections) or use from/to block tranches.
var logs =awaitweb3.eth.getPastLogs({ address : factoryAddresses, topics});//Navigate the logs to obtain info about collectionsfor (var log of logs) {//Retrieve the correct ABI using the first log topic through the key/value map previously madevar collectionABIValue = ethItemFactoryTopicsAndCollectionABIValues[log.topics[0]];//As already said, last topic param is che Collection addressvar collectionAddress =web3.eth.abi.decodeParameter("address",log.topics[log.topics.length-1]);//Make a checksum address for eventual off-chain research purposes collectionAddress =web3.utils.toChecksumAddress(collectionAddress);//Grab the correct Collection ABI from the configuration filevar collectionABI = configuration[collectionABIValue];//Extract Collection Category label from the ABI value. Just remove the latest 3 "ABI" characters from the stringvar collectionCategory =collectionABIValue.split("ABI")[0];//Put everything in the allCollections arrayallCollections.push({ address : collectionAddress, category : collectionCategory, contract :newweb3.eth.Contract(collectionABI, collectionAddress) });}//Use all the collections as you wishconsole.table(allCollections.map(it => {return {address :it.address, category :it.category}}));
This allows you to receive all of the tokens that exist in the Ethereum Ecosystem (wrapped into ITEMs), instead of relying on hardcoded lists of well-known tokens or white-listed decisions by dApp frontend owners.
You can also request all of the existing ITEMs from specific Collections using this call:
Request Every ITEM for a single collection:
//Grab the desired Collection addresses. You can choose any single Collection or group of Collections you want. In this case we grab all
var collectionAddresses =allCollections.map(it =>it.address);//The EthItem Token Standard implements the event NewItem(uint256 indexed objectId, address indexed interoperableInterfaceAddress) raised every time a new Item is created/wrapped for the first time
var topics = [web3.utils.sha3("NewItem(uint256,address)")];var logs =awaitweb3.eth.getPastLogs({ address : collectionAddresses, topics});//Navigate logsfor(var log of logs) {//Get the Collection that created this item (the original event emitter)var collectionAddress =web3.utils.toChecksumAddress(log.address);var collection =allCollections.filter(it =>it.address === collectionAddress)[0];//If not already done, initialize the items array in the Collectioncollection.items =collection.items || [];//Object Id is the first argument param of the Eventvar collectionItemObjectId =web3.eth.abi.decodeParameter("uint256",log.topics[1]);//Object ERC20 Wrapper is the second param of the Eventvar interoperableInterfaceAddress =web3.eth.abi.decodeParameter("uint256",log.topics[2]);//Create the contract var collectionItemInteroperableInterfaceContract = new web3.eth.Contract(configuration.IEthItemInteroperableInterfaceABI, interoperableInterfaceAddress);
//Assemble the Collection Item, you can add all the additional info you want (e.g. cross-referencing the Collection this Item belongs to)
var collectionItem = { objectId : collectionItemObjectId, address : collectionItemInteroperableAddress, contract : collectionItemInteroperableInterfaceContract };//Add every single Collection Item to the corresponding Collection's arraycollection.items.push(collectionItem);}