Building Web3 Wallets (Part 3) - Displaying Curve Transactions

Have you wondered how you can easily retrieve all your transactions on a DeFi protocol like Curve? In this part, we'll build a React component to do that.

In the last section, we built a component that displays the Liquidity Provider (LP) tokens of a wallet address. With the power of GoldRush’s Get Redeemable token balance endpoint, we even managed to find out what the underlying redeemable tokens are worth. This is what our end product looked like:

While it is good to see what LP tokens you have and what can you redeem them for, it is perhaps even more useful to be able to quickly see your interaction history with the Curve protocol. This may be useful if you’re building a DeFi taxation tool, for instance, or a DeFi portfolio manager.

In this section, we’ll build out a Transactions component that shows you all the transactions that a wallet address has made on the Curve protocol. You’ll end up with something like this:

Ready? Let’s get started!

(Estimated time to follow along: 20mins)

Prerequisites

  • Basic familiarity with React

  • Some HTML/CSS knowledge

  • Fetching data using APIs

Tutorial: Building a Curve Transactions Component

1

Figuring out what data you need

Let’s take a closer look at the Transactions component that we want to build.
From this, we can break down our data needs into the following fields:
  1. Action: the action associated with the transaction - e.g. 'Swap’ or ‘Add’
  2. Date: the date of the transaction - e.g. ‘Oct 14’
  3. Token 1: the token out of wallet - e.g. ‘USDC’
  4. Token 2: the token received, e.g. ‘USDT’
  5. Token 1 value: the value of token out, e.g. ‘-$8000’
  6. Token 2 value: the value of token received, e.g. ‘+$8000’
  7. Transaction Fees (ETH): fees for the swap, e.g. ‘0.0031 ETH’
  8. Fees quote (USD): fees for the swap in USD, e.g. ‘($15.00)’
Again, a seemingly simple component, with a lot of data fields!
2

Identifying the right GoldRush API endpoint to use

Luckily, you can get all of these data points using just one GoldRush endpoint: the Class C endpoint for Curve, Get transactions by wallet address endpoint.
Here is what the request path looks like:
https://api.covalenthq.com/v1/cq/covalent/app/curve/transactions/?address={walletAddress}&key={apiKey}
ℹ️ If you want to try it for yourself, be sure to replace the walletAddress and apiKey variables, and pop the endpoint into the browser. If you don’t have a GoldRush API key, you can get it for free here.
This is what the response looks like:
Let’s take a closer look at one item.
The response item contains most of the fields we are interested in: event_name (action), block_signed_at (date), contract_ticker_symbol (Token 1 ticker), value_quote (Token 1 value), fees_paid (fees), and gas_quote (fees in USD). However, it only contains the token data for one side of the swap. The data for the other side of the swap is in the next item:
These responses correspond to what we see on Etherscan:
Now that we know how to get the data, let’s dive in and build our component!
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 part3-defi-protocols-txns
npm start
Head over to localhost:3000 in your browser. You should see the following:
If you don’t, it’s possibly because you have not created the .env file. Create it at your project root and add your GoldRush API to the REACT_APP_APIKEY variable, like so:
REACT_APP_APIKEY='ckey_8bd56xxxxxxxxxxxxxxxxxxxx499'
Save, and head back to your browser.
The component you see on your screen is basically what we created for part 2. Of course, the actual LP token holdings might differ on your screen depending on whether the address’s 3Crv token holdings have changed. The wallet I’ve used is the same one as above: 0xc0152cd4c429b7273db278a0355f0dfc9edbe840.
With this framework in place, it's time to fetch the data!
4

Fetching the LP token data

Head over to the components/Transactions.js file. Within it, we have the JSX template for the Transactions section. Our goal now is to fetch the relevant data first.
Add an effect hook using the Get transactions by wallet address endpoint above and console.log the response.
const Transactions = () => {
    const [data, setData] = useState([...Array(3).keys()])
    const [loading, setLoading] = useState(false)

    const apiKey = process.env.REACT_APP_APIKEY
    const walletAddress = '0xc0152cd4c429b7273db278a0355f0dfc9edbe840'
    const lpTransactionsEndpoint = `https://api.covalenthq.com/v1/cq/covalent/app/curve/transactions/?address=${walletAddress}`

    useEffect(() => {
        setLoading(true)
        fetch(lpTransactionsEndpoint, {method: 'GET', headers: {
          "Authorization": `Basic ${btoa(apiKey + ':')}`
        }})
          .then(res => res.json())
          .then(res => {
            console.log(res.data.items)
            setLoading(false)
          })
      }, [lpTransactionsEndpoint, apiKey])
...
Save, and npm start in your terminal.
Heading over the dev console, you should be able to see this:
Congratulations, you’ve successfully retrieved the Curve protocol transaction data for the wallet!
5

Cleaning the data

Now, there are a couple of things that we’ll need to do to get the data rendered in precisely the way we want.
First, as mentioned above, each of the items in the response array contains information of a specific token within a transaction.
For a transaction involving 2 tokens, there will be 2 corresponding items. We would thus need to group the items from the same transactions together for easier display. You can do it via a function like so:
...
useEffect(() => {
        setLoading(true)
        fetch(lpTransactionsEndpoint, {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.tx_hash] = p[c.tx_hash] || []).push(c)
                    return p
                }, {})
            )
            setData(groupedData)
            setLoading(false)
          })
      }, [lpTransactionsEndpoint, apiKey])
The result will be a grouped array:
The reason why we want to group the same transactions into an array first is that we’ll want all the items from the same transaction to render into a single row for our display. Of course, you can achieve this in any way you want.
Expanding the first array, you can see that each object contains the same tx_hash.
Nice! Now you’ll be able to map over this object to render each of the rows of data.
6

Rendering the right data

You’ll finally be able to render the data onto the page. Let’s begin with the Action field (swap, add_liquidity, etc).
Replace the “Swap” placeholder here:
return ( ... <div className='txnContainer'> <div className='action'> <div className='actionText'>Swap</div> ...
with the following:
<div className='actionText'>{item[0].event_name}</div>
Save, and you’ll be able to see this:
Not too bad! Now you know what each transaction signifies. The formatting is not ideal, so let’s define a simple function to clean up the action names:

const cleanAction = (actionName) => {
    switch (actionName) {
        case 'swap':
            return 'Swap'
        case 'add_liquidity': 
            return 'Add'
        case 'remove_liquidity':
            return 'Remove'
        case 'remove_liquidity_one_coin':
            return 'Remove One'
        case 'remove_liquidity_imbalance':
            return 'Remove Imbalance'
        default: 
            return actionName
    }
}
And calling it in our JSX:
<div className='actionText'>{cleanAction(item[0].event_name)}</div>
We see the following:
Much better. Now, replacing the rest of the placeholder values…
return( ... <div className='sectionContainer'> {data.map(item => { return ( <div className='rowTxn' key={item}> <div className='txnContainer'> <div className='action'> <div className='actionText'>{cleanAction(item[0].event_name)}</div> <div className='date'>{new Date(item[0].block_signed_at).toLocaleString('en-US', { day: 'numeric', month: 'short', year: "2-digit" })}</div> <div className='date'>{new Date(item[0].block_signed_at).toLocaleString('en-US', {timeStyle: 'short' })}</div> </div> <div className='tokenBundle'> <div><img className='tokenLogoTransactions' src='<https://res.cloudinary.com/dl4murstw/image/upload/v1677729872/greybox_zkioqf.png>' alt='tokenlogo'/></div> <div className='tokeninfo'> <p className='txnText'>{-(getTokenOut(item).value/(10**getTokenOut(item).contract_decimals)).toFixed(2)}</p> <p className='txnText'>{getTokenOut(item).contract_ticker_symbol}</p> </div> </div> <div className='arrow'><img src='<https://res.cloudinary.com/dl4murstw/image/upload/v1668500894/next_e7qvca.png>' alt='arrow' height="32px"/></div> <div className='tokenBundle'> <div><img className='tokenLogoTransactions' src='<https://res.cloudinary.com/dl4murstw/image/upload/v1677729872/greybox_zkioqf.png>' alt='tokenlogo'/></div> <div className='tokeninfo'> <p className='txnText'>+{(-getTokenIn(item).value/(10**getTokenIn(item).contract_decimals)).toFixed(2)}</p> <p className='txnText'>{getTokenIn(item).contract_ticker_symbol}</p> </div> </div> <div className='rightTxn'> <div className='gasEth'>{(Number(item[0].fees_paid)/(10**item[0].gas_token_decimals)).toFixed(4)} <img alt="" src="<https://res.cloudinary.com/dl4murstw/image/upload/v1668511869/gas-station_ydpfe5.png>" height="12" /> </div> <div className='gasQuote'>ETH</div> <div className='gasQuote'>(${item[0].gas_quote.toFixed(2)})</div> </div> </div> </div> ) })} </div> </> )
Save and head over to your browser, you’ll be able to see this:

Congratulations. You have managed to build a very compact component that shows you important data on your Curve transactions. All with 1 API call!

In our case, you will realize that what is missing is the token logos. At the point of writing (28th March 2023), the response for Get transactions by wallet address does not contain a token logo (it might be added in the future). The current workaround is to make use of the Trust wallet repository to render the logos. Apart from that, our component is done.

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 part3-defi-protocols-txns-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 on reaching this part of the Building Wallets series. We’ve managed to accomplish quite a bit. In the previous part, we managed to get all the LP token holdings of your users. In this part, we have managed to get all their transactions on Curve.

In the final section, we’ll look at how you can get important data on all Curve’s liquidity pools to display to your users. Stay tuned.

Read more