This guide explains how to build a balance widget that displays an account’s multi-chain asset balances and their USD value, using the Universal Accounts SDK as its data source.
To see a live example of this, head to the Universal Accounts demo.
Balance Widget
You’ll learn:
  • What data to fetch from a Universal Account.
  • How UAs’ asset structure is organized.
  • How to render a portfolio overview in a React component.

Get started with Universal Accounts

Check out our Quickstart guide to learn how to build an app leveraging Universal Accounts.

Fetching Primary Assets from a Universal Account

Once a Universal Account is initialized, you can retrieve the user’s cross-chain Primary Assets balances with:
const assets = await universalAccount.getAssets();
This returns a list of tokens and their aggregated balances across all supported chains.

Primary Assets Response Structure

Each entry in the response above represents a single token type (e.g. ETH, USDT, USDC), including its price, total balance, and per-chain breakdown.
This unified balance only includes Primary Assets, across any chain the UA holds them on. The SDK will then automatically select the most efficient payment source and route assets to execute the transaction.
You can find an example of the JSON structure below:
JSON
{
  "assets": [
    {
      "tokenType": "eth",                         // Token symbol
      "price": 1583.89,                           // Current price in USD
      "amount": 0.002555304503076061,             // Total across all chains
      "amountInUSD": 4.047321249377142,           // Total USD value

      "chainAggregation": [
        {
          "token": {
            "assetId": "eth",
            "type": "eth",
            "chainId": 59144,                     
            "address": "0x000000...0000",
            "decimals": 18,
            "realDecimals": 18,
            "isMultiChain": true,
            "isMultiChainDefault": false
          },
          "amount": 0.002555304503076061,         
          "amountInUSD": 4.047321249377142,
          "rawAmount": 2555304503076061
        },
        {
          "token": {
            "assetId": "eth",
            "type": "eth",
            "chainId": 1,                        
            "address": "0x000000...0000",
            "decimals": 18,
            "realDecimals": 18,
            "isMultiChain": true,
            "isMultiChainDefault": false
          },
          "amount": 0,
          "amountInUSD": 0,
          "rawAmount": 0
        }
      ]
    }
  ],
  "totalAmountInUSD": 4.047321249377142
}
Use the chainAggregation field to display balances per chain.

Complete Example: Universal Account Balance Widget

Below is a ready-to-use React component that displays a user’s Universal Account portfolio. It aggregates balances and USD values for each supported asset, sorts them by value, and renders a modern dashboard UI.
Get your Universal Accounts project ID from the Particle Dashboard.
This widget makes it simple to show users an at-a-glance overview of their holdings—unified across all chains and token types—with just a single API call to universalAccount.getAssets(). You can use or customize this component as a starting point for a wallet dashboard or portfolio summary.
This example uses:
  • ShadCN UI components (Card, CardContent). Ensure they are set up in your project.
  • Lucide Icons—specifically the Wallet icon.
  • Token icons must be available at /public/tokens/[symbol].png (e.g., /tokens/eth.png, /tokens/usdc.png). You can customize or replace these as needed.
Below you will find the complete code for the BalanceWidget component:
BalanceWidget.tsx
"use client";

import { Card, CardContent } from "@/components/ui/card";
import { Wallet } from "lucide-react";

type ChainInfo = {
  token: {
    assetId?: string;
    chainId: number;
    address: string;
  };
  amount: number;
  amountInUSD: number;
};

type Asset = {
  tokenType: string;
  price: number;
  amount: number;
  amountInUSD: number;
  chainAggregation: ChainInfo[];
};

type WalletWidgetProps = {
  assets: Asset[];
  totalAmountInUSD: number;
};

/**
 * Formats a number as currency with appropriate precision
 */
const formatCurrency = (value: number, decimals = 2): string => {
  return value.toLocaleString(undefined, {
    minimumFractionDigits: decimals,
    maximumFractionDigits: decimals,
  });
};

/**
 * Formats a crypto amount with appropriate precision
 */
const formatCryptoAmount = (value: number): string => {
  return value.toLocaleString(undefined, {
    minimumFractionDigits: 0,
    maximumFractionDigits: 4,
  });
};

/**
 * WalletWidget component displays a user's crypto asset portfolio
 */
export default function WalletWidget({
  assets,
  totalAmountInUSD,
}: WalletWidgetProps) {
  // Filter valid assets with positive USD values
  const validAssets = assets
    .filter(
      (asset) =>
        asset &&
        typeof asset.tokenType === "string" &&
        typeof asset.amount === "number" &&
        typeof asset.amountInUSD === "number" &&
        !isNaN(asset.amount) &&
        !isNaN(asset.amountInUSD) &&
        asset.amountInUSD > 0
    )
    // Sort by USD value (highest first)
    .sort((a, b) => b.amountInUSD - a.amountInUSD);

  return (
    <div className="w-full max-w-[800px] mx-auto">
      <Card className="bg-gradient-to-br from-purple-700 to-purple-900 text-white border-none rounded-xl shadow-xl overflow-hidden relative">
        <CardContent className="p-8">
          <div className="absolute top-4 right-4 text-white/70 text-sm font-medium">
            Universal Account Overview
          </div>

          {/* Asset list in top right corner */}
          <div className="absolute top-10 right-4 w-[220px] max-h-[240px] overflow-y-auto bg-purple-800/50 backdrop-blur-sm rounded-xl p-4 space-y-2 [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]">
            {validAssets.map((asset) => (
              <div
                key={asset.tokenType}
                className="flex items-center justify-between p-2"
              >
                <div className="flex items-center gap-3">
                  <div className="h-10 w-10 rounded-full bg-purple-700/50 flex items-center justify-center overflow-hidden">
                    <img
                      src={`/tokens/${asset.tokenType.toLowerCase()}.png`}
                      alt={`${asset.tokenType} logo`}
                      className="w-8 h-8 object-contain"
                    />
                  </div>
                  <div>
                    <div className="font-medium text-xs uppercase">
                      {asset.tokenType}
                    </div>
                    <div className="text-xs text-purple-200">
                      {formatCryptoAmount(asset.amount)}
                    </div>
                  </div>
                </div>
                <div className="text-right">
                  <div className="font-medium text-sm">
                    ${formatCurrency(asset.amountInUSD)}
                  </div>
                </div>
              </div>
            ))}
          </div>

          {/* Main content */}
          <div className="max-w-xs">
            <h2 className="text-3xl font-bold mb-1">Your Universal Account</h2>
            <p className="text-purple-200 mb-6">
              Track your crypto assets across multiple chains in one place.
            </p>
            <div className="flex items-center text-3xl font-bold mt-8">
              <Wallet className="mr-2 h-6 w-6" />$
              {formatCurrency(totalAmountInUSD)}
            </div>
          </div>
        </CardContent>
      </Card>
    </div>
  );
}
You can then pass the data from the Primary Assets response to the WalletWidget component:
const primaryAssets = await universalAccount.getPrimaryAssets();

<WalletWidget
  assets={primaryAssets.assets}
  totalAmountInUSD={primaryAssets.totalAmountInUSD}
/>
This component provides a basic summary of the user’s assets. To show more details—including a full chain-by-chain breakdown—you can customize it as needed.For a full breakdown example, refer to the Universal Accounts with Ethers demo, or see it live here.

What’s Next?

Explore the SDK reference for more advanced capabilities.