Building Web3 Wallets (Part 6) - Multi-chain Portfolio Tracker

In this guide, you'll learn how you can build a multi-chain portfolio tracker that fetches tokens across 100+ chains in less than 15 minutes.

Previously, if you wanted to know whether a wallet address is active on a particular chain, you would have to query the user’s balances for that chain. This is a fairly inefficient process. Suppose you want to definitively know which chains a user is active on, and suppose your universe of chains is more than 100 (which is quite common given the state of things) - using an endpoint like GoldRush’s Get Balances for address, you will have to make 100 API calls, one for every chain.

That is super inefficient, both from the perspective of bandwidth wastage, as well as credit cost.

Luckily, we have a fresh new endpoint to tackle this problem. Introducing…the Get wallet activity endpoint🔥🎉

What does this endpoint do? This is actually quite a simple endpoint, but it does something really powerful. If you supply it a wallet address, it’ll return you a list of all the chains that the address is active on (including testnets) from across 100+ supported chains.

This is what a request looks like:

https://api.covalenthq.com/v1/labs/activity/{walletAddress}/?key={APIKEY}

and this is a sample response:

{ "data": { "updated_at": "2023-05-12T02:21:34.916062868Z", "address": "0xdafea492d9c6733ae3d56b7ed1adb60692c98bc5", "items": [ { "name": "eth-mainnet", "chain_id": "1", "is_testnet": false, "db_schema_name": "chain_eth_mainnet", "label": "Ethereum Mainnet", "category_label": "Ethereum", "logo_url": "<https://www.datocms-assets.com/86369/1669653891-eth.svg>", "black_logo_url": "<https://www.datocms-assets.com/86369/1669619544-ethereum.png>", "white_logo_url": "<https://www.datocms-assets.com/86369/1669619533-ethereum.png>", "is_appchain": false, "appchain_of": null, "last_seen_at": "2023-05-12T02:19:23Z" }, { "name": "matic-mainnet", "chain_id": "137", "is_testnet": false, "db_schema_name": "chain_matic_mainnet", "label": "Polygon Mainnet", "category_label": "Polygon", "logo_url": "<https://www.datocms-assets.com/86369/1677870347-property-1-polygon-zkevm-icon-white.svg>", "black_logo_url": "<https://www.datocms-assets.com/86369/1677870457-property-1-polygon-white.png>", "white_logo_url": "<https://www.datocms-assets.com/86369/1677870452-property-1-polygon-colour.png>", "is_appchain": false, "appchain_of": null, "last_seen_at": "2023-04-09T15:40:44Z" }, { "name": "bsc-mainnet", "chain_id": "56", "is_testnet": false, "db_schema_name": "chain_bsc_mainnet", "label": "BNB Smart Chain", "category_label": "Binance", "logo_url": "<https://www.datocms-assets.com/86369/1670003808-binance-smart-chain-icon-white.svg>", "black_logo_url": "<https://www.datocms-assets.com/86369/1670003836-biance-smart-chain-white.png>", "white_logo_url": "<https://www.datocms-assets.com/86369/1670003837-biance-smart-chain-colour.png>", "is_appchain": false, "appchain_of": null, "last_seen_at": "2023-04-27T09:00:00Z" } ] }, "error": false, "error_message": null, "error_code": null }

As you can see, each item in the items array corresponds to a chain object that contains useful attributes like chain name (category_label), chain logo (logo_url), and even flags like is_testnet.

With all these data, we can easily build a crucial component of a multi-chain portfolio tracker - chain selector buttons:

Using these buttons as switches, we can then make separate API calls to the Get balances for address endpoint to get a user’s token balances, allowing us to display the following pie chart, for instance:

A previous problem that requires more than 100+ API calls will now be reduced to as many chains as you have wallet activity on - which, in this case, is 6!

Excited to try it out? This is what this tutorial is for. By the end of it, you would have built the following component:

If you just want to look at the repo yourself, you can find it here. If not, follow along for a step by step guide.

Ready? Let’s begin!

(Estimated time to follow along: 20mins)

Prerequisite

  • Basic familiarity with React

  • Some HTML/CSS knowledge

  • Fetching data using APIs

  • Basic familiarity with UI libraries like Antd and Recharts (both of which will be used)

Step 1: Initialize the project

Run the following commands to download the template we’ll use for this guide:

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

cd building-wallets

npm i

git checkout part6-multichain-portfolio

npm start

You should be able to see the following in your browser:

This is just a template to get you started. If you go to App.js, you’ll see that we render two components, <ChainSelector /> and <TokenAllocation />, corresponding to the two sections on the page above.

Everything is styled with App.css.

Now, let’s replace the dummy data with some fresh ones.

Step 2: Fetch the data

The first data we want to fetch is the multi-chain wallet activity data using the Get wallet activity endpoint. We’ll do so in the <App /> component using an effect hook:

function App() { const [chains, setChains] = useState() const walletAddress = '0x0b17cf48420400e1D71F8231d4a8e43B3566BB5B' const walletActivityEndpoint = `https://api.covalenthq.com/v1/labs/activity/${walletAddress}/` const apiKey = process.env.REACT_APP_APIKEY useEffect(() => { fetch(walletActivityEndpoint, {method: 'GET', headers: { "Authorization": `Basic ${btoa(apiKey + ':')}` }}) .then(res => res.json()) .then(res => { console.log(res.data.items) setChains(res.data.items) }) }, [walletActivityEndpoint, apiKey])

As always, be sure to supply your GoldRush API key in the .env file, in the following format:

REACT_APP_APIKEY='ckey_8bdxxxxxxxxxxxxxxxxxxxxxxxxxx99'

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

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

Congratulations, you’ve successfully fetched the wallet activities data for the specified walletAddress.

Step 3: Rendering the chain selector buttons

To render the chain selector buttons, pass the chains state variable as a prop into <ChainSelector /> component within App.js

<ChainSelector chains={chains} />

and then head over to ChainSelector.js. Replace the placeholder values with the data stored in chains:

//ChainSelector.js import { Button } from 'antd' const ChainSelector = ( { chains } ) => { ... <Button type="primary" icon={<Logo url={item.logo_url}/>} size="small" onClick={() => handleChainSelect(item.chain_id)}> {item.category_label} </Button> ... }

Saving and returning to localhost:3000, you should now be able to see this:

Awesome!

However, you will notice that we have rendered two Ethereum buttons above. This is because one of the buttons is for mainnet while the other one is for testnet. Let’s exclude the testnet data at the level of our response, by including the following filter:

//App.js useEffect(() => { fetch(... .then(res => { const excludeTestnet = res.data.items.filter(item => item.is_testnet === false) setChains(excludeTestnet) })

Saving the project...

Great, now there are no duplicates. We’ve successfully built the chain switches for our multi-chain portfolio app.

Step 4: Fetching wallet balance data for a particular chain

Having chain switches are not useful if we don’t want to also display some useful data from those chains. In the following part, we’ll fetch the data to display the token composition of the wallet:

Head over to the <TokenAllocation /> component and fetch the data in an effect hook using the Get balances for address endpoint, like so:

const TokenAllocation = ( { walletAddress, selectedChainId} ) => { const [loading, setLoading] = useState(false) const [data, setData] = useState() const balancesEndpoint = `https://api.covalenthq.com/v1/${selectedChainId}/address/${walletAddress}/balances_v2/` const apiKey = process.env.REACT_APP_APIKEY useEffect(() => { setLoading(true) fetch(balancesEndpoint, {method: 'GET', headers: { "Authorization": `Basic ${btoa(apiKey + ':')}` }}) .then(res => res.json()) .then(res => { console.log(res.data.items) setData(res.data.items) setLoading(false) }) }, [balancesEndpoint, apiKey])

Here, you’ll notice that we have supplied the balancesEndpoint with a selectedChainId variable, and walletAddress. This is because the balances endpoint requires a specific chain and wallet address to fetch balances from.

Now, we have not implemented these two props yet, so let’s go back to define them and add them to our <TokenAllocation /> rendered in App.js as props:

function App() { const [selectedChainId, setSelectedChainId] = useState(1) ... return( <TokenAllocation selectedChainId={selectedChainId} walletAddress={walletAddress}/>

Saving this and heading to dev console, you should be able to see that the wallet data is fetched:

Saving the project and heading to localhost:3000...

We see that each row of token is now being generated dynamically!

Of course, the values are still being hard coded, so let’s change that.

Step 5: Cleaning the response

Before we edit the dummy data, we need to clean up our fetched balances responses a little.

By default, each response using the Get balances for address endpoint returns us this:

{ "contract_decimals": 18, "contract_name": "Ether", "contract_ticker_symbol": "ETH", "contract_address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "supports_erc": null, "logo_url": "<https://www.datocms-assets.com/86369/1669619533-ethereum.png>", "last_transferred_at": "2023-05-09T08:34:59Z", "native_token": true, "type": "cryptocurrency", "balance": "12957712048094486472", "balance_24h": "12957712048094486472", "quote_rate": 1765.843, "quote_rate_24h": 1796.617, "quote": 22881.285, "pretty_quote": "$22,881.29", "quote_24h": 23280.045, "pretty_quote_24h": "$23,280.04", "nft_data": null, "is_spam": null }

In order to display the data for the Token Allocation component

We’ll need to calculate how much an individual token is valued as a percentage of the total value of a wallet user’s holdings.

To do that, a [transformData function] is defined within utils.js and called in TokenAllocations.js, which takes the raw response from the GoldRush Get balance for address endpoint, and transforms each item into the following shape:

{ "logo": "<https://www.datocms-assets.com/86369/1669619533-ethereum.png>", "name": "ETH", "ratio": 0.4564906811132276, "balance": 12.957712048094487, "quote": 22868.004 } ...

where quote is the value of the token in USD, and ratio is the amount proportion to total value.

(I wouldn’t get into the details of this function, but you can check it out here. )

Here is where we’ll transform the data:

//TokenAllocation.js useEffect(() => { setLoading(true) fetch(balancesEndpoint, {method: 'GET', headers: { "Authorization": `Basic ${btoa(apiKey + ':')}` }}) .then(res => res.json()) .then(res => { const { newData, totalValue } = transformData(res.data.items) console.log(newData) setData(newData) setLoading(false) }) }, [balancesEndpoint, apiKey])

Saving the edits, we will see the following on screen:

Awesome!

Next, we’ll need to feed the token data into the pie chart, contained within the <Diagram /> component.

Step 6: Rendering the pie chart

Rendering the pie chart is easy. We’ve already cleaned the data into the format required for the Recharts pie chart, so let’s tweak the <Diagram /> component to the following:

import { PieChart, Pie, Cell } from 'recharts'; import { COLORS } from '../utils'; const Diagram = ({ data }) => { //Delete the dummyData return ( <PieChart width={200} height={200}> <Pie data={data} innerRadius={60} outerRadius={80} fill="#8884d8" paddingAngle={5} dataKey="quote" //change this to 'quote' > {data.map((entry, index) => ( <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} /> ))} </Pie> </PieChart> ) } export default Diagram

Saving and heading over to browser:

Nice. The proportions look just about right.

Now, there’s two more features to implement before we wrap it up. The first one is the chain selector logic. The second one is the net worth field. Let’s give the multi-chain selector feature a go.

Step 7: Implementing multi-chain selector feature

First, remember the selectedChainId state variable in App.js we created earlier?

function App() { const [chains, setChains] = useState() const [selectedChainId, setSelectedChainId] = useState(1)

We’ll use this state variable to control which chain’s data is being fetched.

First, within App.js, let’s pass this selectedChainId state into our <TokenAllocation /> component:

//App.js <TokenAllocation selectedChainId={selectedChainId} walletAddress={walletAddress}/>

Then, let’s create an event handler:

//App.js const handleChainSelect = (chainId) => { setSelectedChainId(chainId) }

We’ll pass this handler down as a prop to our <ChainSelector /> component:

//App.js <ChainSelector chains={chains} handleChainSelect={handleChainSelect}/>

And within ChainSelector.js, we assign it to the <Button /> element:

//ChainSelector.js <Button type="primary" icon={<Logo url={item.logo_url}/>} size="small" onClick={() => handleChainSelect(item.chain_id)}>

And that’s about it! Clicking on each button will set the selectedChainId state, which will fetch the respective chain’s data in the <TokenAllocation /> component.

Looking amazing.

Now for the final touch: our net worth field shows $000. Let’s fix that.

To calculate the ‘net worth’ of each chain, we need to sum up all the quote values of the fetched balances response. You can implement it like so:

//TokenAllocation.js const TokenAllocation = ( { walletAddress, selectedChainId} ) => { ... const [netWorth, setNetWorth] = useState() useEffect(() => { ... .then(res => { const { newData, totalValue } = transformData(res.data.items) setNetWorth(totalValue) ...

Saving and going to localhost:3000,

A multi-chain portfolio tracker, powered by only 2 GoldRush endpoints!

We have arrived at our end product.

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 part6-multichain-portfolio-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

Congratulations. You’ve successfully managed to build a multi-chain portfolio tracker that allows you to fetch your user’s token balances across all 100 chains that the address can potentially be active on, all under…15 minutes?

Hopefully you can see how powerful the brand new Get wallet activity endpoint is, especially when used with other popular GoldRush endpoints such as Get balances for address, or Get transactions for address. In this example, we have used it as a switch to get all the user’s balances for a chain. But you can well use it to get all transactions of a user, all DeFi protocol interactions, all NFTs, for multiple chains that a user is active on. Experiment and try it for yourself today.

Happy buidling!

If you haven’t already, get your FREE API key today and give it a spin!


Like this how-to guide? Give it a RT on Twitter.

Not fulfilling what you need? Reach out to us and let us know how to improve this endpoint.

Read more