Skip to main content
IntermediateEst8 minsdk-core15.4.1Project build checked

Fetch an account's token balances

Read the tokens an account holds, EGLD's ESDT siblings. Three reads, all on INetworkProvider:

  • getFungibleTokensOfAccount(address, pagination?) returns every fungible ESDT.
  • getNonFungibleTokensOfAccount(address, pagination?) returns every NFT / SFT / meta-ESDT.
  • getTokenOfAccount(address, token) returns one specific token's balance.

All read-only. This recipe reads a liquidity-pool contract (which reliably holds several fungibles) and a known NFT-holding address, so every call returns real, non-empty data.

Prerequisites

  • Node.js >= 20.19.0.
  • Network access. No wallet, no PEM, no EGLD.

Install

mkdir fetch-account-token-balances
cd fetch-account-token-balances
# Create the project files shown on this page.
npm install
npm run build
npm start
Complete project files omitted from the main walkthrough
package.json
{
"name": "cookbook-recipe-fetch-account-token-balances",
"version": "1.0.0",
"private": true,
"description": "Cookbook recipe — read the tokens an account holds: all fungible ESDTs, all NFTs/SFTs, and the balance of one specific token, via a network provider.",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"typecheck": "tsc --noEmit --strict"
},
"dependencies": {
"@multiversx/sdk-core": "15.4.1",
"axios": "1.18.1",
"bignumber.js": "9.3.1",
"protobufjs": "7.6.5"
},
"devDependencies": {
"@types/node": "20.19.43",
"typescript": "5.9.3"
},
"engines": {
"node": ">=20.19.0"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"allowJs": false,
"skipLibCheck": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"declaration": false,
"sourceMap": false,
"outDir": "dist",
"types": ["node"]
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

Reading the balances

src/tokenBalances.ts
// src/tokenBalances.ts — the subject of this recipe: reading the tokens an
// account holds. Three reads, all on INetworkProvider:
//
// - getFungibleTokensOfAccount(address, pagination?) → every fungible ESDT
// - getNonFungibleTokensOfAccount(address, pagination?) → every NFT / SFT / meta-ESDT
// - getTokenOfAccount(address, token) → one token's balance
//
// getTokenOfAccount has a sharp edge for NFTs — see fetchNftBalance below.

import { Token, TokenComputer } from '@multiversx/sdk-core';
import type {
INetworkProvider,
TokenAmountOnNetwork,
Address,
IPagination,
} from '@multiversx/sdk-core';

/** All fungible ESDTs the account holds. Pagination is honored by the Api
* provider; the Proxy provider ignores it and returns everything. */
export async function fetchFungibleTokens(
provider: INetworkProvider,
address: Address,
pagination?: IPagination,
): Promise<TokenAmountOnNetwork[]> {
return pagination === undefined
? provider.getFungibleTokensOfAccount(address)
: provider.getFungibleTokensOfAccount(address, pagination);
}

/** All non-fungible tokens (NFT / SFT / meta-ESDT) the account holds. */
export async function fetchNonFungibleTokens(
provider: INetworkProvider,
address: Address,
pagination?: IPagination,
): Promise<TokenAmountOnNetwork[]> {
return pagination === undefined
? provider.getNonFungibleTokensOfAccount(address)
: provider.getNonFungibleTokensOfAccount(address, pagination);
}

/** One fungible token's balance for the account. */
export async function fetchFungibleBalance(
provider: INetworkProvider,
address: Address,
identifier: string,
): Promise<TokenAmountOnNetwork> {
return provider.getTokenOfAccount(address, new Token({ identifier }));
}

/**
* One NFT's balance. getTokenOfAccount for an NFT wants the BASE collection
* identifier plus the nonce — it appends the nonce hex itself. Passing the
* already-extended identifier (e.g. "XPASS-423274-04") double-appends the nonce
* and the API rejects it as an invalid NFT identifier. Since
* getNonFungibleTokensOfAccount returns the EXTENDED identifier, strip it back
* to the base with TokenComputer first.
*/
export async function fetchNftBalance(
provider: INetworkProvider,
address: Address,
extendedIdentifier: string,
nonce: bigint,
): Promise<TokenAmountOnNetwork> {
const baseIdentifier = new TokenComputer().extractIdentifierFromExtendedIdentifier(extendedIdentifier);
return provider.getTokenOfAccount(address, new Token({ identifier: baseIdentifier, nonce }));
}

Wiring it up

src/index.ts
// src/index.ts — read the fungible tokens of a liquidity-pool contract (which
// reliably holds several) and the NFTs of a known NFT-holding address. No
// wallet, no gas — these are public balances.
//
// Usage:
// npm run build && npm start

import { ApiNetworkProvider, Address } from '@multiversx/sdk-core';
import {
fetchFungibleTokens,
fetchNonFungibleTokens,
fetchFungibleBalance,
fetchNftBalance,
} from './tokenBalances';

const MAINNET_API = 'https://api.multiversx.com';
// An xExchange WEGLD/USDC pair contract — reliably holds fungible ESDTs.
const FUNGIBLE_HOLDER = 'erd1qqqqqqqqqqqqqpgqeel2kumf0r8ffyhth7pqdujjat9nx0862jpsg2pqaq';
// A known address that holds an NFT.
const NFT_HOLDER = 'erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th';

async function main(): Promise<void> {
const provider = new ApiNetworkProvider(MAINNET_API, { clientName: 'mvx-cookbook' });

// Fungible tokens (paginated).
const fungibleHolder = Address.newFromBech32(FUNGIBLE_HOLDER);
const fungibles = await fetchFungibleTokens(provider, fungibleHolder, { from: 0, size: 5 });
console.log(`Fungible tokens of ${FUNGIBLE_HOLDER} (up to 5):`);
for (const t of fungibles) {
console.log(` ${t.token.identifier}: ${t.amount}`);
}

// One specific fungible balance.
const wegld = await fetchFungibleBalance(provider, fungibleHolder, 'WEGLD-bd4d79');
console.log(`getTokenOfAccount('WEGLD-bd4d79'): ${wegld.amount}`);

// Non-fungible tokens.
const nftHolder = Address.newFromBech32(NFT_HOLDER);
const nfts = await fetchNonFungibleTokens(provider, nftHolder, { from: 0, size: 5 });
console.log(`\nNon-fungible tokens of ${NFT_HOLDER} (up to 5):`);
for (const t of nfts) {
console.log(` ${t.token.identifier} (nonce ${t.token.nonce}): amount ${t.amount}`);
}

// One specific NFT balance — showing the base-identifier + nonce requirement.
const [firstNft] = nfts;
if (firstNft !== undefined) {
const back = await fetchNftBalance(provider, nftHolder, firstNft.token.identifier, firstNft.token.nonce);
console.log(`getTokenOfAccount(base id + nonce) for ${firstNft.token.identifier}: amount ${back.amount}`);
} else {
console.log('(no NFTs on this address right now — pass another address to see the NFT path)');
}
}

main().catch((err: unknown) => {
console.error(err);
process.exitCode = 1;
});

Run it

npm start

Expected output (balances are live and will differ):

Fungible tokens of erd1qqqqqqqqqqqqqpgqeel2kumf0r8ffyhth7pqdujjat9nx0862jpsg2pqaq (up to 5):
USDC-c76f1f: 733827376705
EGLDUSDC-594e5e: 1000
WEGLD-bd4d79: 223980465825062238009362
getTokenOfAccount('WEGLD-bd4d79'): 223980465825062238009362

Non-fungible tokens of erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th (up to 5):
XPASS-423274-04 (nonce 4): amount 1
getTokenOfAccount(base id + nonce) for XPASS-423274-04: amount 1

How it works

Fungible and non-fungible are separate calls. A wallet's USDC and its NFTs come from two different endpoints. Both return TokenAmountOnNetwork[], each element a token (identifier + nonce) plus an amount (bigint, in the token's own smallest denomination). The single-token getTokenOfAccount('WEGLD-bd4d79') matches the amount from the list, one balance, two ways to reach it.

Amounts are raw bigints. 733827376705 for USDC is 733,827.376705 after USDC's 6 decimals; 223980465825062238009362 for WEGLD is ~223,980 after 18 decimals. Divide by 10 ** decimals (from Fetch token metadata) to display, never hard-code 18.

Pitfalls

Pitfall 1: getTokenOfAccount for an NFT wants the BASE identifier + nonce

getTokenOfAccount appends the nonce hex to the identifier itself. getNonFungibleTokensOfAccount returns the already-EXTENDED identifier (XPASS-423274-04), so passing that back in double-appends the nonce (...-04-04) and the API rejects it with "Invalid NFT identifier". Strip it to the base first with TokenComputer.extractIdentifierFromExtendedIdentifier(), then pass { identifier: base, nonce }, reproduced and fixed against the live API.

Pitfall 2: pagination is Api-only

{ from, size } is honored by ApiNetworkProvider and silently ignored by ProxyNetworkProvider, whose getFungibleTokensOfAccount(address) signature has no pagination parameter at all. On the proxy you get everything back regardless, page in application code if the account holds a lot.

Pitfall 3: an empty list is normal, not an error

An account with no ESDTs returns [], and getTokenOfAccount for a token the account does not hold throws "Token for given account not found" rather than returning zero. Treat "not held" as a caught error or an empty array, not an exception you forgot to handle.

See also