feat(frontend): live wallet balances and safe maximum stake calculation - #577
Merged
PeterOche merged 1 commit intoAug 28, 2026
Merged
Conversation
StakingInterface derived its 25/50/75/MAX presets from a hard-coded BALANCE = 1000, so a user could select an amount unrelated to the connected wallet and only discover the problem when the transaction failed. The screen also could not tell an insufficient token balance apart from a missing trustline or from not holding enough XLM to pay the fee. Add src/lib/stellar and the useWalletBalances hook as the single source of truth for how much a wallet can actually stake: - the stake asset is identified by asset code AND issuer (or the SAC contract ID), never by symbol, so a same-ticker impostor asset is not counted as a balance and a market denominated in another asset blocks staking outright; - spendable is narrower than balance: the base reserve, selling liabilities and, when staking XLM, a fee buffer all come off the top, so MAX cannot produce a transaction the network is certain to reject; - MAX and the percentage presets are clamped to the contract's min_stake / max_stake_per_user; - every amount stays a bigint of stroops, preserving Stellar's 7 decimals; the amount field is now editable so an exact amount can be entered, and formatting happens only at display. Horizon's three outcomes are modelled explicitly (ok, not-found, unavailable) so an unreachable RPC is shown as stale data rather than silently read as a zero balance. The panel renders a distinct state for disconnected wallet, unfunded account, missing/unauthorized trustline, stale response, zero balance and insufficient fee balance, each with a recovery action. Reads are keyed by network|address: concurrent consumers and the post-stake refresh join one in-flight request, and a reply that lands after the wallet changed is discarded rather than attributed to the new address. Covered by 75 unit tests over decimal conversion, MAX/percent maths and snapshot derivation, 12 hook tests over refresh and de-duplication, and 24 component tests over each disabled/error state.
PeterOche
approved these changes
Aug 28, 2026
10 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
StakingInterface derived its 25/50/75/MAX presets from a hard-coded BALANCE = 1000, so a user could select an amount unrelated to the connected wallet and only discover the problem when the transaction failed. The screen also could not tell an insufficient token balance apart from a missing trustline or from not holding enough XLM to pay the fee.
Add src/lib/stellar and the useWalletBalances hook as the single source of truth for how much a wallet can actually stake:
Horizon's three outcomes are modelled explicitly (ok, not-found, unavailable) so an unreachable RPC is shown as stale data rather than silently read as a zero balance. The panel renders a distinct state for disconnected wallet, unfunded account, missing/unauthorized trustline, stale response, zero balance and insufficient fee balance, each with a recovery action.
Reads are keyed by network|address: concurrent consumers and the post-stake refresh join one in-flight request, and a reply that lands after the wallet changed is discarded rather than attributed to the new address.
Covered by 75 unit tests over decimal conversion, MAX/percent maths and snapshot derivation, 12 hook tests over refresh and de-duplication, and 24 component tests over each disabled/error state.
closes #576