SDK
balances
Read a wallet's on-chain balances. This is a plain read: no signer, no userOp, no relay. It works for any address (a Wallet you created or a bare 0x… address), including counterfactual wallets that haven't been deployed yet.
// `client` and `wallet` from earlier
const { native } = await client.balances({ wallet });
console.log(native); // bigint — native token balance in weiPass a bare address if that's all you have:
const { native } = await client.balances({
wallet: "0xabc…" as `0x${string}`,
});Target a specific chain when the client is configured with more than one:
import { createClient, BNB, ETHEREUM } from "@altananetwork/sdk";
const client = createClient({ chains: [BNB, ETHEREUM] });
// BNB Smart Chain (the default — first chain)
const bnb = await client.balances({ wallet });
// Ethereum
const ethereum = await client.balances({ wallet, chainId: 1 });ERC-20 tokens
Pass tokens to read ERC-20 balances in the same call. All per-token reads are batched into a single multicall:
const res = await client.balances({
wallet,
tokens: ["0x55d398326f99059ff775485246999027b3197955"], // USDT (BSC)
});
for (const t of res.tokens!) {
if (!t.ok) continue; // token whose reads reverted (bad address, non-ERC-20)
console.log(t.display, t.symbol); // "12.5 USDT" — ready to render
console.log(t.raw); // bigint — the on-chain amount, use for transfers
}BEP-677 scaled UI amounts
Tokens implementing BEP-677 (IScaledUIAmount, detected via ERC-165 id 0xa60bf13d) carry a display multiplier: wallets must show raw × uiMultiplier / 1e18 while raw balances and allowances stay unchanged on-chain. balances handles this automatically — display (and scaled.scaledRaw) carry the multiplier; raw never does.
const [t] = (await client.balances({ wallet, tokens: [scaledToken] })).tokens!;
if (t.ok && t.scaled) {
t.display; // "1500" — already scaled (raw 1000, ×1.5)
t.scaled.uiMultiplier; // 1500000000000000000n (1e18 fixed-point)
t.scaled.scaledRaw; // 1500000000000000000000n — the bigint behind display
t.scaled.pending; // set iff a multiplier change is scheduled but not yet effective
}Scheduled multiplier changes (newUIMultiplier() / effectiveAt(), ERC-165 id 0x4bd27648) are compared against the block timestamp of the same multicall: a change still in the future is surfaced under scaled.pending without being applied early; a change already effective is applied even if the token's uiMultiplier() lazily reports the old value.
Parameters
client.balances(opts: ClientBalancesOptions): Promise<BalancesResult>;
type ClientBalancesOptions = {
wallet: Wallet | Address; // a Wallet object or a bare 0x… address
tokens?: readonly Address[]; // ERC-20s to include; BEP-677 display scaling is automatic
chainId?: number; // defaults to the client's default chain
};
type BalancesResult = {
native: bigint; // native token balance in wei (BNB on BNB Smart Chain, ETH on Ethereum)
tokens?: TokenBalance[]; // present iff `tokens` was passed; input order preserved
};
type TokenBalance =
| {
address: Address;
ok: true;
raw: bigint; // on-chain balanceOf — what transfers/allowances use
decimals: number;
symbol: string; // "" if symbol() is missing/undecodable
display: string; // human string, BEP-677 multiplier already applied
scaled?: { // present iff the token implements IScaledUIAmount
uiMultiplier: bigint; // active multiplier, 1e18 fixed-point
scaledRaw: bigint; // raw * uiMultiplier / 1e18, truncated
pending?: { newUIMultiplier: bigint; effectiveAt: bigint };
};
}
| { address: Address; ok: false; error: string };Notes
- Raw vs display.
rawis always the unscaled on-chain amount — build transfers and allowance checks on it.displayis what to show users; for BEP-677 tokens the two intentionally differ. - Truncation. Scaling uses the spec's integer formula
raw × uiMultiplier / 1e18, truncating toward zero. Round-trips are not lossless. - Non-ERC-165 tokens are fine. Tokens without
supportsInterface(e.g. USDT) simply come back unscaled; the failed detection call is expected and handled. - A bad token doesn't throw. An address whose
balanceOf/decimalsreads revert yields{ ok: false, error }and leaves the other entries intact. - No funds required. It's a read against the chain's public RPC, so it doesn't cost gas and doesn't need the wallet to be funded or deployed.
- Units are wei. Use viem's
formatEtherfornative; token entries already providedisplay.