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.
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:
{
"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
}
See all 44 lines
Use the chainAggregation
field to display balances per chain.
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.
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:
"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 >
);
}
See all 127 lines
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.