Fetching Ethereum Token Metadata For Use In Decentralised Apps ⚡️

Craig Bovis
0x Tracker
Published in
6 min readMay 28, 2020

When developing Ethereum apps, one of the early challenges you’ll likely come across is fetching token metadata. You typically require more context because Ethereum transactions deal in token addresses and integer values rather than names/symbols and floating point values.

For example, a typical transfer log might look something like:

Courtesy of Etherscan

Without additional context, we have no idea what was transferred and how much of it was transferred. This is because the token address 0x0d8775f648430679a709e98d2b0cb6250d2887ef and transfer value of 7495030000000000000000 mean absolutely nothing to us mere humans. Try telling someone you’ve sent them 7,495,030,000,000,000,000,000 0x0d8775f648430679a709e98d2b0cb6250d2887ef and see what they say.

The amount being transferred here is actually 7,495.03 Basic Attention Token (BAT), however we can’t discover this without doing more research. At a minimum we’ll need to know the decimals associated with the token. Knowing the decimals allows us to convert the integer amount into a friendlier floating point amount. In this case BAT has 18 decimals which means we divide 7495030000000000000000 by 1000000000000000000 to get 7,495.03. Ideally though we’d like to know the token’s name and symbol as well for easier identification.

If the app is small then you could curate this metadata yourself but for larger apps we’ll need an automated solution. This is especially important in the case of 0x Tracker since the 0x protocol places no restrictions on which tokens can be traded. We’d be forever updating our registry to support new tokens.

There are a number of third party APIs available to query this information (e.g. Ethplorer), however we’re building decentralised apps so I’d like to demonstrate how we can query on-chain data instead, removing the need to trust third parties 🤖

The ERC-20 token standard recommends the implementation of name(), symbol(), and decimals() methods on ERC-20 contracts but doesn’t REQUIRE them. Most token contracts tend to implement them though which is useful for us. The ERC-721 standard also recommends the implementation of name() and symbol() methods. Decimals aren’t required for ERC-721 tokens since by definition they’re unique and as such it’s not possible to transfer more than one of a given ERC-721 asset.

With that context out of the way, let’s get to some code… 👨🏼‍💻

For my examples I’ll be using ethers.js, a Javascript library for interacting with Ethereum nodes, however there are plenty of options available for other popular languages. The general concepts should apply regardless of the library you use but will need adapting to your library’s specific APIs.

Here’s the code for a simple Node.js module which fetches the metadata for a given token contract address. It works for both ERC-20 and ERC-721 tokens:

const ethers = require('ethers');const WEB3_ENDPOINT = 'https://cloudflare-eth.com';const handleError = () => {
return undefined;
};
const getTokenMetadata = async (address) => {
const abi = [
'function name() view returns (string name)',
'function symbol() view returns (string symbol)',
'function decimals() view returns (uint8 decimals)',
];
const { JsonRpcProvider } = ethers.providers;
const provider = new JsonRpcProvider(WEB3_ENDPOINT);
const contract = new ethers.Contract(address, abi, provider);
const [name, symbol, decimals] = await Promise.all([
contract.name().catch(handleError),
contract.symbol().catch(handleError),
contract.decimals().catch(handleError),
]);
return { decimals, name, symbol };
};
module.exports = { getTokenMetadata };

Now let’s break it down…

Since we’re just querying contract state there’s no transaction cost involved, however we do need an Ethereum node to query. Cloudflare provide a free Ethereum gateway that we can use, however you can also look into Infura or any number of other services. The cloudflare RPC endpoint is https://cloudflare-eth.com and to interact with it we need to create an instance of JsonRpcProvider, passing in the endpoint URL to the constructor.

The next step is to create an instance of the contract we intend to query. To do this, we need to first know the address and Application Binary Interface (ABI) of the contract we’re trying to interact with. The ABI defines the public interface of a contract through which queries and commands can be made. Conveniently however we don’t need to know the full contract ABI, only the parts we want to interact with.

The partial ABI for fetching ERC-20 or ERC-721 metadata is as follows:

const abi = [
'function name() view returns (string name)',
'function symbol() view returns (string symbol)',
'function decimals() view returns (uint8 decimals)',
];

Hopefully this is fairly intuitive. Essentially it defines three parameterless methods which return strings (in the case of name and symbol) or an integer (in the case of decimals). With our contract instance successfully constructed, we can simply call the required methods to fetch the token metadata:

const handleError = () => {
return undefined;
};
// ...const [name, symbol, decimals] = await Promise.all([
contract.name().catch(handleError),
contract.symbol().catch(handleError),
contract.decimals().catch(handleError),
]);

Since we’re making network calls, the contract methods need to be called asynchronously. In the case of JS, this is done using Promises.

You’ll notice that each method call is individually error handled. This is because we can’t guarantee the existence of a given method until we call the contract. In this case we simply return undefined for a given method if any errors occur. You may wish to handle specific error types differently though to distinguish between missing methods and network errors for example.

Add some colour by fetching token images…

Knowing what was transferred and how much of it was transferred is key, however it can also be useful to add some images into the mix. Imagine how much harder this table would be to scan without the visual hint of icons:

Unfortunately there’s no on-chain standard for token images at present, however an awesome open source repo set up by Trust Wallet can help us out. The repository’s token whitelist contains a list of all supported tokens, with images being located at:

https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/{token_address}/logo.png

Linking directly to raw.githubusercontent.com from your own app isn’t recommended due to the short-lived cache headers returned by GitHub. Instead, I’d recommend downloading the images to your own CDN or using Statically instead. The URL for a token becomes the following when requested through Statically:

https://cdn.staticaly.com/gh/TrustWallet/tokens/master/tokens/{token_address}.png

Token addresses in URLs are case-sensitive but you can use the whitelist to determine the correct case for a given token.

One last bit of info…

Remember how I said ERC-721 assets were unique? You probably already knew that right… Well, because of this the metadata we fetched previously actually belongs to the contract, not individual assets within the contract. If we call the Axie contract for example, we’ll get a name of Axie and symbol of AXIE. That’s handy, but not great if we need to distinguish between AXIE 56 and AXIE 57. That could be the difference between Old Kent Road and Mayfair! 🏨

Don’t fret though, the token gods also thought about this scenario. The ERC-721 standard specifies that contract authors can opt to provide a tokenURI() method which accepts a token identifier and returns a URI. That URI should point to a JSON file which conforms to the “ERC721 Metadata JSON Schema” format:

{
"title": "Asset Metadata",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Identifies the asset to which this NFT represents"
},
"description": {
"type": "string",
"description": "Describes the asset to which this NFT represents"
},
"image": {
"type": "string",
"description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
}
}
}

So for example, executing tokenURI with 56 on the Axie token contract returns https://axieinfinity.com/api/axies/56 which in turn provides more information about AXIE 56. Superb 🎉

The ABI for the tokenURI method is function tokenURI(uint256 _tokenId) view returns (string URI). I’ll leave it up to you dear reader to adapt our previous code example to use that instead. Good luck!

If you enjoyed this post consider joining our monthly newsletter below. Until next time 👋

--

--