Wallet balances

Per-wallet token balances with optional USD valuation via SDP.

GET /v1/payments/wallets/{walletId}/balances returns a custody wallet's native SOL balance and every SPL token it holds, with raw and human-readable amounts, decimals, and a USD valuation where SDP has a price. SOL always comes first, as token: "SOL" with mint: "So11111111111111111111111111111111111111112". Token accounts with a zero balance are left out.

Request

walletId is the provider wallet ID: the walletId field from GET /v1/wallets, not the wallet's id (cwlt_…) or its address. The request needs an API key with wallets.read; a key limited to selected wallets reads only those.

curl 'https://api.solana.com/v1/payments/wallets/privy_wallet_123/balances' \
  -H "Authorization: Bearer $SDP_API_KEY"
const res = await fetch("https://api.solana.com/v1/payments/wallets/privy_wallet_123/balances", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.SDP_API_KEY}`
  }
});
GetWalletBalances.java
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.solana.com/v1/payments/wallets/privy_wallet_123/balances"))
    .header("Authorization", "Bearer sf_...")
    .GET()
    .build();

If SDP can't read the SOL balance or any token balance, the request returns 503 instead of partial or zero balances. To read state no older than a slot you already saw, pass minimumSlot; the response echoes it in data.balanceReadContext.minimumSlot.

Response shape

The payload sits in the standard data / meta envelope, with the balances in data.walletBalances.balances:

response.json
{
  "data": {
    "walletBalances": {
      "walletId": "privy_wallet_123",
      "address": "3xYZa...2aBc",
      "balances": [
        {
          "token": "SOL",
          "mint": "So11111111111111111111111111111111111111112",
          "amount": "2500000000",
          "uiAmount": "2.5",
          "decimals": 9
        },
        {
          "token": "USDC",
          "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
          "amount": "1500000000",
          "uiAmount": "1500",
          "decimals": 6,
          "usdPrice": 1,
          "usdValue": 1500
        }
      ]
    }
  },
  "meta": { "requestId": "req_...", "timestamp": "2026-05-18T00:00:00.000Z" }
}

Per-balance fields:

FieldNotes
tokenToken symbol if known, otherwise the onchain mint.
mintOnchain mint address.
amountRaw amount in the smallest unit (a string, to preserve precision).
uiAmountHuman-readable decimal string: amount / 10^decimals.
decimalsThe token's decimal count.
usdPricePer-token USD price; omitted when no price is available.
usdValueTotal USD value (usdPrice * uiAmount); omitted when no price is available.
confidentialReserved for confidential transfer balances; absent for standard balances.

Use cases

  • Account dashboards: show a custody wallet's contents to its operator.
  • Pre-transfer balance checks: confirm a wallet holds enough of a token before you start a payout batch.
  • Treasury accounting: take periodic snapshots into an internal ledger.

Pricing freshness

SDP prices balances only on mainnet. In a sandbox project on devnet, usdPrice and usdValue are always absent.

usdPrice is a display estimate from Tokens.xyz. SDP caches each price for up to 60 seconds. Some source snapshots refresh hourly; SDP omits snapshots older than two hours or without a timestamp. The snapshot timestamp doesn't prove when its underlying price was observed.

Use an executable quote for a trade and an independently verified valuation for accounting. The onchain amount and uiAmount remain the balance values. When pricing is unavailable, usdPrice and usdValue are absent: the omission means "unknown," not "$0.00."

Compared with onchain RPC

You can also list SPL token balances by calling getTokenAccountsByOwner on a Solana RPC node and decoding the token-account data. Use SDP's balances endpoint for:

  • Token resolution: SDP resolves the mint to a symbol when it knows one; raw RPC returns mints only.
  • USD pricing: RPC has no prices.
  • One fewer dependency: no separate RPC integration when you already call SDP.

Go to RPC directly for:

  • State at an exact slot: for an audit that needs the wallet as of one slot. SDP's minimumSlot sets only a lower bound.
  • Confidential transfer balances: full extension support is RPC-side.
  • Latency-sensitive paths: RPC is one hop closer to the cluster.