APIs used:
  • Curve: Get all markets  

Congratulations, you’ve arrived at part 4 of this series. This means that not only do you know how to display all your token balances in your wallet app, you also know how to display all your LP tokens from DeFi protocols such as Curve, as well as all your Curve transactions.

What We Will Build

In this final part, you will be creating another useful feature that you can drop into your app: a table of all the liquidity pools on Curve. This table will feature important data points such as the near real-time constituent token balances within the pool, the pool type, the pool addresses, their TVL (Total Value Locked), as well as their supply APY (Annual Percentage Yield).

Your component will look like this:

Prerequisites

  • Basic familiarity with React.

  • Some HTML/CSS knowledge.

  • Fetching data using APIs.

Tutorial: Building a Liquidity Pool Table on Curve

Ready? Let’s get started!

(Estimated time to follow along: 20mins)

1

Figuring out what data you need

Let’s take a closer look at the Curve pools component.

From this, we can break down our data needs into the following fields:

  1. Pool name: tokens within the pool - e.g. ‘ETH / stETH’

  2. Pool contract address: the contract address of the liquidity pool - e.g. ‘0xdc24…7022’

  3. Pool type: the type of the pool - e.g. ‘Plain pool’ / ‘Crypto pool’

  4. APY: the annual percentage yield of the pool (a function of the swap fees earned from the pool)- e.g. ‘0.0003%’

  5. TVL: Total Value Locked within the pool - e.g. ‘$1694.0M’

  6. Pool token name - the name of the tokens within the liquidity pool- e.g. ‘Liquid staked Ether 2.0’

  7. Pool token balance - the balance of the tokens within the liquidity pool e.g. ‘480143 stETH’

  8. Value of token - the value of the tokens within the liquidity pool, as calculated at the quote rate at time of request e.g. ‘$832.9M’

As usual, a seemingly simply component with quite a few data points!

2

Identifying the right Covalent API endpoint to use

Luckily, you can get all of these data points using just one Covalent endpoint: the Class C endpoint for Curve, Get all markets endpoint.

Here is what the request path looks like:

https://api.covalenthq.com/v1/cq/covalent/app/curve/markets/?key={apiKey}

ℹ️ If you want to try it for yourself, be sure to replace the apiKey variable, and pop the endpoint into the browser. If you don’t have a Covalent API key, you can get it for free here.

This is what the response looks like (first 2 items):

Let’s take a closer look at the first item.

The response item contains most of the fields we are interested in: contract_ticker_symbol (Pool name for one token), pool_address (Pool address), pool_type (Pool Type), supply_apy (APY), tvl_quote (TVL). However, it only contains the token data for one side of the liquidity pool.

The data for the other token within the pool is in the next item:

You can identify that these belong to the same pool as they have the same pool_address.

Now that we know how to get the data, let’s start building!

3

Clone this starter kit & initialize the project

Open the terminal and run the following commands:

git clone https://github.com/xiaogit00/building-wallets.git

cd building-wallets

npm i

git checkout part4-defi-protocols-pools

npm start

Head over to localhost:3000 in your browser. You should see the following:

This is simply where we left off from Part 3.

If your page is stuck at the loading screen, you will most likely need to supply your API key in the .env file. Create a .env file that specifies your Covalent API key in this format:

REACT_APP_APIKEY='ckey_8bdxxxxxxxxxxxxxxxxxxxxxxxxxx99'

(If you don’t have a Covalent API key, you can get it for free here.)

Then, npm start again.

For the rest of this guide, we’ll be filling in the data for the pools component.

4

Fetching the Curve pool data

Head over to the components/Pools.js file. Within it, we have the JSX template for the Pools component. Our goal now is to fetch the relevant data first.

Add an effect hook using the Get all markets endpoint above and console.log the response.

Save, and npm start in your terminal.

const Pools = () => {
    const [data, setData] = useState(null)
    const apiKey = process.env.REACT_APP_APIKEY

    const poolsEndpoint = `https://api.covalenthq.com/v1/cq/covalent/app/curve/markets/`

    useEffect(() => {
        fetch(poolsEndpoint, {method: 'GET', headers: {
            "Authorization": `Basic ${btoa(apiKey + ':')}`
          }})
            .then(res => res.json())
            .then(res => {
                console.log(res.data.items)
            })
    }, [poolsEndpoint, apiKey])

Heading over the dev console, you should be able to see this:

Congratulations, you’ve successfully retrieved the Curve pools data!

5

Rendering the response items

The next steps are simple. With the data in hand, we are going to render them.

1. Pool Name

To render the pool name, we need to combine the contract_ticker_name fields of the items from the same pool (so that they appear as, say, ’ETH / stETH’). In order to achieve this, we shall group the items with the same pool_address into an array, much like what we did for part 3. The following function will do trick:

JavaScript
var groupedData = Object.values(
                    res.data.items.reduce((p, c) => {
                    (p[c.pool_address] = p[c.pool_address] || []).push(c)
                    return p
                }, {})

We shall add this to the effect hook, and set the data state variable:

JavaScript
import { useState, useEffect } from 'react';
import '../App.css';
import PoolTokenBalance from './PoolTokenBalance';

const Pools = () => {
    const [data, setData] = useState(null)
    const apiKey = process.env.REACT_APP_APIKEY

    const poolsEndpoint = `https://api.covalenthq.com/v1/cq/covalent/app/curve/markets/`

    useEffect(() => {
        fetch(poolsEndpoint, {method: 'GET', headers: {
            "Authorization": `Basic ${btoa(apiKey + ':')}`
          }})
            .then(res => res.json())
            .then(res => {
                var groupedData = Object.values(
                    res.data.items.reduce((p, c) => {
                    (p[c.pool_address] = p[c.pool_address] || []).push(c)
                    return p
                }, {})
            )
                setData(groupedData)
            })
    }, [poolsEndpoint, apiKey])

Console logging the data state variable, we see that the result is a grouped array:

To render the pool name, we’ll simply map over the items within the same array, and join the contract_ticker_symbol fields. Rendering it on the page:

<div className='poolTokenName'>{item.map(token => token.contract_ticker_symbol).join(' / ')}</div>

…and heading back to the browser:

We see our pool names. Awesome.

Moving on, let's render the pool type.

2. Pool type

Now, Curve pools are separated into different types. Namely, they are plain pools, meta pools, crypto pools, and lending pools. Each of them has got its own characteristics. The Covalent API returns these categories in the response for the Get all markets endpoint. The pool_type field of our response takes this list of possible values:

  • base_pool <> plain_pool

  • meta_pool

  • crypto_pool

  • base_pool <> lending_pool

Obviously, these values are pretty long, and we don’t want to render the whole string in our component. So let’s build a function to clean them up before we render them:

JavaScript
const cleanPoolType = (poolType) => {
    switch (poolType) {
        case 'base_pool <> plain_pool':
            return 'Plain Pool'
        case 'meta_pool': 
            return 'Meta Pool'
        case 'crypto_pool':
            return 'Crypto Pool'
        case 'base_pool <> lending_pool':
            return 'Lending Pool'
        default: 
            return poolType
    }
}

And finally rendering them on the page:

<div className='poolType'>{cleanPoolType(item[0].pool_type)}</div>

There we go - we see the pool type pop up the way we want them.

3. Rest of the data

There’s nothing too fancy about rendering the rest of the fields (APY, and TVL). You can reference how it’s done in the JSX snippet of our component below:

JavaScript
...
return (
<>
    <div className='rowPool clickable' key={item} onClick={() => toggleDisplay(i)}>
        <div className='left'>
            <div className='logoContainer'>
                <img className='poolTokenLogo' src='<https://res.cloudinary.com/dl4murstw/image/upload/v1678790049/spaces_-MFA0rQI3SzfbVFgp3Ic_uploads_F5ZS9RzAWKZnNxm9F85H_Curve-Logo-HighRez_zzlvug.webp>' alt='tokenlogo'/>
            </div>
            <div className='pool-left-info-container'>
                <div className='poolTokenName'>{item.map(token => token.contract_ticker_symbol).join(' / ')}</div>
                <a href={'<https://etherscan.io/address/>' + item[0].pool_address} target='_blank' rel='noreferrer'><div className='poolAddress'>
                    <div><img className='scroll' src='<https://res.cloudinary.com/dl4murstw/image/upload/v1668495918/scroll_bkmays.png>' alt='scroll' width='12px' /></div>
                    <div className='poolAddressText'> {truncateEthAddress(item[0].pool_address)}</div>
                </div>
                </a>
            </div>
        </div>

        <div className='poolType'>{cleanPoolType(item[0].pool_type)}</div>

        <div className='apy'>{item[0].supply_apy.toFixed(5) + '%'}</div>
        
        <div className='tvl'>${(item[0].tvl_quote / 1000000).toFixed(1)}M</div>
    </div>
</>

)

Within the code above, we have rendered the pool address, APY, as well as TVL, with a minimal cleanup process.

Saving it, we get the following:

Awesome!

Now we have a component that returns us all the pools of Curve, along with the APY, TVL, pool type, and contract address.

6

Add-on feature: constituent token balance

Just like in part 1, it’ll be neat to be able to check out what the constituent token balance is within a liquidity pool. And since we already have the data within the same response, why not render it as well? They are contained in the balance and quote fields of our response:

The approach we’ll take is similar to Part 1: triggering a render of a new <PoolTokenBalance /> component on a click of the row. This is the onClick handler (toggleDisplay) that we will add:

const Pools = () => {
	...
	const toggleDisplay = (i) => {
        setDisplay({
            ...display,
            [i]: !display[i]
        })
    }

	return (
	...
	<div className='rowPool clickable' key={item} onClick={() => toggleDisplay(i)}>
		...
	{data.map((item, i) => {
		...
		{display[i] ? <PoolTokenBalance items={item}/>: null}
		...
	})
	...

}

On the click of each row, the <PoolTokenBalance /> component gets rendered.

The <PoolTokenBalance /> component is fairly straightforward. It takes the items of each row as props (the one we grouped earlier) and renders the constituent tokens along with their balances:

// PoolTokenBalance.js
import { useState, useEffect } from 'react';
import '../App.css';

const PoolTokenBalance = ({ items }) => {
   return (
    <>  
        <div className='poolTokenBalanceHeader'>  
            <div className='rowHeaderPool'>Token Balance</div>
            <div className='rowHeaderAPY'> Value (USD)</div>
        </div>
        <div className='poolTokenContainer'>
            {items.map(token => {
                return (

                    <div className='redeemTokenRow'>
                        <div className='redeem-left'> 
                            <div className='poolTokenName'>{token.contract_name}</div>
                            <div className='poolTokenBalance'>{(token.balance / (10**token.contract_decimals)).toFixed(5)} {token.contract_ticker_symbol}</div>
                        </div>
                        <div className='redeem-right tvl'> ${(token.quote/1000000).toFixed(1) + 'M'} </div>
                    </div>
                    
                )
            })}
        </div>
    </>
   )
}

export default PoolTokenBalance

Save this and head over to your browser, you’ll be able to see the following:

Congratulations. You have managed to build a very compact component that shows you all the data from Curve liquidity pools, along with the pool balance!

If you have not been following along and would just like the end state, you can do the following to see this app locally:

git clone https://github.com/xiaogit00/building-wallets.git

cd building-wallets

npm i

git checkout part4-defi-protocols-pools-end

npm start

Just be sure to add your API key in a .env file, like so:

REACT_APP_APIKEY='ckey_8bdxxxxxxxxxxxxxxxxxxxxxxxxxx99'

All in a Day’s Work

The on-chain data of DeFi protocols can be complex. Without the right tools, you might just end up spending a lot of your time figuring out the nitty gritty of their smart contract architecture, whilst still not getting the high-level overview data that you need. Hopefully, by the end of this series, you will be able to easily retrieve DeFi protocols’ on-chain data for your app, whatever it is you want to build.

Throughout this series, we’ve illustrated how you can get DeFi protocols’ data using the specific example of Curve. The beauty of Covalent API is that you can get the data of other protocols just as easily: Aave, Balancer, Compound, Dodo, Frax, InstaDapp, Lido, Yearn, and more.

Do sign up for a free API key and try it yourself.

Happy buidling!