getBalance Solana Developer Guide
Learn how to read a single Solana account balance with getBalance, while handling API keys in a frontend — Shield keys that keep your account key out of the browser, a wallet connector in pure JavaScript, and the connected wallet's balance on screen.
Reference documentation: getBalance
Before We Begin
If you are new to Solana development, it would be a good idea to read through the developer guides for getAccountInfo, getMultipleAccounts, and getProgramAccounts before continuing.
This guide will require a FluxRPC API key (a free plan one is sufficient). There is no need to copy-paste it — just navigate back to this page while logged in, and we will automatically use the key you select.
In this guide, we will accomplish the following:
- Fetch the balance of a single account on the Solana blockchain
- Use shielded API keys to build a frontend application without revealing your API key to users
- Build a wallet connector in pure JavaScript
- Display the balance of the wallet that the user connects
We will provide examples in JavaScript in this guide.
Fetching the Balance of an Account on Solana
Just want to check a balance?
You do not need this guide. The getBalance Try It runs the same request in your browser — paste an address, press the button, done. This page is for putting that request inside a site of your own, which means writing a little code.
Fetching an account's balance on Solana is very straightforward. You only need to know the account's public key to perform a getBalance RPC call. While it is also possible to get an account's balance with getAccountInfo and (more efficiently) with getMultipleAccounts, often in a frontend application it's practical to just fetch the balance of a single account, e.g. a user's wallet.
As a pure RPC call, getBalance works as follows:
curl "https://eu.fluxrpc.com?key=<Your-API-Key>" -s -X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getBalance",
"params": [
"83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"
]
}'
Shielded API Keys
One thing that's impractical in the previous request is that it requires your API key. If your frontend code includes that API key, it's trivial for anyone to take it and use it for themselves. Shielded API keys mitigate this issue.
Shielded API keys have a low per-IP-address rate limit — 5 requests per second. This means they work fine for your app, which is used by many users on many different IP addresses, but someone can't access the full rate limit of your API key with what you give them. Shielded keys are used at slightly different URLs than normal API keys:
curl "{{SHIELD_RPC_URL}}?key={{SHIELD_KEY}}" -s -X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getBalance",
"params": [
"83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"
]
}'
Install a Wallet Extension
Before continuing, you will need a Solana wallet browser extension — Phantom, Solflare, or Backpack. You'll also need to create a wallet if you don't already have one. Each extension announces itself to the pages you visit, which is how the example finds it later.
Create the Two Files
Make a new folder anywhere, and create two empty files inside it with your editor.
wallet-balance/
├── index.html
└── app.js
Put this in index.html. The IDs match up with the JS code.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>FluxRPC Wallet Balance</title>
</head>
<body>
<main>
<h1>FluxRPC Wallet Balance</h1>
<button id="connect-wallet-button" type="button">Connect Wallet</button>
<button id="get-balance-button" type="button" disabled>Get Balance</button>
<p id="wallet-address"></p>
<p id="status-message" role="status"></p>
<pre id="balance-output"></pre>
</main>
<script src="./app.js" defer></script>
</body>
</html>
app.js contains all the JavaScript logic. The sections after this one build it up one part at a time and explain each. If you would rather copy the entire file once, it's the first code block below:
Add the Shield Key
Start app.js with your Shield key and the Shield host. Sign in and both values below are already yours, otherwise replace the placeholders by hand. Paste the key on its own: the ?key= part is added at request time, and a full URL in FLUXRPC_SHIELD_KEY is the easiest way to earn yourself a confusing invalid api key response.
The third constant is a unit. Solana counts balances in lamports, and one SOL is a billion of them. If you were displaying the balance of a specific token (using getTokenAccountBalance, or the more efficient trick we covered in getMultipleAccounts), you might use a different number here, determined by the mint decimals. For example if it was mint decimals=6, you might put 1_000_000.
The rest is the five element IDs from the HTML and the one piece of state this app keeps: the connected address.
const FLUXRPC_SHIELD_KEY = "{{SHIELD_KEY}}";
const FLUXRPC_SHIELD_RPC_URL = "{{SHIELD_RPC_URL}}";
const LAMPORTS_PER_SOL = 1_000_000_000;
const connectButton = document.getElementById("connect-wallet-button");
const balanceButton = document.getElementById("get-balance-button");
const addressOutput = document.getElementById("wallet-address");
const statusOutput = document.getElementById("status-message");
const balanceOutput = document.getElementById("balance-output");
let walletAddress = "";
Find the Installed Wallets
Wallets do not share a common variable to look under — Phantom, Solflare, and Backpack each use their own, and any wallet released tomorrow will use another. So instead of guessing names, use the Wallet Standard handshake that all major Solana wallets implement: your page dispatches one event announcing it is ready, and each installed wallet answers by registering itself.
The page never names a wallet, so this works with extensions that did not exist when you wrote it. Wallets that register a moment later push into the same array, so a slow extension still turns up before anyone clicks.
// The Wallet Standard handshake: the page announces it is ready, and every
// installed wallet answers by registering itself.
function discoverWallets() {
const found = [];
const api = {
register(...wallets) {
wallets.forEach((wallet) => {
if (wallet.chains?.some((chain) => chain.startsWith("solana:"))) {
found.push(wallet);
}
});
return () => {};
},
};
window.addEventListener("wallet-standard:register-wallet", (event) => {
event.detail(api);
});
window.dispatchEvent(
new CustomEvent("wallet-standard:app-ready", { detail: api }),
);
return found;
}
const solanaWallets = discoverWallets();
Connect the Wallet
connectWallet asks the first wallet it found for permission through its standard:connect feature and reads account.address. This is already a base58 public key, so no conversion is needed. As we mentioned earlier, that public key is the only thing the balance request needs.
Connecting the wallet to get the user's public key is a permission prompt, not a signature. Connecting a wallet in this way should never ask the user to sign a transaction.
async function connectWallet() {
const wallet = solanaWallets[0];
if (!wallet) {
throw new Error(
"No Solana wallet found. Install Phantom, Solflare, or Backpack, then reload this page.",
);
}
const { accounts } = await wallet.features["standard:connect"].connect();
walletAddress = accounts[0].address;
addressOutput.textContent = "Connected public key: " + walletAddress;
}
Request the Balance
Now, we fetch the balance using the user's public key and our shielded FluxRPC API key. It's the getBalance RPC call we mentioned earlier.
async function loadBalance() {
const response = await fetch(
FLUXRPC_SHIELD_RPC_URL + "?key=" + encodeURIComponent(FLUXRPC_SHIELD_KEY),
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "getBalance",
params: [walletAddress],
}),
},
);
const payload = await response.json();
// JSON-RPC reports failures in the body, not in the HTTP status code.
if (payload.error) {
throw new Error(payload.error.message || payload.error);
}
const lamports = payload.result.value;
balanceOutput.textContent = JSON.stringify(
{
publicKey: walletAddress,
lamports,
sol: lamports / LAMPORTS_PER_SOL,
},
null,
2,
);
}
Wire the Buttons
Finally we'll add some simple click handlers. Each one announces what it is doing, awaits the step, and prints the error message if it throws. Get Balance starts disabled in the HTML and becomes available once a wallet is connected.
connectButton.addEventListener("click", async () => {
statusOutput.textContent = "Opening the wallet...";
try {
await connectWallet();
statusOutput.textContent = "Wallet connected.";
balanceButton.disabled = false;
} catch (error) {
statusOutput.textContent = error.message;
}
});
balanceButton.addEventListener("click", async () => {
statusOutput.textContent = "Loading balance through FluxRPC...";
try {
await loadBalance();
statusOutput.textContent = "Balance loaded.";
} catch (error) {
statusOutput.textContent = error.message;
}
});
In this example, we've specifically avoided complex dependencies. This is for two reasons — first, the extra abstraction would get in the way of us showing you how things work. Second, using unnecessary dependencies is an attack vector for supply chain attacks.
There have been a few bad ones on NPM this year (I probably won't have to update this text next year...). So it's a good idea to avoid unnecessary dependencies where practical.
Run It on localhost
Run one of these inside the folder holding your two files to launch a temporary web server:
# if you have Python
python3 -m http.server 8000
# the same command, on Windows
py -m http.server 8000
# if you have Node.js instead
npx serve . --listen 8000
Then open http://localhost:8000 in your browser. Type that address yourself rather than using any links from the terminal. This is because you want to specifically be on localhost and not some IP address, it's likely that your wallet will refuse to connect to a non-HTTPS website unless it's localhost.
If you prefer not to use a terminal
In VS Code, install the Live Server extension, then right-click
index.htmland choose Open with Live Server. It serves the folder onlocalhostand opens the browser for you.
Click Connect Wallet and approve the wallet prompt. The page prints your public key and the status line reads Wallet connected. Then click Get Balance. The output block fills in with the wallet's mainnet balance:
{
"publicKey": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"lamports": 1435000000,
"sol": 1.435
}
lamports is what the RPC returns; sol is that number divided by 1,000,000,000.
Troubleshooting
- "No Solana wallet found" — nothing answered the handshake. First look at the address bar: Phantom, and some other extensions, load only on
https://andlocalhost, so if it reads[::]:8000,0.0.0.0:8000, orfile://, reopen the page onhttp://localhost:8000— same server, different address. If the address is right, check the extension is installed and enabled, then reload the page. - Nothing happens when you click Connect Wallet — either the wallet opened a prompt your browser suppressed (open the extension from the toolbar and approve it there), or the browser never loaded
app.js. The Network tab shows a 404 in that case: check the file sits next toindex.htmland is named exactlyapp.js. Some editors quietly append.txt. invalid api keyor a 401 response —FLUXRPC_SHIELD_KEYholds the wrong value. It should be the Shield key alone: not the account API key, and not a URL with?key=already in it.- The balance is always 0 — either the wallet is on devnet or testnet (this endpoint reads mainnet only), or the address genuinely holds no SOL. getBalance cannot tell you which.
- Get Balance stays greyed out — it only turns on after Connect Wallet succeeds. If the status line shows an error, fix that first.
Tips & Tricks
- Use the Shield key in browser code, never the account API key.
- Treat the Shield key as public, not secret. A key shipped to browsers is readable by definition. Anyone can lift it from your source and send requests on your bandwidth. Masking techniques hide your account key, it does not stop that. Use a Shield key dedicated to the site you are deploying, and keep an eye on its usage.
- A Shield key's lifecycle follows the API key behind it, so retiring one means deleting that API key on the API Keys page and creating a replacement. A separate key per site keeps that from taking down everything else you run.
- Before you ship a real app, subscribe to the wallet's
standard:eventschangeevent so the page clears the balance when the user switches accounts or disconnects. - If you need more security for your API key, you can create a backend that holds the true API key, and implement your own per-user rate limiting or access control.
Next Steps
If you have not already, getAccountInfo covers what else lives in an account besides its balance, and getMultipleAccounts is what you reach for the moment you need more than one balance at a time.