Written by
Owen Venter
Published on
February 2, 2024
Copy link

How to build a Solana Portfolio Viewer with Next.js and Helius's DAS API

Objectives

In this tutorial, we'll build a front-end application to display the contents of a Solana wallet using the DAS API from Helius. We'll showcase various token types and their detailed information, including metadata, collection info, and fungible token prices.

This tutorial will guide you through the process of building something similar to Galleria, by breaking down the necessary steps involved.

Prerequisites

  • Helius API key.
  • Familiarity with React.
  • Experience with Next.js.

Overview of Technologies Used

  • Next.js: A React framework for building server-side rendered applications.
  • DAS API from Helius: A comprehensive API for accessing Solana token data.
  • Vercel: A platform for hosting Next.js applications.
  • Tailwind CSS: A utility-first CSS framework for styling.

Setting up the Project

The DAS API

The DAS API is an open-source specification and system offered by Helius. The API provides a simple interface for interacting with digital assets on the Solana blockchain. It supports various token types, including fungible tokens, regular NFTs, and compressed NFTs, and offers features like indexing off-chain metadata.

API Key Setup

To obtain a Helius API key, visit Helius Developer Portal. Here's how to get started:

  1. Create an account using a Solana wallet, Google account, or GitHub account.
  2. Once logged in, you'll be prompted to create an API key.
  3. Store this key securely, as we will be using this to make our API calls.

Building the Next.js Application

Step 1: Setting Up Your Next.js Project

Create a New Next.js App with Tailwind


npx create-next-app@latest helius-portfolio --typescript --eslint

Use the following configurations:

This will allow us to use Tailwind CSS for styling as well as use the new App router.


cd helius-portfolio

Step 2: Creating Components

Creating a folder for components:Create a components folder in the app directory to maintain an organized structure.

  1. SearchBar Component (components/SearchBar.js):
  2. This component handles user input for wallet addresses.
  3. It utilizes the useState hook for managing the address state and useRouter for navigation upon form submission.
  4. The form is styled using Tailwind CSS classes for a sleek look.
  5. TokenCard Component (components/TokenCard.js):
  6. Designed to display token information.
  7. Conditionally renders different layouts for fungible and non-fungible tokens.
  8. Utilizes the Token and Attribute types from ../types/Token.

SearchBar Component:

  • components/searchBar.ts

import { useRouter } from "next/navigation";
import React, { useState } from "react";

export default function SearchBar() {
  const router = useRouter();
  const [address, setAddress] = useState("");

  function handleSubmit(event: React.FormEvent) {
    event.preventDefault();
    router.push(`/${address}`);
  }

  return (
    
setAddress(e.target.value)} className="border-2 border-gray-300 rounded-md w-full text-center px-4 py-2 mb-4 flex-grow text-black" />
); }

TokenCard Component:

This component parses the token data from the API and shows relevant information on each token card. In the example below, we show some basic information such as the image, symbol, amount, and value of each fungible token. For Non-fungible Tokens, we show the image, name, description, and attributes. You can extract any of the information that is provided by the API and display this on the card. An example would be to show compression-specific information for cNFTs or inscription info for inscribed assets. To explore more about the specific information you can work with you can check out the DAS docs: https://docs.helius.dev/compression-and-das-api/digital-asset-standard-das-api


"use client";

import React from "react";
import { Token, Attribute } from "../types/Token";

interface TokenCardProps {
  token: Token;
  tokenType: string;
}

const TokenCard = ({ token, tokenType }: TokenCardProps) => {
  return (
    
{/* Other token information */} {tokenType === "fungible" ? (
{token.content.metadata.name}

{token.content.metadata.symbol}

{/*

{token.content.metadata.description}

*/}

Amount:{token.token_info.balance}

{token.token_info.price_info?.total_price ? (

Value: ${token.token_info.price_info.total_price}

) : null}
) : (
{token.content.metadata.name}

{token.content.metadata.name}

{token.content.metadata.description}

    {token.content.metadata?.attributes?.map( (attribute: Attribute, index: number) => (
  • {attribute.trait_type}: {attribute.value}
  • ) )}
)}
); } export default TokenCard;

Step 3: Fetching Data from DAS

We'll use the searchAssets method from the DAS API to fetch token data. This method is flexible, allowing us to specify criteria like wallet addresses and token types.

Creating a lib folder:

  1. Make a lib folder in the app directory for the API call.
  2. Create a searchAssets.ts file inside it.

API Call Function (lib/searchAssets.ts):

  • This function fetches tokens based on the wallet address.
  • It handles potential errors and logs responses for debugging

interface Tokens {
  items: any[];
}

const fetchTokens = async (walletAddress: string): Promise => {
  const url = `https://mainnet.helius-rpc.com/?api-key=`;
  console.log(
    `Starting search for tokens for wallet address: ${walletAddress}`
  );
  try {
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        jsonrpc: "2.0",
        id: "my-id",
        method: "searchAssets",
        params: {
          ownerAddress: walletAddress,
          tokenType: "all",
          displayOptions: {
            showCollectionMetadata: true,
          },
        },
      }),
    });
    const data = await response.json();
    console.log(
      `Data returned for wallet address ${walletAddress}:`,
      data.result
    );
    return { items: data.result };
  } catch (error) {
    console.error("Error fetching tokens:", error);
    return { items: [] };
  }
};

export default fetchTokens;
  • Set up the token type
    We will need to set up a type for the response from the DAS API. This is good practice when working with Typescript. You can create a folder called types and create a file called token.ts

export interface ApiResponse {
  total: number;
  limit: number;
  cursor?: string;
  items: Token[];
}

export interface Token {
  interface: string;
  id: string;
  content: Content;
  authorities: Authority[];
  compression: Compression;
  grouping: Grouping[]; // or any[]
  royalty: Royalty;
  creators: Creator[]; // or any[]
  ownership: Ownership;
  supply: Supply | null | number;
  mutable: boolean;
  burnt: boolean;
  token_info: TokenInfo;
  mint_extensions: MintExtensions;
  inscription: Inscription;
  spl20?: Spl20;
}

export interface Content {
  $schema: string;
  json_uri: string;
  files: any[];
  metadata: Record;
  links: Record;
}

export interface Authority {
  address: string;
  scopes: string[];
}

export interface Compression {
  eligible: boolean;
  compressed: boolean;
  data_hash: string;
  creator_hash: string;
  asset_hash: string;
  tree: string;
  seq: number;
  leaf_id: number;
}

export interface Grouping {
  group_key: string;
  group_value: string;
  collection_metadata: CollectionMetadata;
}

export interface Royalty {
  royalty_model: string;
  target: string | null;
  percent: number;
  basis_points: number;
  primary_sale_happened: boolean;
  locked: boolean;
}

export interface Creator {
  address: string;
  share: number;
  verified: boolean;
}

export interface Ownership {
  frozen: boolean;
  delegated: boolean;
  delegate: null | string;
  ownership_model: string;
  owner: string;
}

export interface Supply {
  print_max_supply: number;
  print_current_supply: number;
  edition_nonce: number;
}

export interface TokenInfo {
  symbol: string;
  balance: number;
  supply: number;
  decimals: number;
  token_program: string;
  associated_token_address: string;
  price_info: PriceInfo;
}

export interface Inscription {
  order: number;
  size: number;
  contentType: string;
  encoding: string;
  validationHash: string;
  inscriptionDataAccount: string;
}

export interface Spl20 {
  p: string;
  op: string;
  tick: string;
  amt: string;
}

export interface File {
  uri: string;
  cdn_uri: string;
  mime: string;
}

export interface Metadata {
  attributes: Attribute[];
  description: string;
  name: string;
  symbol: string;
}

export interface Attribute {
  value: string;
  trait_type: string;
}

export interface CollectionMetadata {
  name: string;
  symbol: string;
  image: string;
  description: string;
  external_url: string;
}

export interface PriceInfo {
  price_per_token: number;
  total_price: number;
  currency: string;
}

export interface MintExtensions {}

Step 4: Displaying Token Info

We'll categorize the API response into fungible and non-fungible tokens. Using the Token Card component, these tokens will be displayed. A toggle button allows users to switch between the two token types.

Integrating Components on the Search Page

  • Search Page (pages/index.ts)
  • The landing page features a Search Bar where users can enter wallet addresses.
  • It's styled with Tailwind CSS for a clean and responsive design.

"use client";

import React from "react";
import SearchBar from "./components/searchBar";

export default function Home() {
  return (
    

Solana Portfolio Viewer

{"Built with Helius's DAS API"}

); }

Setting Up the Token Page

We'll utilize dynamic routes in Next.js for individual wallet addresses. This enhances user experience by allowing easy navigation and URL sharing.

  • Set up a directory called [wallet], the brackets enable dynamic routing. Once done, create a file called page.tsx that will contain all the business logic for all dynamically loaded pages
  • Token Page Setup (pages/[wallet]/page.tsx):
  • This page displays tokens associated with the wallet address in the URL.
  • It makes an API call to fetch token data and displays it using the Token Card component.

"use client";

import React, { useState, useEffect } from "react";
import fetchTokens from "../lib/searchAssets";
import TokenCard from "../components/tokenCard";
import { Token, Attribute } from "../types/Token";

interface Tokens {
  items: Token[];
}

interface PageProps {
  params: {
    wallet: string;
  };
}

export default function Page({ params }: PageProps) {
  const [tokens, setTokens] = useState(null);
  const [tokenType, setTokenType] = useState("fungible");

  useEffect(() => {
    fetchTokens(params.wallet).then(setTokens).catch(console.error);
  }, [params.wallet]);

  console.log("tokens", tokens);

  const fungibleTokens = tokens
    ? tokens.items.filter(
        (token) =>
          token.interface === "FungibleToken" ||
          token.interface === "FungibleAsset"
      )
    : [];

  const nonFungibleTokens = tokens
    ? tokens.items.filter(
        (token) =>
          token.interface !== "FungibleToken" &&
          token.interface !== "FungibleAsset"
      )
    : [];

  const displayedTokens =
    tokenType === "fungible" ? fungibleTokens : nonFungibleTokens;

  return (
    

Portfolio Viewer

{params.wallet}

{displayedTokens.length > 0 ? (
{displayedTokens.map((token) => ( ))}
) : (

Loading...

)}
); }

After all of this has been set up your project directory should look like this:

Next.js directory

Step 5: Styling the Application

Tailwind CSS will be our styling framework. Its utility-first approach enables us to quickly style components while ensuring a consistent and responsive design. You can use Tailwind to customize your application in any way you wish.

Step 6: Testing the Application

Testing Locally:

  • Run your Next.js app:

npm run dev
  • Test by entering different wallet addresses and ensure the correct data is displayed.

Deployment

We will deploy our application using Vercel. Vercel offers free hosting and provides valuable analytics and insights, making it an ideal choice for hosting Next.js applications. You can set this up at https://vercel.com/.

Conclusion

Congratulations on finishing this tutorial! We hope that you found it informative. The DAS API is a very powerful and speedy tool that enables you to retrieve token data from the chain. This portfolio viewer is an excellent example of what the API is capable of. We hope that you can take the basic site that was created and turn it into something great. If you have any questions, please do not hesitate to join our Discord and ask us directly!

You can find the complete code here: https://github.com/helius-labs/galleria

Further Learning Resources

To deepen your understanding of Next.js, Solana blockchain, and API integration, explore these resources:

This guide provides a solid foundation for building blockchain-based applications with Next.js and interacting with the Solana blockchain using the DAS API.