Sites like Coinmarketcap and Coingecko often require an HTTP API endpoint for returning data like total supply or circulating supply, here I explain how you build one and get your token listed:
If you’re launching a new token on Ethereum or another EVM chain like Binance Smart Chain, there’s a long checklist of requirements to give your token its best chance at success. One of those is making certain that your token is promptly listed on major token data sites like Coinmarketcap (CMC) and Coingecko (CG). If you’ve never gone through the process before, it can be a confusing and sometimes frustrating one to deal with. Even for the pros, we sometimes run into hitches, but for total newbies it can be a nightmare. One of the most challenging listing requirements is building an HTTP API which returns key token data such as total supply and circulating supply.
This article has four sections each covering one aspect of getting your token listed:
- The basics of applying to get your token listed
- Definitions of token data terms such as “total supply”, “max supply”, and “circulating supply”
- How to write an API which returns the total supply and circulating supply figures
In section 4 I will run you through an example API project step by step with examples. The complete code is available for you to review or use for your own API.
(Note: The code is provided as is, and primarily as an educational tool. No guarantee is made for its correctness or reliability. Use at your own risk.)
Before You Begin
The first step of getting your token listed is submitting a request ticket. For this article, we will focus only on CMC and CG. The links to the forms can be found at the bottom of each website’s homepage:
After filling in all the required information, you will eventually come to a section asking you to fill in a token data excel sheet. For CMC, you must fill this sheet out in order to get even the basic info for your token listed, while for CG, you can get the basic info of your token listed first, but you must do a second request after the first listing in order to get access to and submit the token data sheet in order to have more complete data about your token shown on CG.
The sheets for CMC and CG are very similar, with only a few small differences. The data you fill in here will form the basis for calculating the figures returned by your API. It’s very important to fill the information in accurately to prevent your application from being rejected.
Both token data sheets ask for the following:
- Circulating supply
- Total supply
- Max supply
- Explorer link
- Richlist link
- Top 20 wallets with details for each wallet (most important of which is whether the tokens are “locked” or “unlocked” — more on this later)
CG has these additional requirements:
- Coingecko link (as unlike CMC, it requires you to first get your basic token info listed)
- Distribution schedule
- HTTP REST endpoint returning **circulating **supply as a single number
CMC has only one additional requirement:
- HTTP REST endpoint returning **total **supply as a single number
A small tip for making it a bit easier to fill out these sheets. Simply navigate to your token’s Etherscan page, click the “Holders” tab, and select the table with the top 20 tokens, and copy. You can now paste the token information directly into CMC and CG’s token data sheets with the info for each address preserved:
Filling in these sheets is generally straightforward, but there’s lots of room for making simple errors. We’ll try to clear up the points where we often see projects tripping up in the next few sections.
Definitions of Key Terms
The three main figures required by both token data sheets are “total supply”, “max supply”, and “circulating supply”. While they may seem straightforward enough, subtle misunderstandings can cause mistakes to happen. Below I define each one in detail:
- Total supply Total supply is the total number of tokens which currently exist on chain minus any tokens which have been verifiably burnt.
- Max Supply Max supply is the hard coded total number of tokens which will ever exist on chain.
Not all tokens have a max supply! Some tokens, such as Tether USDT, have no limit on the number of tokens which can be issued. In cases like these, max supply is infinite.
For some tokens, the max supply is equal to the total supply. This happens in cases where 100% of the tokens are minted at launch and none have been burnt.
Burnt tokens are not deducted from max supply as it represents the maximum number of tokens which will ever exist in the history of the token. It is a value hard coded in the smart contract.
- Circulating Supply Circulating supply is the most complex to calculate and therefore most prone to error, but the basic formula is fairly simple:
Circulating supply = total supply — locked tokens — burnt tokens
While it’s fairly straightforward to determine total supply and burnt tokens, “locked tokens” is where people often get into trouble. A variety of tokens fall into the “locked” category:
Team controlled tokens reserved for future usage for a variety of reasons such as technical development, community expansion, team salaries, bounties, liquidity incentives, etc.
Vested tokens of investor funds or individuals which are to be released over time according to a vesting schedule
Incentive tokens — these tokens are distributed over time according to a set schedule in order to incentivize some behavior (for example, to operators of nodes for the project, or as incentives for liquidity providers).
**Take note:* Locked tokens may be literally “locked” in a smart contract and released according to the contract’s code, or maybe just be under control of the team and released according to the team’s own plan. As long as the tokens have been reserved from launch either by the team and investors or a smart contract and not yet entered any markets, they are considered “locked”.*
How to Write Your Token API
And now on to the fun stuff! You should now have a good grasp of how to apply for your token listing and how to calculate key token data figures. For this tutorial, I am assuming a familiarity with Express, the basics of setting up Express routes, and with MongoDB or other database or data caching solution. I recommend using Insomnia or Postman for testing your routes (I personally prefer Insomnia).
Preparation
Before you begin writing your API, we must first prepare a few things.
- Get a free Infura account and endpoint for your Web3.js connection. After creating your account, make your first new project and go to the settings page. On the settings page you can find your http and websockets endpoints. We will use these later.
- Get your free MongoDB Atlas account, choose a free tier cluster location near you, and set up your database. Make sure to allow access from all IP addresses by adding 0.0.0.0/0 under “Network Access” And make sure you have created a user with permissions to read and write from your DB under the “Database Access” tab: From the “Databases” tab, click connect on your newly created database: Next choose “Connect your application” And copy the connection string from the following screen and set it aside for later. The password here is the password for the user you just created in the previous step, but we will not hardcode the password into the string. Just hold onto this for now. (Note: MongoDB is used here because it is free, user friendly, and plays nicely with Heroku. However for production purposes you will likely want to use some other simple caching solution like Redis or just writing to a json file.)
Getting to Know the App Structure
Let’s jump into writing your token API. Example code can be found here, we’ll start by familiarizing ourselves with the app structure:
We have five folders along with the main server.js entry file.
- /abi — Here we store the ABI files for each project.
- /addresses — All the addresses required for accessing relevant smart contracts and addresses of top 20 token holders and team / investors / advisors.
- /middleware — For our own middleware — currently only has a simple middleware function for removing trailing slashes from urls.
- /routes — Here we define all our public routes which we will make available.
- /utils — This is where the meat of the project is. This is where we set up and instantiate our database, where we define the logic for getting token data, and optionally get price data. (The getPriceData.js function is not actually used in this example project, but I’ve left it in anyway in case you want to add it. It uses Coingecko’s free token data API which is very convenient for reporting live token data.)
- Server.js — Here we pull everything together from all the /utils, /routes, and /middleware files and start our server listening at its assigned port.
Jumping In
We’ll start digging into how the code works from the utils/getChainData.js file. I’ve included extensive comments to clarify any sections which may be unclear. In summary, getChainData accomplishes three main things:
- Define setupWeb3 async function which uses web3.js to connect to HTTP or Websocket endpoints in order to set up web3s object which will allow us to query Ethereum and Binance Smart Chain nodes in order to get current on chain data.
- Define an updateData async function with a schedule function loop to get on chain data for each project at regular intervals. updateData *takes a collection of web3 objects as input which gets passed to each *getProjectOneData and getProjectTwoData function for each function to use to make web3 calls.
- Define function getChainData to call setupWeb3 and updateData *asynchronously so that the established web3 objects are made available to the functions for updating token data. Return *getChainData function from this module.
const schedule = require(“node-schedule”) // Used to set schedule for calling Infura to update token data
const Web3 = require(“web3”) // Used for connecting with node endpoints (Ethereum and BSC for this project) to get live information about on chain data
const sleep = require(‘ko-sleep’); // Used to set a time delay between retrying Web3 connections
const getProjectOneData = require(“./getProjectOneData”) // Logic for collecting and calculating all data for ProjectOne
const getProjectTwoData = require(“./getProjectTwoData”) // Logic for collecting and calculating all data for ProjectTwo
// const getPriceData = require(“./getPriceData”) // Unused in this example project
// Function to setup web3 objects for chains to be queried.
const setupWeb3 = async () => {
// Multiple Binance Smart Chain endpoints are supplied in case one is down. More endpoints can be found at [https://docs.binance.org/smart-chain/developer/rpc.html](https://docs.binance.org/smart-chain/developer/rpc.html)
const bsc_endpoints = [
“https://bsc-dataseed.binance.org/",
“https://bsc-dataseed1.defibit.io/",
“https://bsc-dataseed1.ninicoin.io/",
]
let bsc_web3
// Run through three provided BSC endpoints until a connection is established and a valid web3 object is returned
while (true){
for(i=0; i <bsc_endpoints.length; i++) {
bsc_web3 = await new Web3(new Web3.providers.HttpProvider(bsc_endpoints[i]))
if (bsc_web3.currentProvider) break
await sleep(100)
}
if (bsc_web3.currentProvider) break
}
let web3
// Only a single Infura endpoint is provided for the Ethereum web3 object as Infura endpoints are highly stable. Note that WebsocketProvider is used here, if you prefer an HTTP endpoint make sure to change it to HttpProvider.
while (true){
// INFURA_URL is available as an environment variable. It’s recommended to use dotenv for setting env variables in your development environment: [https://www.npmjs.com/package/dotenv](https://www.npmjs.com/package/dotenv)
web3 = await new Web3(new Web3.providers.WebsocketProvider(process.env.INFURA_URL))
if (web3.currentProvider) break
await sleep(100)
}
// Return all established web3 objects
return {web3, bsc_web3}
}
// This function passes the established web3 objects to the getProjectOneData and getProjectTwoData functions inside of the schedule functions. The schedule function comes from node-schedule and uses cron syntax which you can experiment with at [https://crontab.guru/.](https://crontab.guru/.) I’ve set it to update every 15 seconds here as it’s useful for testing purposes. A less frequent update schedule is recommended for production.
const updateData = async (web3_collection) => {
schedule.scheduleJob(“0,15,30,45,59 * * * * *”, async () => {
getProjectOneData(web3_collection)
getProjectTwoData(web3_collection)
})
}
// Here we define a function to call the async setupWeb3 function and use the resolved promise ‘web3_collection’ as input for updateData which begins the update loop
const getChainData = () => {
setupWeb3().then((web3_collection) => updateData(web3_collection))
}
module.exports = getChainData
Making Web3 Calls, Calculating, and Storing Token Data
Next we will examine utils/getProjectOneData.js. We will not examine utils/getProjectTwoData.js as it is essentially the same but simpler as it only gets data from BSC, not Ethereum as well.
Each of these getProjectXData functions primarily accomplishes four things:
- Use the passed in web3 objects to establish smart contract objects and make web3 calls to get raw data
- Perform calculations on raw data returned from web3 calls in order to get the figures we need to return from our API
- Format raw data so it is well organized and easily human readable — convert numbers according to decimals of each token, add optional additional information. . Write formatted data to our database.
const addresses = require(“../addresses/projectOne”) // Get all relevant Ethereum and BSC addresses
const projectOneAbi = require(“../abi/projectOneAbi.json”) // Get the token ABI for the project. ABIs can be found on the Etherscan page for the contract if the contract has been verified. Otherwise you may need to ask your Solidity dev for it.
const numeral = require(“numeral”) // NPM package for formatting numbers
const db = require(“./db”) // Util for setting up DB and main DB methods
// Async function which takes in web3 collection, makes web3 calls to get current on chain data, formats data, and caches formatted data to MongoDB
const getProjectOneData = async (web3s) => {
// Unpack web3 objects for Ethereum and BSC
const {web3, bsc_web3} = web3s
// Get Ethereum block number
const blockNumber = await web3.eth.getBlockNumber()
// Get BSC block number — error handling used here due to unreliable BSC endpoints, best to add it for the Ethereum block number as well in production.
let bsc_blockNumber
try {
bsc_blockNumber = await bsc_web3.eth.getBlockNumber()
}
catch(err) {
bsc_blockNumber = 0
console.log(“CANT GET bsc_blockNumber”)
console.log(err)
}
// Collect addresses in one ‘addresses’ object
const {eth_addresses, bsc_addresses} = addresses
// Set number formatting default
numeral.defaultFormat(“0,0”);
// Instantiate all smart contract object(s)
// web3.eth.Contract() creates a smart contract object using the ABI and address of the contract which allows you to call all the smart contract functions listed in the ABI. Since we are not supplying a private key to our web3 object, we can only use it for reading on chain data, not for anything requiring signing — which is all we need for this project.
// Here we instantiate the Ethereum smart contract object
let projectOne = new web3.eth.Contract(projectOneAbi, eth_addresses.contract)
// Here we instantiate the BSC smart contract object
let bsc_projectOne
try {
bsc_projectOne = new bsc_web3.eth.Contract(projectOneAbi, bsc_addresses.contract)
}
catch(err) {
console.log(“couldn’t connect to BSC”)
console.log(err)
}
// For converting to proper number of decimals. We use this to convert from raw numbers returned from web3 calls to human readable formatted numbers based on the decimals for each token.
const convert = (num, decimal) => {
return Math.round((num / (10*10**(decimal-3))))/100
}
// Make tokenData object. This object is used for storing formatted and calculated results from web3 calls from both Ethereum and BSC web3 objects. It is divided into 3 sections for data on BSC, Ethereum, and aggregate data from both chains in ‘combined’.
let tokenData = {
combined: {
totalSupply: {value: null},
circulatingSupply: {value: null},
},
eth: {
totalSupply: {value: null},
circulatingSupply: {value: null},
},
bsc: {
totalSupply: {value: null},
circulatingSupply: {value: null},
}
}
// Get base Ethereum values
const burnt_on_eth = await projectOne.methods.balanceOf(eth_addresses.burnt).call()
tokenData.eth.totalSupply.value = await projectOne.methods.totalSupply().call()
const team_1 = await projectOne.methods.balanceOf(eth_addresses.team_1).call()
const team_2 = await projectOne.methods.balanceOf(eth_addresses.team_2).call()
const team_3 = await projectOne.methods.balanceOf(eth_addresses.team_3).call()
// Get base BSC values
try {
burnt_on_bsc = await bsc_projectOne.methods.balanceOf(bsc_addresses.burnt).call()
}
catch(err){
console.log(`burnt_on_bsc: ${err}`)
burnt_on_bsc = err
}
try {
tokenData.bsc.totalSupply.value = await bsc_projectOne.methods.totalSupply().call()
}
catch(err){
console.log(`tokenData.bsc.totalSupply.value: ${err}`)
tokenData.bsc.totalSupply.value = err
}
let bsc_team_1
try {
bsc_team_1 = await bsc_projectOne.methods.balanceOf(bsc_addresses.team_1).call()
}
catch(err){
console.log(`bsc_team_1: ${err}`)
bsc_team_1 = err
}
let bsc_team_2
try {
bsc_team_2 = await bsc_projectOne.methods.balanceOf(bsc_addresses.team_2).call()
}
catch(err){
console.log(`bsc_team_2: ${err}`)
bsc_team_2 = err
}
// In the following section we perform calculations on base values returned from web3 calls to get the final values we want to return in our API.
// Get derived values ETH
const team_eth = Number(team_1) + Number(team_2) + Number(team_3)
tokenData.eth.totalSupply.value -= burnt_on_eth
tokenData.eth.circulatingSupply.value = Number(tokenData.eth.totalSupply.value) — Number(team_eth)
// Get derived values BSC
const team_bsc = Number(bsc_team_1) + Number(bsc_team_2)
tokenData.bsc.totalSupply.value -= burnt_on_bsc
tokenData.bsc.circulatingSupply.value = Number(tokenData.bsc.totalSupply.value) — Number(team_bsc)
// Get joint values
tokenData.combined.totalSupply.value = tokenData.bsc.totalSupply.value + tokenData.eth.totalSupply.value
tokenData.combined.circulatingSupply.value = Number(tokenData.bsc.circulatingSupply.value) + Number(tokenData.eth.circulatingSupply.value)
// Below we add additional information which is not strictly necessary if the API is used only for CG and CMC listing, but may be desired for other purposes such as a token dashboard.
// Set up descriptions
tokenData.eth.totalSupply.description = “Total supply of projectOne on ETH”
tokenData.bsc.totalSupply.description = “Total supply of projectOne on BSC”
tokenData.eth.circulatingSupply.description = “Circulating supply of projectOne on ETH”
tokenData.bsc.circulatingSupply.description = “Circulating supply of projectOne on BSC”
tokenData.combined.totalSupply.description = “Total supply of projectOne (BSC & ETH)”
tokenData.combined.circulatingSupply.description = “Circulating supply of projectOne (BSC & ETH)”
// Set names
tokenData.eth.totalSupply.name = “Total Supply of projectOne on ETH”
tokenData.bsc.totalSupply.name = “Total Supply of projectOne on BSC”
tokenData.eth.circulatingSupply.name = “Circulating Supply of projectOne on ETH”
tokenData.bsc.circulatingSupply.name = “Circulating Supply of projectOne on BSC”
tokenData.combined.totalSupply.name = “Total Supply of projectOne on (BSC & ETH)”
tokenData.combined.circulatingSupply.name = “Circulating Supply of projectOne on (BSC & ETH)”
// Set converted and formatted value, block, and timestamp
const tokendata_eth = tokenData.eth
const tokendata_bsc = tokenData.bsc
const tokendata_combined = tokenData.combined
// Below we run through each of our tokendata objects for both chains and the combined chain data and convert or format when needed. We als add block number and date.
Object.keys(tokendata_combined).forEach(key => {
tokendata_combined[key].value = convert(tokendata_combined[key].value, 18)
tokendata_combined[key].formattedValue = numeral(tokendata_combined[key].value).format()
tokendata_combined[key].block = blockNumber
tokendata_combined[key].bsc_block = bsc_blockNumber
tokendata_combined[key].timestamp = Date()
})
Object.keys(tokendata_eth).forEach(key => {
tokendata_eth[key].value = convert(tokendata_eth[key].value, 18)
tokendata_eth[key].formattedValue = numeral(tokendata_eth[key].value).format()
tokendata_eth[key].block = blockNumber
tokendata_eth[key].timestamp = Date()
})
Object.keys(tokendata_bsc).forEach(key => {
tokendata_bsc[key].value = convert(tokendata_bsc[key].value, 18)
tokendata_bsc[key].formattedValue = numeral(tokendata_bsc[key].value).format()
tokendata_bsc[key].block = blockNumber
tokendata_bsc[key].timestamp = Date()
tokendata_combined[key].bsc_block = bsc_blockNumber
})
// Finally after all data has been collected and formatted, we set up our database object and call db.updateprojectOneData() in order to cache our data in our MongoDB database.
try {
const client = db.getClient()
db.updateprojectOneData(tokenData, client)
}
catch(err) {
console.log(err)
}
}
module.exports = getProjectOneData
Bringing it All Together in Server.js
Finally we bring everything together in Server.js. As I am assuming a familiarity with ExpressJS and databases / data caching I won’t be covering the utils/db.js module or the routes *folder. The *server.js code is fairly straightforward. The only thing of note is that the getChainData function is called here to start the token data update loop.
require(“dotenv”).config()
const express = require(‘express’)
const rateLimit = require(“express-rate-limit”);
const morgan = require(‘morgan’)
const bodyParser = require(‘body-parser’)
const cors = require(‘cors’)
const db = require(‘./utils/db’)
const projectTwoRoutes = require(‘./routes/projectTwo’)
const projectOneRoutes = require(‘./routes/projectOne’)
const getChainData = require(“./utils/getChainData”)
const removeTrailingSlash = require(‘./middleware/removeTrailingSlash’);
const PORT = process.env.PORT || 3001
// Call getChainData here to begin chain data update loop and start caching new data to database
getChainData()
const app = express()
// Limit request rate
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
// apply to all requests
app.use(cors())
app.use(bodyParser.text())
// limit requests
app.use(limiter);
// remove trailing slash
app.use(removeTrailingSlash);
// Logging
app.use(morgan(‘tiny’))
// Remove trailing slash
// Routes
app.use(/^\/$/, (req, res) => {
res.send(“Welcome to the Fomocraft API!”)
})
// add cached data to req
app.use(‘/v1/projectTwo’, async (req, res, next) => {
const client = db.getClient()
try {
await db.getCachedprojectTwoData(client).then(result => req.chainData = result)
}
catch(err){
console.log(“error getting data”)
console.log(err)
}
next()
})
// add cached data to req
app.use(‘/v1/projectOne’, async (req, res, next) => {
const client = db.getClient()
try {
await db.getCachedprojectOneData(client).then(result => req.chainData = result)
}
catch(err){
console.log(“error getting data”)
console.log(err)
}
next()
})
app.use(‘/v1/projectOne’, projectOneRoutes)
app.use(‘/v1/projectTwo’, projectTwoRoutes)
app.use((req, res) => {
res.status(404).json({error: true, message: “Resource not found”})
})
app.listen(PORT)
console.log(`App listening on ${PORT}`)
And that just about covers it! Again, you can find the full example code here, and if you have any further questions feel free to reach out to me directly on Twitter or Telegram at @noahniuwa and I’ll answer you there and update this article for clarification.
Top comments (1)
I am missing some steps.