Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased (develop)

- changed: Sign MoonPay buy/sell widget URLs and bind them to the customer's IP via the info server, for MoonPay's on-ramp IP-matching security upgrade.
- fixed: Improve the unstake error experience by replacing the popup alert and generic "unknown error occurred" with the real error in the scene's error field, and showing a clear message when the wallet lacks the balance to cover the unstaking network fee.

## 4.50.0 (2026-07-21)

Expand Down
38 changes: 38 additions & 0 deletions src/__tests__/stakeErrorUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { describe, expect, test } from '@jest/globals'
import { DustSpendError, InsufficientFundsError } from 'edge-core-js'

import { lstrings } from '../locales/strings'
import { getDisplayErrorMessage } from '../util/stakeErrorUtils'

describe('getDisplayErrorMessage', () => {
test('returns the message of an Error that carries one', () => {
expect(getDisplayErrorMessage(new Error('Insufficient funds'))).toBe(
'Insufficient funds'
)
})

test('fishes out the message from edge-core-js error subclasses', () => {
expect(
getDisplayErrorMessage(new InsufficientFundsError({ tokenId: null }))
).not.toBe('')
expect(getDisplayErrorMessage(new DustSpendError())).not.toBe('')
})

test('falls back to the generic string for an Error with no message', () => {
expect(getDisplayErrorMessage(new Error(''))).toBe(
lstrings.unknown_error_occurred_fragment
)
})

test('falls back to the generic string for non-Error values', () => {
expect(getDisplayErrorMessage('some string')).toBe(
lstrings.unknown_error_occurred_fragment
)
expect(getDisplayErrorMessage(undefined)).toBe(
lstrings.unknown_error_occurred_fragment
)
expect(getDisplayErrorMessage(null)).toBe(
lstrings.unknown_error_occurred_fragment
)
})
})
23 changes: 18 additions & 5 deletions src/components/scenes/Staking/StakeModifyScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { useDispatch, useSelector } from '../../../types/reactRedux'
import type { EdgeAppSceneProps } from '../../../types/routerTypes'
import { getCurrencyIconUris } from '../../../util/CdnUris'
import { getWalletName } from '../../../util/CurrencyWalletHelpers'
import { getDisplayErrorMessage } from '../../../util/stakeErrorUtils'
import {
enableStakeTokens,
getPolicyIconUris,
Expand Down Expand Up @@ -227,15 +228,26 @@ const StakeModifySceneComponent: React.FC<Props> = props => {
)
setErrorMessage(errMessage)
} else if (err instanceof InsufficientFundsError) {
setErrorMessage(lstrings.exchange_insufficient_funds_title)
// The unstake network fee is paid in the wallet's native asset, so
// tell the user which balance they need instead of a bare
// "Insufficient Funds".
setErrorMessage(
changeQuoteRequest.action === 'unstake'
? sprintf(
lstrings.stake_error_insufficient_funds_unstake_s,
wallet.currencyInfo.currencyCode
)
: lstrings.exchange_insufficient_funds_title
)
} else if (
err instanceof HumanFriendlyError ||
err instanceof DustSpendError
) {
setErrorMessage(err.message)
} else {
showError(err)
setErrorMessage(lstrings.unknown_error_occurred_fragment)
// Show the real error in the on-scene error field rather than a
// scary popup alert plus a generic "unknown error occurred".
setErrorMessage(getDisplayErrorMessage(err))
}
})
.finally(() => {
Expand Down Expand Up @@ -329,8 +341,9 @@ const StakeModifySceneComponent: React.FC<Props> = props => {
}, 10000)
})
.catch((err: unknown) => {
showError(err)
setErrorMessage(lstrings.unknown_error_occurred_fragment)
// Surface the real error in the on-scene error field instead of a
// scary popup alert plus a generic "unknown error occurred".
setErrorMessage(getDisplayErrorMessage(err))
})
.finally(() => {
setSliderLocked(false)
Expand Down
2 changes: 2 additions & 0 deletions src/locales/en_US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1745,6 +1745,8 @@ const strings = {
stake_modal_modify_stake_title: 'Stake from %s',
stake_modal_modify_unstake_title: 'Unstake from %s',
stake_error_insufficient_s: 'Insufficient %s',
stake_error_insufficient_funds_unstake_s:
'This wallet needs a %s balance to cover the network fee for unstaking.',
stake_error_stake_below_minimum: 'Stake amount below minimum',
stake_error_unstake_below_minimum: 'Unstake amount below minimum',
state_error_pool_full_s:
Expand Down
1 change: 1 addition & 0 deletions src/locales/strings/enUS.json
Original file line number Diff line number Diff line change
Expand Up @@ -1368,6 +1368,7 @@
"stake_modal_modify_stake_title": "Stake from %s",
"stake_modal_modify_unstake_title": "Unstake from %s",
"stake_error_insufficient_s": "Insufficient %s",
"stake_error_insufficient_funds_unstake_s": "This wallet needs a %s balance to cover the network fee for unstaking.",
"stake_error_stake_below_minimum": "Stake amount below minimum",
"stake_error_unstake_below_minimum": "Unstake amount below minimum",
"state_error_pool_full_s": "The %1$s asset pool is currency full. Please try again later.",
Expand Down
12 changes: 12 additions & 0 deletions src/util/stakeErrorUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { lstrings } from '../locales/strings'

/**
* Extract a user-presentable message from an unknown error thrown while
* fetching or approving a stake change quote. Falls back to a generic string
* when the error carries no message, so the on-scene error field can show the
* real reason instead of a scary popup alert plus "unknown error occurred".
*/
export const getDisplayErrorMessage = (err: unknown): string =>
err instanceof Error && err.message !== ''
? err.message
: lstrings.unknown_error_occurred_fragment
Loading