On Monad, the protocol reserves a transaction sender's gas limit against their balance before execution so a transaction cannot fail partway through for insufficient funds. As a result, the value returned by eth_getBalance is the account's total balance, not the amount immediately spendable: while transactions are pending or in flight, part of that balance is reserved and unavailable. A naive balance >= value check therefore disagrees with the chain and can produce spurious 'insufficient funds' errors or a UI balance lower than the user expects. Monad exposes an additional reserve-balance RPC surface to wallets so the reserved amount can be read explicitly rather than inferred. This article explains the mechanism, shows a runnable Node.js check that computes spendable MON with a fallback for endpoints that do not expose the reserve method, and provides a results table to measure against your own endpoint.
The Parallel-Execution Problem Reserve Balance Solves
Monad executes transactions in parallel, which means a transaction's outcome must be knowable before it runs. If a transaction could discover mid-execution that the sender no longer has enough balance to pay for gas, the chain would have to unwind work that other transactions may already depend on. Monad's reserve-balance mechanism prevents that by charging the sender's gas limit against a reserved balance before execution begins, guaranteeing the funds are available. The Monad Documentation on Reserve Balance describes this as the protocol-level guarantee that a transaction cannot fail for insufficient funds partway through.
This is a deliberate design choice, not an accounting quirk. On a sequential chain, a transaction that runs out of gas simply reverts and the sender keeps the unspent portion. On a parallel-execution chain, the protocol must commit the maximum possible cost up front so that concurrent transactions cannot race for the same funds. The reserve is the mechanism that makes that commitment explicit.
For backend developers and wallet integrators, the practical consequence is that the chain's notion of 'available balance' is narrower than the total balance. The total is what eth_getBalance returns; the spendable amount is the total minus whatever is currently reserved. Understanding that distinction is the difference between a balance check that agrees with Monad and one that silently disagrees.
The mechanism is documented by Monad itself in the Reserve Balance documentation and the wallet-integration guidance, while the standard method it extends is defined by the Ethereum JSON-RPC specification for eth_getBalance. Read the Monad page for the reserve semantics and the specification for the base contract, because only the former explains why the two agree on the raw number but differ on the spendable amount.
- Reserve balance is a protocol guarantee: the sender's gas limit is committed before execution.
- It exists because parallel execution cannot tolerate a mid-transaction funding failure.
- The reserved amount is held against pending and in-flight transactions and released as they settle.
Why eth_getBalance Is an Incomplete Spendable-Balance Answer
The Ethereum JSON-RPC specification defines eth_getBalance as returning the account balance in wei. Monad implements that contract, so the method returns the account's total balance. What the standard does not define is a separate 'spendable' figure, because on a sequential chain the two are effectively the same at the moment of the call. On Monad they can differ whenever transactions are in flight or the account maintains a reserve.
A naive check such as balance >= value therefore passes when the account's total balance is sufficient but its spendable balance is not. The transaction is then rejected, or the wallet shows an error the user cannot reconcile with the balance displayed in the UI. The Monad Documentation's Wallet Developer Integration Guide treats this as a first-class integration concern: wallets are expected to read the reserve surface rather than infer spendable balance from eth_getBalance alone.
The gap is not a bug in eth_getBalance. It is the standard method answering a different question than the one the client is asking. The standard question is 'how much does this account hold'; the client's question is 'how much can this account spend right now'. On Monad those are distinct, and the reserve surface is how the second question is answered.
- eth_getBalance returns total balance, per the Ethereum JSON-RPC specification.
- Spendable balance equals total balance minus the currently reserved amount.
- The two diverge when transactions are pending or in flight, or when a reserve is maintained.
Reserve-Balance Accounting Across the Transaction Lifecycle
Reserved balance is held against pending and in-flight transactions and released as they settle. When a transaction is submitted, the protocol reserves the sender's gas limit; when the transaction settles, the unused portion of that reservation is released back to the spendable balance. This means spendable balance is a moving quantity that recovers only after settlement, not at submission time.
The lifecycle interaction matters for polling logic. A client that reads eth_getBalance immediately after submitting a transaction will see the total balance unchanged, because the reservation does not reduce the total. A client that reads the reserve surface will see the reserved amount rise, and spendable balance fall, until the transaction settles. The Monad transaction lifecycle and asynchronous execution receipts guide explains why settlement timing is not always immediate, and eth_getTransactionReceipt returns null and receipt polling covers the polling pattern that confirms when the release has occurred.
For a backend that submits several transactions from one account, the accounting compounds: each in-flight transaction holds its own gas-limit reservation, so the spendable balance is reduced by the sum of all outstanding reservations. This is the mechanism behind the visible symptom where a UI shows a balance lower than the user expects — part of the balance is reserved against in-flight transactions' gas limits.
- Reservation occurs at submission; release occurs at settlement.
- Total balance is unchanged by reservation; spendable balance is reduced.
- Multiple in-flight transactions reserve cumulatively against the same account.
The RPC Surface for Reading the Reserve Explicitly
Rather than inferring the reserve from the difference between two balance reads, a wallet or backend should read the reserve surface directly. The Monad Documentation's JSON-RPC Overview documents an additional reserve-balance RPC surface exposed to wallets alongside the standard methods. The exact method name and response shape are documented by Monad; treat the method as Monad-specific and verify it against the current documentation for your target network before relying on it in production.
The standard calls remain necessary. eth_getBalance provides the total balance, and eth_getTransactionCount provides the nonce used for transaction construction and replacement. The reserve surface adds the missing third input: the amount currently reserved. Spendable balance is then total minus reserved. The EVM nonce management with eth_getTransactionCount guide covers the nonce side of that trio, which matters because a stuck nonce can keep a reservation outstanding longer than expected.
Whether the reserve method is available depends on the endpoint. Some providers expose it; others do not. This is a documented / varies by provider distinction: the mechanism is Monad protocol behaviour, but the RPC surface's availability is an endpoint property. A robust client should probe for the method and fall back to inference when it is absent, and should treat inference as an approximation rather than an authoritative figure.
- Read total balance with eth_getBalance and nonce with eth_getTransactionCount.
- Read the reserved amount from Monad's reserve-balance RPC surface where available.
- Compute spendable = total - reserved; fall back to inference only when the method is absent.
Gas Limits, eth_estimateGas, and Parallel Spendable Balance
Because the whole gas limit is reserved, an over-estimated limit ties up spendable balance until the transaction settles. If a transaction would actually consume 40,000 gas but the client sets a limit of 200,000, the protocol reserves against 200,000 for the duration. The difference is not lost — it is released at settlement — but it is unavailable in the meantime, which reduces how much the account can spend in parallel.
This connects eth_estimateGas directly to spendable balance. A tight, accurate estimate minimises the reserved amount and maximises the balance available for concurrent transactions. A padded estimate is safer against out-of-gas reverts but costs spendable headroom. The tradeoff is real and should be made deliberately: for a wallet submitting one transaction at a time, padding is cheap; for a backend submitting many transactions from one account, padding compounds across every in-flight transaction.
The Estimating gas price with eth_feeHistory guide covers the fee side of transaction construction. The gas-limit side is where the reserve interaction lives, and it is worth measuring: submit a transaction with a known over-estimate and observe how spendable balance behaves until settlement.
- The full gas limit is reserved, not the actual gas consumed.
- Over-estimated limits reduce parallel spendable balance until settlement.
- Tighter estimates increase spendable headroom but raise out-of-gas risk.
A Runnable Node.js Spendable-Balance Check
The following script reads the total balance, attempts to read the reserve surface, and reports the spendable amount. It uses only the standard fetch API and a JSON-RPC POST body, so it runs on Node.js 18 or later without dependencies. Replace the endpoint URL with your own Monad RPC endpoint; the Monad RPC endpoints (RPC Assistant) page lists options, and Monad mainnet covers the network itself.
The script probes for the reserve method by calling it and checking for a JSON-RPC error. If the method is not implemented, the endpoint returns an error object and the script falls back to reporting the total balance with an explicit note that spendable balance could not be determined. This is the correct behaviour: never silently report total balance as spendable when the reserve surface is unavailable.
// spendable-balance.js — Node.js 18+
// Usage: node spendable-balance.js <rpcUrl> <address>
const rpcUrl = process.argv[2];
const address = process.argv[3];
if (!rpcUrl || !address) {
console.error('Usage: node spendable-balance.js <rpcUrl> <address>');
process.exit(1);
}
async function rpc(method, params) {
const res = await fetch(rpcUrl, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
});
const json = await res.json();
if (json.error) {
const err = new Error(json.error.message || 'rpc error');
err.code = json.error.code;
throw err;
}
return json.result;
}
function toMon(weiHex) {
return Number(BigInt(weiHex)) / 1e18;
}
(async () => {
const totalHex = await rpc('eth_getBalance', [address, 'latest']);
const total = toMon(totalHex);
console.log('total balance (MON):', total);
// Probe the Monad reserve-balance surface.
// Method name and params are documented by Monad; verify against current docs.
let reserved = null;
try {
const reservedHex = await rpc('eth_getReserveBalance', [address, 'latest']);
reserved = toMon(reservedHex);
console.log('reserved (MON):', reserved);
console.log('spendable (MON):', total - reserved);
} catch (e) {
console.warn('reserve surface unavailable on this endpoint:', e.message);
console.warn('spendable balance cannot be determined; total shown above.');
}
})();A curl Probe for Endpoint Reserve-Surface Availability
Before wiring the reserve surface into a production client, confirm that your endpoint implements it. The following curl command sends a single JSON-RPC request and prints the raw response. A successful response contains a result field with a hex quantity; an endpoint that does not implement the method returns an error object with a method-not-found code. Run it against each endpoint you intend to use, because availability is a documented / varies by provider property.
The same probe pattern applies to any Monad-specific method. Keep the probe in your deployment checklist so that a provider change does not silently degrade your spendable-balance calculation into an inference.
curl -s -X POST "$MONAD_RPC_URL" \
-H 'content-type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getReserveBalance",
"params": ["0xYourAddressHere", "latest"]
}'Results Table: Measuring Reserve Behaviour on Your Endpoint
The table below is a template for measuring your own endpoint. Fill it in by running the Node.js script and the curl probe against each endpoint you use, then repeating the balance reads while a transaction is pending. The goal is to establish, for your infrastructure, whether the reserve surface is present and how spendable balance behaves under load. Do not rely on numbers from this article; measure against your own endpoint.
Record the endpoint label, whether eth_getBalance is present, whether the reserve surface is present, the total balance, the reserved amount, and the spendable amount under a pending transaction. A row where the reserve surface is absent is not a failure of the endpoint — it means your client must fall back to inference and should say so in its UI or logs.
- Endpoint label: a name you will recognise later.
- eth_getBalance present: yes/no.
- Reserve surface present: yes/no (from the curl probe).
- Total balance (MON): from eth_getBalance.
- Reserved (MON): from the reserve surface, or 'n/a'.
- Spendable under pending tx (MON): total minus reserved, or 'indeterminate'.
Failure Modes and Troubleshooting
The most common symptom is a wallet or backend reporting 'insufficient funds' when eth_getBalance looks sufficient. This happens when the client compares total balance against value without subtracting the reserve. The fix is to compute spendable balance and compare against that. If the reserve surface is unavailable, the client should surface the uncertainty rather than assert sufficiency.
A second symptom is a spendable balance that recovers only after settlement. This is expected behaviour, not a stuck state: the reservation is released when the transaction settles. If the recovery seems slow, check whether the transaction is actually settling by polling the receipt, as described in eth_getTransactionReceipt returns null and pending receipt polling. A transaction that never settles — for example, one stuck behind a nonce gap — will hold its reservation indefinitely, which is why nonce management matters.
A third symptom is an endpoint that does not implement the reserve method, forcing inference. Inference means reading total balance and subtracting an estimate of the reserve derived from your own pending transactions. That estimate is only as good as your view of the mempool, which is not authoritative. Treat inferred spendable balance as a lower bound and prefer endpoints that expose the reserve surface for any workflow where the distinction matters.
- Spurious 'insufficient funds': compare against spendable, not total.
- Slow recovery: confirm settlement via receipt polling before assuming a fault.
- Missing reserve method: fall back to inference and label the result as approximate.
Limitations and Tradeoffs of the Reserve Model
The reserve is a protocol guarantee with a UX cost. It makes parallel execution safe, but it means the balance a user sees and the balance a user can spend are not the same number while transactions are in flight. Wallets that ignore the distinction will disagree with Monad's own wallet behaviour, and users will notice.
The reserve surface is Monad-specific and not portable to other EVM chains. Code written against it will not run unchanged on chains that lack the mechanism, and code written for those chains will under-report spendable balance on Monad. If you maintain a multi-chain client, isolate the reserve logic behind a capability check rather than assuming it everywhere.
Finally, the reserve surface's availability varies by endpoint. The mechanism is documented Monad behaviour; the RPC surface is an endpoint property. Design your client so that a missing reserve method degrades gracefully, and so that a provider change does not silently turn an authoritative spendable-balance figure into an inference. For production workloads, review RPC pricing and API service options with reserve-surface availability as an explicit requirement.
- Reserve improves execution safety at the cost of a more complex balance model.
- The reserve surface is Monad-specific and not portable across EVM chains.
- Endpoint availability varies; degrade gracefully and label inferred values.
Next Steps for Integrating Spendable Balance
Start by probing your endpoints for the reserve surface using the curl command above, and record the results in the table. Then wire the Node.js check into your balance-display and pre-flight validation paths, replacing any balance >= value comparison with a spendable >= value comparison. Where the reserve surface is absent, log the fallback so you can see how often inference is used.
Next, tighten your gas-limit strategy. Review where eth_estimateGas is called and whether the padding is justified for your submission pattern. For accounts that submit concurrently, measure how much spendable headroom padding consumes and adjust. The Monad RPC timeouts and reliable retry patterns guide is useful here, because retries can create additional in-flight transactions and therefore additional reservations.
Finally, keep the mechanism in view when debugging balance discrepancies. The OnFinality Learn hub collects related guides on Monad RPC behaviour, and the Monad RPC endpoints (RPC Assistant) page is the starting point for endpoint selection. Treat spendable balance as a first-class quantity in your client, not a derived afterthought.
- Probe endpoints and record reserve-surface availability.
- Replace balance >= value with spendable >= value in validation paths.
- Review gas-limit padding and retry behaviour for their effect on reservations.