Token Tracking Solution
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:
1
//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)
2
3
var configuration = require("configuration.json");
4
5
//Get the Web3 engine
6
var Web3 = require('web3');
7
8
//Initialize your connection with a Ethereum node
9
var web3 = new Web3(configuration.web3URLConnectionString);
10
11
//Retrieve the last version of the EthItem Orchestrator Smart Contract
12
var ethItemOrchestrator = new web3.eth.Contract(configuration.ethItemOrchestratorABI, configuration.ethItemOrchestratorAddress);
13
14
//Prepare a var to collect all the ITEMs
15
var allCollections = [];
Copied!
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).
1
//Call the Orchestrator and then the KnowledgeBase to retrieve all the ERC20Wrappers
2
var knowledgeBase = new web3.eth.Contract(configuration.EthItemKnowledgeBaseABI , await ethItemOrchestrator.knowledgeBase().call());
3
var ethItemERC20WrapperAddresses = await knowledgeBase.methods.erc20Wrappers().call();
4
5
for(var collectionAddress of ethItemERC20WrapperAddresses) {
6
//Normalize the address for eventual search by address purposes
7
collectionAddress = web3.utils.toChecksumAddress(collectionAddress);
8
9
//Collection category to distinguish all collection types, "W20" means that this Collection is a Wrapper of ERC20 Tokens
10
var collectionCategory = "W20";
11
12
var collectionABI = configuration.W20ABI;
13
14
//The needed basic info to operate are of course the Collection address, category and Smart Contract. They can be of course enriched
15
allCollections.push({
16
address : collectionAddress,
17
category : collectionCategory,
18
contract : new web3.eth.Contract(collectionABI, collectionAddress)
19
});
20
}
Copied!
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).
1
var factoryAddresses = await
2
ethItemOrchestrator.methods.factories().call();
Copied!
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).
1
//First three params are indexed to make specific researches
2
//A key-value approach is used to keep track of the respective Collection ABI and Category
3
var ethItemFactoryEventsAndABIValues = {
4
"NewNativeCreated(address,uint256,address,address)" : "NativeABI",
5
"NewWrappedERC1155Created(address,uint256,address,address)" : "W1155ABI",
6
"NewWrappedERC721Created(address,uint256,address,address)" : "W721ABI"/*,
7
"NewWrappedERC20Created(address,uint256,address,address)" : "W20ABI"*/
8
};
Copied!
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.
1
//Let's convert plain events in keccak256 topics so they can be used for the web3.eth.getPastLogs call
2
var ethItemFactoryTopicsAndCollectionABIValues = {};
3
Object.entries(ethItemFactoryEventsAndABIValues).forEach(it => ethItemFactoryTopicsAndCollectionABIValues[web3.utils.sha3(it[0])] = it[1]);
4
5
//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)
6
var topics = [Object.keys(ethItemFactoryTopicsAndCollectionABIValues)];
7
8
//Call the blockchain.
9
//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.
10
var logs = await web3.eth.getPastLogs({
11
address : factoryAddresses,
12
topics
13
});
14
15
//Navigate the logs to obtain info about collections
16
for (var log of logs) {
17
//Retrieve the correct ABI using the first log topic through the key/value map previously made
18
var collectionABIValue = ethItemFactoryTopicsAndCollectionABIValues[log.topics[0]];
19
20
//As already said, last topic param is che Collection address
21
var collectionAddress = web3.eth.abi.decodeParameter("address", log.topics[log.topics.length - 1]);
22
23
//Make a checksum address for eventual off-chain research purposes
24
collectionAddress = web3.utils.toChecksumAddress(collectionAddress);
25
26
//Grab the correct Collection ABI from the configuration file
27
var collectionABI = configuration[collectionABIValue];
28
29
//Extract Collection Category label from the ABI value. Just remove the latest 3 "ABI" characters from the string
30
var collectionCategory = collectionABIValue.split("ABI")[0];
31
32
//Put everything in the allCollections array
33
allCollections.push({
34
address : collectionAddress,
35
category : collectionCategory,
36
contract : new web3.eth.Contract(collectionABI, collectionAddress)
37
});
38
}
39
40
//Use all the collections as you wish
41
console.table(allCollections.map(it => {return {address : it.address, category : it.category}}));
Copied!
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:
1
//Grab the desired Collection addresses. You can choose any single Collection or group of Collections you want. In this case we grab all
2
var collectionAddresses = allCollections.map(it => it.address);
3
4
//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
5
var topics = [web3.utils.sha3("NewItem(uint256,address)")];
6
7
var logs = await web3.eth.getPastLogs({
8
address : collectionAddresses,
9
topics
10
});
11
12
//Navigate logs
13
for(var log of logs) {
14
15
//Get the Collection that created this item (the original event emitter)
16
var collectionAddress = web3.utils.toChecksumAddress(log.address);
17
var collection = allCollections.filter(it => it.address === collectionAddress)[0];
18
19
//If not already done, initialize the items array in the Collection
20
collection.items = collection.items || [];
21
22
//Object Id is the first argument param of the Event
23
var collectionItemObjectId = web3.eth.abi.decodeParameter("uint256", log.topics[1]);
24
25
//Object ERC20 Wrapper is the second param of the Event
26
var interoperableInterfaceAddress = web3.eth.abi.decodeParameter("uint256", log.topics[2]);
27
28
//Create the contract
29
var collectionItemInteroperableInterfaceContract = new web3.eth.Contract(configuration.IEthItemInteroperableInterfaceABI, interoperableInterfaceAddress);
30
31
//Assemble the Collection Item, you can add all the additional info you want (e.g. cross-referencing the Collection this Item belongs to)
32
var collectionItem = {
33
objectId : collectionItemObjectId,
34
address : collectionItemInteroperableAddress,
35
contract : collectionItemInteroperableInterfaceContract
36
};
37
38
//Add every single Collection Item to the corresponding Collection's array
39
collection.items.push(collectionItem);
40
}
Copied!
Copy link