Git Product home page Git Product logo

token-display's Introduction

Token Balance and Metadata Helius API Showcase


In this project walkthrough, we are going to go through how you can create a dAPP UI that allows users to track the inventory of any wallet using Helius's API.

Sneak Peek:

[preview]



Getting Started:

In order to run locally:

  • Node.js needs to be installed on your OS
  • Clone the token-balance-metadata repo
  • Start the project
    1. Run npm install to install all our project dependencies
    2. Run npm run dev to run our application

npm install downloads the packages found in package.json

npm run dev runs the website. The website can now be accessed from localhost:3000 or 0.0.0.0

In order for queries to be made, a Helius API key is necessary - in order to obtain one, visit

https://www.helius.xyz

Now, create a file called .env.local on the outermost directory of the cloned repo, adding the following text into the file.

HELIUS_KEY = <your Helius API key>

The site is now fully functional on your local machine.



balance.js

As we are making queries to the Helius API, a way to make requests is necessary, this can be through the native fetch, or in this case Axios.

const axios = require('axios');
async function getBalance(req) {
  const url = `https://api.helius.xyz/v0/addresses/${req}/balances?api-key=${process.env.HELIUS_KEY}`
  let { data } = await axios.get(url);
  return data.tokens
}

An asynchronous function is used in order to make requests, Helius's API is especially great in this instance, GET requests being especially simple and fast, allowing us a simple query function.

The req variable is the wallet that the user has input from the front-end, it will be added later, for testing purposes an assigned variable can be set with a static wallet already chosen.


In the main function, the list of tokens fetched from Helius will be passed into another function getMetadata where the metadata for the tokens will be returned, this will allow us to display them on the website.

let balance = await getBalance(wallet)
let balanceMetadata = await getMetadata(balance);

Again, an asynchronous function as used in order to fetch data from the Helius API. The endpoint this time however is /tokens/metadata as although we have just gotten the list of tokens from the previous endpoint, the data is concise and does not include details, example below.

{
    "tokenAccount":"GiLNww7rPrw2P6SsCccBTN26BwBijee5xdXq5uhRHaC",
    "mint":"9GnYb1ukBUKHobqpmNdzBE7VkYn7wWqianpKaYFPChk",
    "amount": 22000000000,
    "decimals": 9
}
async function getMetadata(tokens) {

  let mintAccounts = [], queriedTokens = tokens.map((e)=>e.mint);
  let url = `https://api.helius.xyz/v0/tokens/metadata?api-key=${process.env.HELIUS_KEY}`
  
  if (tokens.length > 100) {
    for (let i = 0; i < Math.ceil(tokens.length / 100); i++) {
      let batch = queriedTokens.slice(0, 100)
      queriedTokens = queriedTokens.slice(100, queriedTokens.length)
      let query = await axios.post(url, { mintAccounts: batch })
      mintAccounts = mintAccounts.concat(query.data)
    }
  }
  else {
    let query = await axios.post(url, { mintAccounts: tokens })
    mintAccounts = query.data;
  }

  return mintAccounts
}

The map function will be used in order to extract the mint addresses from each object in the array of objects that is the wallet's list of tokens, the output is the variable queriedTokens an array of the each token's mint addresses, this will be passed into Helius's API in order to obtain the tokens' metadata.

Another example of pagination can be found on Helius.


res.status(200).json(balanceMetadata)

Our internal API will now return the array of tokens that the wallet holds along with their metadata.



AddressInput.jsx

The AddressInput component is essential in connecting our front and backend, connecting the internal API's retrieved data with the TokenDisplay component.

We will be making requests to our internal API, therefore use of Axios will be continued.

import axios from 'axios';
import React, { useState } from "react";
import TokenDisplay from "./TokenDisplay";

The state variable address will be used to store our user's inputted wallet address. The variable balance will be used to store our user's retrieved token data from out internal API. The variable submit will be used in order for us to provide a better UX, by adding an indicator that the data is loading. The variable enter also accompanies the other loading related variables, used to determine when the user has entered the query, but before the query response has been received.

const [address, setAddress] = useState("");
const [balance, setBalance] = useState();
const [submit, setSubmit] = useState(false)
const [enter, setEnter] = useState(false)

A simple form will be used in order for the user to input a wallet address. Classes and styling will not be including in this walkthrough as they are not relevant (they are still included in the actual code).

<form onSubmit={handleSubmit}>
  <input
    type="text"
    value={address}
    onChange={(e) => setAddress(e.target.value)}
  />
  <button type="submit">
    <Image alt="mag" src="/mag.svg" width="24" height="24"></Image>
  </button>
</form>

Once entered, the onChange function calls our state variable's function, assigning the user's input as the variable address added earlier.

async function handleSubmit(event) {
  event.preventDefault();

  setEnter(true)

  const response = await axios.post("/api/balance", { address: address });

  setSubmit(true)

  try {
    setBalance(response.data)
    console.log(response.data)
  }
  catch (err) {
    console.log(err)
  }
}

When the user clicks the submit button, the variable handleSubmit is called, where the request to our internal API is made. This makes a request to our earlier designed internal API, with the payload containing an object with the property address, passing in the state variable we assigned earlier from the user's input.

The setSubmit state variable will be used in order to determine when the data is retrieved, so that a loading indicator can be used while the user waits.

<React.Fragment>{(
        enter ? (
          <React.Fragment>{(
            submit ? (
              <TokenDisplay tokens={balance}></TokenDisplay>
            ) : (
              <Image alt="load" src="/loading.svg"></Image>
              ))}</React.Fragment>
        ) : (
          <div></div>
        )
      )}
</React.Fragment>

Once the user's data has been received, it is passed into the TokenDisplay function.



TokenDisplay.jsx

Where the magic happens, the user's tokens and data that have been fetched from Helius's API are displayed.

A quick edit is necessary, being the fungible token's most important value, the amount is currently not being passed back to the frontend, in order to fix this at the end of the getMetadata function in balance.js, add

mintAccounts.map((e,i)=>e.amount = tokens[i].amount);

Starting with the fungible tokens,

for (let i = 0; i < tokens.length; i++) {
        if (tokens[i].onChainData) {
            if (tokens[i].onChainData.tokenStandard == "Fungible" || tokens[i].onChainData.tokenStandard "FungibleAsset") {
                FTs.push(
                    <div key={tokens[i].mint}>
                        <a target="_blank" rel="noreferrer" href={"https://xray.helius.xyz/token/" + tokens[i].mint}>
                            <div>
                                <div>
                                    <React.Fragment>{(tokens[i].offChainData ?
                                        (<img src={tokens[i].offChainData.image}></img>) : (<PlaceholderToken></PlaceholderToken>>
                                        ))}</React.Fragment>
                                    <div>
                                        {tokens[i].onChainData.data.name}
                                    </div>
                                </div>

                                <div>{tokens[i].amount}</div>

                            </div>
                        </a>
                    </div>
                )

                continue
            }
        }
}

First, we iterate through the list of the users tokens we obtained from the Helius API, the first if statement is not particularly important, simply to avoid errors from the second if statement. While some older tokens may not contain this specific piece of metadata to determine whether or not it is fungible, to be safe, we will be using tokens specifically with the tokenStandard of "Fungible" or "FungibleAsset".

Now that we've determined whether or not the token is fungible, we can start appending to an array of DIVs that we will output later. The mint address, name, and amount of tokens the wallet holds is virtually always present, a placeholder image being added incase an image is not attached on-chain.

Moving onto NFTs

if (tokens[i].offChainData) {
  NFTs.push(
      <div key={tokens[i].offChainData.mint}>
          <div>
              <a target="_blank" rel="noreferrer" href={"https://xray.helius.xyz/token/" + tokens[i].mint}>
                  <div>
                      <img src={tokens[i].offChainData.image}></img>
                  </div>
                  <div>
                      <div>
                          <div>
                              <div>{tokens[i].offChainData.name}</div>
                              <Image alt="Verified" src="/verified.svg" width="16" height="16"></Image>
                          </div>
                          <div>{tokens[i].onChainData.data.symbol ? (tokens[i].onChainData.data.symbol) : tokens[i].offChainData.symbol}</div>
                      </div>

                      <div>
                          <a target="_blank" rel="noreferrer" href={"https://www.magiceden.io/item-details/" + tokens[i].mint}>
                              <Image alt="ME" src="/ME.svg" width="24" height="24"></Image>
                          </a>
                      </div>
                  </div>
              </a>
          </div>

      </div>
  )
}

To reiterate, the initial if statement is not particularly important, their main purpose to screen for errors - by screening for these errors, a number of older NFTs may be missing though the vast majority should still be able to be displayed.

Like before, jsx is created using each object's (tokens) data, for some NFTs, the symbol of the collection may be available on-chain, therefore a quarternary operator is used in order to add them if possible.

Thats it! A simple display of all tokens for any given wallet using minimal code.

Next Steps

  • Occasional issues with API calls, error code 400 unsure why
  • Queries for wallets with a large number of tokens will fail as Vercel has a timeout on API calls over 10 seconds, therefore a cap of 100 tokens is put in place to bypass this.

token-display's People

Contributors

tidelaw avatar dependabot[bot] avatar

Stargazers

 avatar Kulture avatar nyk avatar Harsh Ghodkar avatar  avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.