- Get token balances for address
- Curve: Get balances for a wallet address
Now, let us move on to the fun part. In the previous section, we focused on building a simple component that displays all your token holdings. We learned that getting all the tokens for your address is super simple: all it took was one Covalent API call. In this part, we are going to expand the functionality of your wallet to return the wallet activities from your favorite DeFi protocols, such as Aave, Curve, Balancer, Frax, InstaDapp, Yearn, and more.
Getting wallet data from DeFi protocols
Part of what makes Web3 wallets exciting is that they are composable. While Web2 applications and their data function mostly in silos (Instagram would never interface with Tiktok, for instance), Web3 data is public, and all write functionalities with the DeFi protocols’ smart contracts are public as well. This means that our wallet is not ‘just’ a wallet - it is a portal for us to engage with the rest of the Web3 world. But how do we hook our wallets to the Web3 world at large?
Well, again, two parts: first, we need to read on-chain data from those protocols, and second, we need to write on-chain data to those protocols’ smart contracts by executing their functions. This section, like the last one, focuses on reading the data. With these data, we can quickly get a sense of what Liquidity Provider (LP) tokens we have and how much they’re worth (these are the tokens you receive when you supply liquidity to a liquidity pool). We can even get all recent transactions we have made with the protocol, along with the APY (Annual Percentage Yield) of the pools. All this enables your wallet to function almost like an app store, with the DeFi protocols themselves being the individual apps.
What we will be building
In this guide, we will build the following Curve LP Token Balances component:
On the left, you get each LP token’s name, amount, and ticker. On the right, you get their values in USD, as well as the rewards you accrue from the swap fees for holding them. Beneath each row, you get their underlying redeemable tokens.
Pretty slick, no?
Before we begin, let’s dive into a bit of background about Curve first.
What is Curve?
Curve Finance (https://curve.fi) is one of the top protocols in DeFi. It allows you to swap between stablecoins with low fees and slippage. Let’s say I want to swap $8000 USDC to USDT. Instead of going through a centralized service like Binance, I can do it purely on-chain using Curve. That is the utility part. (In version 2, Curve has added support for more volatile assets such as the tricrypto pool - wBTC, ETH, and fUSDT - but it is still mainly known as a stableswap protocol.)
Behind the scenes, Curve enables us to swap stablecoins by using liquidity pools. Liquidity providers inject stablecoins into these pools, enabling other traders to trade against them. In return, liquidity providers get pool rewards accrued against LP tokens that they receive.
What is unique about Curve is simply its niche in the stableswaps space (swapping stablecoins). For everyday DeFi users, this has proven to be a vital use case. For liquidity providers, it gives them an opportunity to earn yield on a stable asset.
What kind of on-chain data is generated by transactions on Curve?
To understand the kinds of data produced by Curve, we need to understand first what are the common user actions you see on Curve.
1. Swaps
This is the most common action performed on Curve. When I want to exchange USDT to USDC, I perform a Swap, which is a type of transaction event containing details such as how much of token A is swapped for token B at what rates and with what fees. We’ll be adding these transaction events into our Curve LP Token Balances component.
2. Adding/Removing Liquidity
How do the pools hold any liquidity in the first place? They come from Liquidity providers injecting their tokens into the pools. When a liquidity provider injects USDC into, say, 3Pools, they will receive 3Crv LP tokens. At any future point, they can swap their LP tokens back for USDC.
This action generates on-chain data such as:
the value of the LP tokens
swap fee rewards of each LP token.
3. Pools
Lastly, the liquidity pools themselves also have certain attributes such as the pool token balances, APY, TVL, and so on. We’ll build a section to see those as well.
Let’s get started!
(Estimated time to follow along: 20mins)
Prerequisite
Basic familiarity with React
Some HTML/CSS knowledge
Fetching data using APIs
Step 1: Clone this starter kit and 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 part2-defi-protocols
npm start
Head over to localhost:3000
in your browser. You should see the following:
This is just a simple template to get you started. In this tutorial, we’ll be focusing on building out the top most component (LP Tokens). In later parts of this series, we’ll build the Transactions and Pools component. If you open up src/App.js
, you’ll see that there are three simple components (<Tokens />
, <Transactions />
, and <Pools />
)that corresponds to the sections you see on this page in the components
folder.
// src/App.js import Tokens from "./components/Tokens"; import Transactions from "./components/Transactions"; import Pools from "./components/Pools"; import './App.css'; function App() { return ( <> <div className='container'> <div className="headerContainer"> <img src="<https://res.cloudinary.com/dl4murstw/image/upload/v1678790049/spaces_-MFA0rQI3SzfbVFgp3Ic_uploads_F5ZS9RzAWKZnNxm9F85H_Curve-Logo-HighRez_zzlvug.webp>" alt="curveLogo" height="96px"/> <h1>Curve</h1> </div> <Tokens /> <Transactions /> <Pools /> </div> </> ); } export default App;
Open up the <Tokens />
component, and let’s begin.
Step 2: Fetching the LP tokens data
First, be sure to 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, add the following code to the <Tokens.js>
component to fetch the data:
const Tokens = () => { const [data, setData] = useState([...Array(3).keys()]) const walletAddress = '0x2702811b54ad6F58BAdBEb17007a1303a21Af45F' const protocol = 'curve' const apiKey = process.env.REACT_APP_APIKEY const lpTokenBalancesEndpoint = `https://api.covalenthq.com/v1/cq/covalent/app/${protocol}/balances/?address=${walletAddress}` useEffect(() => { fetch(lpTokenBalancesEndpoint, {method: 'GET', headers: { "Authorization": `Basic ${btoa(apiKey + ':')}` }}) .then(res => res.json()) .then(res => console.log(res.data.items)) }, [lpTokenBalancesEndpoint, apiKey]) ....
Save and head to the browser again. Opening up the dev console, you should be able to see the following:
Congratulations, you’ve successfully retrieved the Curve LP tokens data for the wallet!
Step 3: Understanding the data response
The endpoint we’ve used above
const lpTokenBalancesEndpoint = `https://api.covalenthq.com/v1/cq/covalent/app/${protocol}/balances/?address=${walletAddress}`
is the Curve - Get balances for wallet address endpoint. This is what the response for one item looks like:
One item in the response corresponds to the balances information of one LP (Liquidity Provider) token held by the user. The endpoint returns all LP token holdings of a wallet address.
One thing to note about this endpoint is that it will only return responses if the wallet holds any LP tokens. If your wallet address does not have LP tokens, it will return an empty array. For the sake of having something to display, I’ve picked a wallet that currently holds quite a few LP tokens.
const walletAddress = '0x2702811b54ad6F58BAdBEb17007a1303a21Af45F'
Step 4: Rendering the live token data
Replace the placeholder values inside the return statement of the <Token.js>
component:
{data.map(item => { return ( <div className='row' key={item}> <div className='left'> <div className='logoContainer'> <img className='tokenLogo' src='<https://res.cloudinary.com/dl4murstw/image/upload/v1677729872/greybox_zkioqf.png>' alt='tokenlogo'/> </div> <div className='left-info-container'> <div className='tokenName'>{item.contract_name}</div> <div className='tokenBalance'>{(Number(item.balance) / (10**item.contract_decimals)).toFixed(6)} {item.contract_ticker_symbol}</div> </div> </div> <div className='right'> <div className='tokenValue'>${item.quote.toFixed(3)}</div> <div className='percentageChange'>(+{item.rewards_quote.toFixed(4)})</div> </div> </div> ) })}
Save, and voilà:
We’ve successfully displayed all the LP token holdings of the selected wallet.
The number on the top right-hand side of each row indicates the value of the token in USD, whereas the number below displays the rewards you get (in USD).
A word on the rewards: this refers purely to the rewards you get in proportion to the swap fees earned in the pool. It does not include rewards from liquidity farming activities, such as when you stake your token in a reward gauge.
There we have it, we’ve built a component that tells us our LP token holdings, its value in USD, as well as the rewards we can redeem, all in one glance.
Now, our component is perfectly fine if we are satisfied with knowing just the LP token values. However, what if we want to know how much these tokens would be worth if we redeemed them right away?
Step 5: New feature: redeemable tokens
In this step, we’re going to create an add-on that allows us to get the underlying token values. It looks something like this:
Expanding each row on click to display the LP tokens’ underlying redeemable tokens and their values.
What are the underlying redeemable tokens of an LP token? When you inject liquidity into a Curve pool, you get LP tokens back. When you want to withdraw your original assets, you will be swapping those LP tokens for your original assets. But just what amount of your original tokens would you get? To understand this, we need to dive in a bit more on the idea of pool proportions and swap rates.
Pool proportions
Every pool has a variable pool token proportion, depending on the balance of tokens within the pool. This balance is always changing. If users inject more of token A and withdraw more of token B, token A will have a higher pool proportion.
Swap rates
Importantly, pool proportions affect the exchange rate between assets within a pool. From the pool above, we see that USDC has a 39.28% pool balance whilst USDT has 21.80%. If we were to swap the token with higher proportion (USDC) for a token of smaller proportion (USDT), the exchange rate we get will be less than 1. On the other hand, if we were to swap USDC for USDT, Curve rewards us with a very good exchange rate of more than 1. When pools are out of proportion, the under-balanced asset will be more expensive whilst the over-balanced one will be cheaper. This is Curve’s in-built mechanism of incentivizing traders to bring the pool to balanced proportion.
For liquidity providers, this has implications on how much you’ll be able to swap your LP tokens for. The optimal rate at which you get the best value for your swap (assuming all things constant) is to swap at the pool proportion rate. You can, of course, choose other levels of proportions, but if you choose to swap for a higher proportion of an under-balanced asset, the value you get out of your LP tokens will be less. This applies the other way round as well: swapping for more tokens of a higher pool balance will get you the most value. So knowing the pool proportions before you make a swap helps guide your swapping strategies.
Luckily for us, with Covalent’s Get Redeemable token balance
endpoint, we’ll be able to get the pool proportion easily. It even returns the balance of the underlying token we are getting.
Let’s go ahead and build this add-on feature.
Within your project folder, you can commit your current progress. Then, hop over to the branch for this add-on:
git checkout part2-defi-protocols-addon
You’ll be able to find that the skeletal framework for this feature has already been created for you. Run npm start
, and click on one of the rows:
You’ll see that it expands to display some default values.
Opening up the <RedeemableTokens />
component, we find that it has the same familiar structure to what we’ve done before. First, we fetch the data in an effect hook, and then we display it. This time, I’ve already fetched it for you, but you’ll need to fill up the placeholder values:
// RedeemableTokens.js import { useState, useEffect } from 'react'; import '../App.css'; const RedeemableTokens = ({ address, poolAddress }) => { const [tokenData, setTokenData] = useState(null) const [loading, setLoading] = useState(false) const apiKey = process.env.REACT_APP_APIKEY const protocol = 'curve' const redeemableTokenBalanceEndpoint = `https://api.covalenthq.com/v1/cq/covalent/app/${protocol}/tokens/?key=${apiKey}&pool-address=${poolAddress}&address=${address}` useEffect(() => { setLoading(true) fetch(redeemableTokenBalanceEndpoint, {method: 'GET', headers: { "Authorization": `Basic ${btoa(apiKey + ':')}` }}) .then(res => res.json()) .then(res => { setLoading(false) setTokenData(res.data.items) }) }, [redeemableTokenBalanceEndpoint]) if (loading) { return ( <div className='loading'>Loading redeemable tokens...</div> ) } else if (!loading && tokenData) { return ( <> <div className='rowHeadersRedeem'> <div className='rowHeaderPool'>Tokens</div> <div className='rowHeaderAPY'> Redeemable Value (USD)</div> </div> <div className='redeemTokenContainer'> {tokenData.map(token => { return ( <div className='redeemTokenRow'> <div className='redeem-left'> <div className='poolTokenName'>token0</div> <div className='poolTokenBalance'>000 USDC</div> </div> <div className='redeem-right tvl'> $00 </div> </div> ) })} </div> </> ) } } export default RedeemableTokens
Note that the Get Redeemable token balance
endpoint has this query format:
https://api.covalenthq.com/v1/cq/covalent/app/${protocol}/tokens/?key=${apiKey}&pool-address=${poolAddress}&address=${address}
Query parameters:
protocol - string: the name of the protocol. Currently, we support Aave, Balancer, Compound, Curve, Dodo, Frax, Instadapp, Lido, and Yearn.
apiKey - string: your API key. Note: passing the API key via the query string is not advised, and is used here for mainly demonstration purposes. Use basic auth instead for all other cases.
poolAddress - string: the address of the liquidity pool on Curve.
address - string: the wallet address you’re querying.
And a sample response looks like this:
Try console logging the tokenData
state to see what values you’ll need to fill!
Given it a go? Here is what it should be:
... return ( ... <div className='redeemTokenRow'> <div className='redeem-left'> <div className='poolTokenName'>{token.contract_name}</div> <div className='poolTokenBalance'>{(token.value / (10**token.contract_decimals)).toFixed(5)} {token.contract_ticker_symbol}</div> </div> <div className='redeem-right tvl'> ${token.quote.toFixed(2)} </div> </div> ... ) ...
Finally, replace the token images with a default Curve image, save it and run npm start
...
<img className='tokenLogo' src='<https://res.cloudinary.com/dl4murstw/image/upload/v1678790049/spaces_-MFA0rQI3SzfbVFgp3Ic_uploads_F5ZS9RzAWKZnNxm9F85H_Curve-Logo-HighRez_zzlvug.webp>' alt='tokenlogo'/>
Voilà! We now have a component that tells us everything about our LP tokens, as well as their underlying tokens.
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 part2-defi-protocols-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
Good job, you’ve successfully reached the end of this tutorial. Hopefully, you can see that displaying a user’s LP token holdings, along with their redeemable token value, is super simple with the help of the Covalent API.
You have probably seen at the start that the LP token balances is only one part of the protocol data a user might be interested in. What’s very important as well is the list of transactions we’ve done with the protocol. In the next part, we’ll be building the protocol transactions component. Stay tuned!