Fix #50: fail-fast spendable check and spendable-balance model#51
Conversation
…cted incoming change Input selection now errors before proving when spendable UTXOs cannot cover amount + fee, distinguishing "insufficient funds" from "funds awaiting confirmation" (the latter only when the expected incoming change actually covers the shortfall). Root cause of #50, where an underfunded selection produced a 0-input transaction that failed the VM's balance assertion after ~20s of proving. get_all_balance returns (available, pending, total): available excludes inputs of unconfirmed transactions, pending is the expected incoming amount (change/self-sends) computed from stored pending transaction details, and total drops by the sent amount at broadcast. The RPC balance response gains pending_balance. The per-account cached total is refreshed after send and forget, not only on block sync. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ifecycle Wallet page shows the ownership view (figure includes pending change, fixed 4-decimal precision, de-emphasized decimals, equal card heights); the Send page shows "Spendable now" with an awaiting-confirmation hint. Broadcast, forget, and block sync all refetch balance + wallets list; the sync_finish listener is height-guarded so idle heartbeats fetch nothing, and the accounts table only overlays on first load. The pending list is account-stamped (executionAddressId) so a stale list never flashes after an account switch, and confirmed transactions leave the pending section on their own via a block-gated refetch. Pending-transaction cards get a proper header (Sent <time> + absolute timestamp on hover + Forget), the accounts-table balance column is right-aligned with tabular figures, address-cell clicks switch accounts (copy button excepted), and the toast utility gains sticky/fixed-id options with an 8s error duration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copying inside a clickable row (e.g. the accounts table, where a row click switches the active account) must never also trigger the row action. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| // Expected incoming amount (change and self-sends) from this wallet's | ||
| // pending transactions; credited back to the balance once they are mined. |
There was a problem hiding this comment.
Is this always a non-negative number? I'm not 100 % sure what it means. It only tracks positive changes to balance, right? If I'm right, please add: "Always a non-negative number; only tracks positive changes to balance." If I'm wrong elaborate on what this means in the docstring of the field.
Also: Use docstring here instead of regular comments. Docstrings are preceded with three forward slashes: /// comment.
There was a problem hiding this comment.
I added docstrings and renamed fields available_balance and pending_balance because I realized they no longer accurately reflected the underlying values after these changes.
The new names are:
available_balance → spendable_balance
pending_balance → pending_change (which is always >= 0)
The available balance shown on the Wallet page is calculated as:
available_balance = spendable_balance + pending_change
The updated names should make their intent clearer and the docstrings provide additional context where needed.
On a related note, many crypto wallets deduct a transaction amount from the available balance as soon as the transaction is broadcast without waiting for its confirmation. Neptune Wallet did not previously follow this convention, but with this change, the available balance shown in the UI will now reflect pending outgoing transactions immediately.
There was a problem hiding this comment.
Just one final check: do you approve the updated naming?
There was a problem hiding this comment.
Just one final check: do you approve the updated naming?
Yes :)
| let pending_utxos = self.updater.get_pending_spent_utxos().await?; | ||
| utxos.retain(|utxo| !pending_utxos.contains(&utxo.id)); | ||
| let pending_ids = self.updater.get_pending_spent_utxos().await?; | ||
| let utxos: Vec<_> = utxos |
There was a problem hiding this comment.
Nice. I like shadowing when used like this, to overwrite an existing value with a "more refined" calculation.
Resolves the src/App.tsx conflict: keep main's unused-variable cleanup (serverUrl-only destructure) and this branch's sync-height guard, and restore the (event) parameter on the SYNC_FINISH_EVENT listener, which the auto-merge dropped while the guard logic reads event.payload. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sending while a previous transaction was still unconfirmed could build an unfunded 0-input transaction that failed with a cryptic VM error after a long proving run. This PR makes such sends fail instantly with a clear message, and reworks the balance model so every figure in the app accounts for pending transactions correctly.
Fixes #50
Backend (Rust)
get_all_balancereturns (available, pending, total). Available excludes inputs of unconfirmed transactions, pending is the expected incoming change, and total drops at broadcast rather than at confirmation. The RPC balance response gainspending_balance.Frontend
Verification
tsc --noEmitclean, no new warnings.tauri devthrough two days of live testing with a dozen-plus real transactions, including failure cases surfaced gracefully by the new error paths.Screenshots
🤖 Generated with Claude Code