diff --git a/CHANGELOG.md b/CHANGELOG.md
index 685ea4ac0e3..28179efd375 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,9 +4,23 @@
- added: Verbose logging for exchange rate queries: the request body, resolved/rate-less counts, and errors are captured when the Verbose Logging setting is enabled.
- added: Exchange-rate cache snapshot in the support log output, plus a `rates-cache-replay` script that re-runs those queries against the rates server and reports the result for each pair.
+- added: "-m" tag on the version number in the Help scene for Maestro test builds
- 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.
+- changed: Style the entire "Already have an account? Sign in" line in the getting-started USP carousel with the tertiary link color, not just "Sign in".
+- changed: Refresh the buy, sell, sort, scan-QR and FIO names icons to the updated design.
+- changed: Use a custom chart icon for the side menu Markets row, so it matches the rest of the menu.
+- changed: Use the UI4 warning card for the Reveal Raw Keys and Reveal Master Private Key password confirmation warnings.
+- changed: Tron resource staking now describes its claim action as reclaiming your own TRX, instead of claiming a reward.
- fixed: NYM max swaps from EVM wallets now report the correct limit error instead of an unsupported-route error (edge-exchange-plugins 2.52.1).
- fixed: Exchange rate queries no longer request each chain's own asset twice, which had been inflating every rate query with duplicate pairs.
+- fixed: XRP minimum balance warning copy to clarify the reserve is met once the address balance reaches 1 XRP, not on top of it.
+- fixed: Round fiat balances to cents in the Wallets list, matching the wallet detail scene
+- fixed: Notification center cards no longer shrink their text to fit. Long titles and messages now truncate with an ellipsis so every card renders at the same size.
+- fixed: Sort the Privacy Settings Nym Mix Net asset list alphabetically by display name
+- fixed: Next button overlapping the wallet list on the Choose Wallets to Add scene
+- fixed: Wrap the fiat value in parentheses on the Stake/Unstake/Claim amount row, and remove the space between the fiat symbol and amount to match the network fee tile.
+- fixed: Staked "locked" balance in the wallet view no longer gets cut off. The crypto amount is truncated to an exchange-rate-appropriate number of decimals, and the text is no longer clamped to a fraction of the card width.
+- 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)
diff --git a/android/app/src/main/assets/fonts/custom.ttf b/android/app/src/main/assets/fonts/custom.ttf
index 605e9dd4b39..313e372e5db 100644
Binary files a/android/app/src/main/assets/fonts/custom.ttf and b/android/app/src/main/assets/fonts/custom.ttf differ
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 988cc271d09..2b4d8469310 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -225,13 +225,11 @@ export default [
'src/components/modals/ScanModal.tsx',
'src/components/modals/StateProvinceListModal.tsx',
- 'src/components/modals/TextInputModal.tsx',
'src/components/modals/TransferModal.tsx',
'src/components/modals/WalletListSortModal.tsx',
'src/components/modals/WcSmartContractModal.tsx',
- 'src/components/navigation/AlertDropdown.tsx',
'src/components/navigation/BackButton.tsx',
'src/components/navigation/CurrencySettingsTitle.tsx',
'src/components/navigation/EdgeHeader.tsx',
@@ -245,7 +243,7 @@ export default [
'src/components/navigation/ParamHeaderTitle.tsx',
'src/components/navigation/SideMenuButton.tsx',
'src/components/navigation/TransactionDetailsTitle.tsx',
- 'src/components/notification/NotificationCenterCard.tsx',
+
'src/components/progress-indicators/AccountSyncBar.tsx',
'src/components/progress-indicators/FullScreenLoader.tsx',
@@ -401,7 +399,7 @@ export default [
'src/components/tiles/AprCard.tsx',
'src/components/tiles/CountdownTile.tsx',
'src/components/tiles/CryptoFiatAmountTile.tsx',
- 'src/components/tiles/EditableAmountTile.tsx',
+
'src/components/tiles/ErrorTile.tsx',
'src/components/tiles/FiatAmountTile.tsx',
'src/components/tiles/InterestRateChangeTile.tsx',
@@ -463,7 +461,7 @@ export default [
'src/plugins/gui/util/fetchRevolut.ts',
'src/plugins/gui/util/initializeProviders.ts',
- 'src/plugins/stake-plugins/currency/tronStakePlugin.ts',
+
'src/plugins/stake-plugins/generic/pluginInfo/optimismTarotPool.ts',
'src/plugins/stake-plugins/generic/policyAdapters/CardanoKilnAdaptor.ts',
'src/plugins/stake-plugins/generic/policyAdapters/EthereumKilnAdaptor.ts',
diff --git a/src/__tests__/modals/__snapshots__/TextInputModal.test.tsx.snap b/src/__tests__/modals/__snapshots__/TextInputModal.test.tsx.snap
index 57fdb3f2886..c67d53b4e75 100644
--- a/src/__tests__/modals/__snapshots__/TextInputModal.test.tsx.snap
+++ b/src/__tests__/modals/__snapshots__/TextInputModal.test.tsx.snap
@@ -286,12 +286,13 @@ exports[`TextInputModal should render with a blank input field 1`] = `
- € 0.23
+ (€0.23)
- € 0.23
+ (€0.23)
- Amount: 0.00001234 BTC (€ 0.23)
+ Amount: 0.00001234 BTC (€0.23)
- € 2.26
+ (€2.26)
- Amount: 0.00001234 BTC (€ 0.23)
+ Amount: 0.00001234 BTC (€0.23)
- € 2.26
+ (€2.26)
- Amount: 0.00001234 BTC (€ 0.23)
+ Amount: 0.00001234 BTC (€0.23)
- Amount: 0.00001234 BTC (€ 0.23)
+ Amount: 0.00001234 BTC (€0.23)
- Amount: 0.00001234 BTC (€ 0.23)
+ Amount: 0.00001234 BTC (€0.23)
- € 2.26
+ (€2.26)
- Amount: 0.00001234 BTC (€ 0.23)
+ Amount: 0.00001234 BTC (€0.23)
@@ -13157,7 +13157,7 @@ exports[`SendScene2 2 spendTargets lock tiles 2`] = `
]
}
>
- € 2.26
+ (€2.26)
@@ -14446,7 +14446,7 @@ exports[`SendScene2 2 spendTargets lock tiles 3`] = `
]
}
>
- Amount: 0.00001234 BTC (€ 0.23)
+ Amount: 0.00001234 BTC (€0.23)
@@ -14786,7 +14786,7 @@ exports[`SendScene2 2 spendTargets lock tiles 3`] = `
]
}
>
- € 2.26
+ (€2.26)
diff --git a/src/__tests__/stakeErrorUtils.test.ts b/src/__tests__/stakeErrorUtils.test.ts
new file mode 100644
index 00000000000..ff520ae1c61
--- /dev/null
+++ b/src/__tests__/stakeErrorUtils.test.ts
@@ -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
+ )
+ })
+})
diff --git a/src/assets/vector/config.json b/src/assets/vector/config.json
index 2cd18808d76..49390301e8b 100644
--- a/src/assets/vector/config.json
+++ b/src/assets/vector/config.json
@@ -7,13 +7,13 @@
"ascent": 850,
"glyphs": [
{
- "uid": "252bd914af239eedd31050c478d0b8ab",
+ "uid": "36680ae6edc65f119cc40e730eb3bdbe",
"css": "sort",
"code": 59401,
"src": "custom_icons",
"selected": true,
"svg": {
- "path": "M181.1 759C190.2 748.6 206 747.5 216.4 756.5L343.6 867.1 474.9 756.3C485.4 747.4 501.2 748.7 510.1 759.3 519 769.8 517.7 785.6 507.1 794.5L359.5 919.1C350.1 927.1 336.3 927 327 918.9L183.6 794.3C173.2 785.2 172.1 769.4 181.1 759ZM345.6 925C331.8 925 320.6 913.8 320.6 900L320.6 299.9C320.6 286 331.8 274.9 345.6 274.9 359.4 274.9 370.6 286 370.6 299.9L370.6 900C370.6 913.8 359.4 925 345.6 925ZM490 241C499 251.4 514.8 252.5 525.2 243.5L652.4 132.9 783.7 243.7C794.3 252.6 810 251.3 818.9 240.7 827.8 230.2 826.5 214.4 815.9 205.5L668.3 80.9C658.9 72.9 645.1 73 635.8 81.1L492.4 205.7C482 214.8 480.9 230.6 490 241ZM654.4 75C640.6 75 629.4 86.2 629.4 100V700.1C629.4 714 640.6 725.1 654.4 725.1 668.2 725.1 679.4 714 679.4 700.1V100C679.4 86.2 668.2 75 654.4 75Z",
+ "path": "M340.9 164.8L341.1 164.8C362.7 164.8 380.2 182.3 380.2 203.9L380.2 806.1C380.2 827.7 362.7 845.2 341.1 845.2L340.9 845.2C319.4 845.2 301.9 827.7 301.9 806.1L301.9 203.9C301.9 182.3 319.4 164.8 340.9 164.8ZM132.3 578.3L132.3 578.3C147.9 564.1 172.2 565.2 186.4 580.9L368.1 781C382.3 796.6 381.1 820.9 365.5 835.1L365.5 835.1C349.8 849.3 325.6 848.1 311.4 832.5L129.7 632.3C115.5 616.7 116.6 592.5 132.3 578.3ZM315.2 835.2L315.2 835.2C299.5 821 298.3 796.8 312.6 781.1L494.3 581C508.5 565.3 532.7 564.2 548.3 578.4L548.3 578.4C564 592.6 565.2 616.8 551 632.5L369.2 832.6C355 848.2 330.8 849.4 315.2 835.2ZM713.4 820L713.3 820C691.7 820 674.2 802.5 674.2 780.9L674.2 178.7C674.2 157.2 691.7 139.7 713.3 139.7L713.4 139.7C735 139.7 752.5 157.2 752.5 178.7L752.5 780.9C752.5 802.5 735 820 713.4 820ZM922 406.4L922 406.4C906.4 420.6 882.1 419.5 867.9 403.8L686.2 203.7C672 188 673.2 163.8 688.8 149.6L688.8 149.6C704.5 135.4 728.7 136.6 742.9 152.2L924.6 352.3C938.8 368 937.7 392.2 922 406.4ZM739.2 149.6L739.2 149.6C754.8 163.8 756 188 741.8 203.7L560.1 403.8C545.9 419.5 521.7 420.6 506 406.4L506 406.4C490.3 392.2 489.2 368 503.4 352.3L685.1 152.2C699.3 136.6 723.5 135.4 739.2 149.6Z",
"width": 1000
},
"search": [
@@ -49,13 +49,13 @@
]
},
{
- "uid": "6b6275743d1b4b31d60ad255b45e44d5",
+ "uid": "6860d518775d7bba51d5ec6e6f788f02",
"css": "buy",
"code": 59393,
"src": "custom_icons",
"selected": true,
"svg": {
- "path": "M663 492Q669 486 677.5 486.5T691.5 492.5 696.5 506.5 690 520L562 641Q557 646 548.5 646T533 639L408 520Q402 515 402 506.5T407.5 492.5 421 486.5 435 492L529 581 530 209Q530 201 535.5 195T549 189 563 195 569 209L568 582ZM50 170Q50 162 56 156T70 150H155Q162 150 167.5 154T174 165L314 776H758L912 304Q914 296 921.5 292.5T936.5 291.5 947.5 301 949 316L791 802Q790 808 784.5 811.5T773 815H299Q292 815 286.5 811T280 800L139 189H70Q62 189 56 183.5T50 170Z",
+ "path": "M123.9 128.7L126.8 128.1C146.2 124 165.2 136.4 169.2 155.7L309.9 822.2C314 841.5 301.6 860.5 282.3 864.6L279.4 865.2C260.1 869.3 241.1 856.9 237 837.6L96.3 171.1C92.2 151.8 104.6 132.8 123.9 128.7ZM803.9 862.3L801.2 861.5C783 856.1 772.6 836.9 778 818.7L926.4 319.4C931.8 301.2 951 290.8 969.2 296.2L971.9 297C990.1 302.4 1000.4 321.6 995 339.8L846.6 839.1C841.2 857.3 822.1 867.7 803.9 862.3ZM547.5 666.3L547.5 666.3C530.1 666.3 515.9 652.1 515.9 634.7L515.9 214.5C515.9 197.1 530.1 183 547.5 183L547.5 183C564.9 183 579.1 197.1 579.1 214.5L579.1 634.7C579.1 652.1 564.9 666.3 547.5 666.3ZM527.9 658.4L527.9 658.4C515 646.8 513.9 626.8 525.5 613.9L634.6 492.2C646.3 479.2 666.2 478.1 679.2 489.8L679.2 489.8C692.2 501.4 693.3 521.3 681.6 534.3L572.5 656C560.9 669 540.9 670.1 527.9 658.4ZM413.6 487.5L413.6 487.5C426.6 475.9 446.5 477 458.2 490L569.8 614.4C581.4 627.4 580.3 647.4 567.4 659L567.4 659C554.4 670.7 534.4 669.6 522.8 656.6L411.2 532.1C399.5 519.1 400.6 499.2 413.6 487.5ZM37.5 127.5L132.8 127.5C151.6 127.5 166.9 142.8 166.9 161.6L166.9 164.2C166.9 183 151.6 198.3 132.8 198.3L37.5 198.3C18.7 198.3 3.4 183 3.4 164.2L3.4 161.6C3.4 142.8 18.7 127.5 37.5 127.5ZM275.8 801.1L811.6 801.1C830.5 801.1 845.9 816.5 845.9 835.5L845.9 838.3C845.9 857.3 830.5 872.7 811.6 872.7L275.8 872.7C256.8 872.7 241.4 857.3 241.4 838.3L241.4 835.5C241.4 816.5 256.8 801.1 275.8 801.1Z",
"width": 1000
},
"search": [
@@ -217,13 +217,13 @@
]
},
{
- "uid": "efd46a92998fd25ea42f02c9f430e29d",
+ "uid": "2867ef0d6d2303a760b4301085bbebea",
"css": "sell",
"code": 59406,
"src": "custom_icons",
"selected": true,
"svg": {
- "path": "M435 344Q429 349 421 349T408 343 403 328 408 315L536 194Q543 189 550 189 560 189 565 196L690 315Q696 321 696 329T691 343 678 349 663 344L570 254 569 627Q569 635 563 640T550 646 536 640 530 627V254ZM50 170Q50 162 56 156T70 150H155Q162 150 168 154T174 165L314 776H758L912 304Q914 296 922 292T937 291 948 301 949 316L791 802Q790 808 785 811T773 815H299Q292 815 287 811T280 800L139 189H70Q62 189 56 183T50 170Z",
+ "path": "M545 183.9L545 183.9C562.4 183.9 576.6 198 576.6 215.5L576.6 635.6C576.6 653.1 562.4 667.2 545 667.2L545 667.2C527.6 667.2 513.4 653.1 513.4 635.6L513.4 215.5C513.4 198 527.6 183.9 545 183.9ZM564.6 191.5L564.6 191.5C577.6 203.1 578.7 223.1 567 236.1L457.9 357.8C446.3 370.7 426.3 371.8 413.4 360.2L413.4 360.2C400.4 348.5 399.3 328.6 410.9 315.6L520 193.9C531.7 180.9 551.6 179.9 564.6 191.5ZM679.2 362.5L679.2 362.5C666.2 374.2 646.2 373.1 634.6 360.1L523 235.6C511.3 222.6 512.4 202.7 525.4 191.1L525.4 191.1C538.4 179.4 558.3 180.5 570 193.5L681.6 318C693.2 330.9 692.1 350.9 679.2 362.5ZM123.9 128.7L126.8 128.1C146.2 124 165.2 136.4 169.2 155.7L309.9 822.2C314 841.5 301.6 860.5 282.3 864.6L279.4 865.2C260.1 869.3 241.1 856.9 237 837.6L96.3 171.1C92.2 151.8 104.6 132.8 123.9 128.7ZM803.9 862.3L801.2 861.5C783 856.1 772.6 836.9 778 818.7L926.4 319.4C931.8 301.2 951 290.8 969.2 296.2L971.9 297C990.1 302.4 1000.4 321.6 995 339.8L846.6 839.1C841.2 857.3 822.1 867.7 803.9 862.3ZM37.5 127.5L132.8 127.5C151.6 127.5 166.9 142.8 166.9 161.6L166.9 164.2C166.9 183 151.6 198.3 132.8 198.3L37.5 198.3C18.7 198.3 3.4 183 3.4 164.2L3.4 161.6C3.4 142.8 18.7 127.5 37.5 127.5ZM275.8 801.1L811.6 801.1C830.5 801.1 845.9 816.5 845.9 835.5L845.9 838.3C845.9 857.3 830.5 872.7 811.6 872.7L275.8 872.7C256.8 872.7 241.4 857.3 241.4 838.3L241.4 835.5C241.4 816.5 256.8 801.1 275.8 801.1Z",
"width": 1000
},
"search": [
@@ -293,13 +293,13 @@
]
},
{
- "uid": "94b028dc195654a277935165d1e95de7",
+ "uid": "c19c509b6a62561f8673aa24e216f22b",
"css": "control-panel-scan-qr",
"code": 59409,
"src": "custom_icons",
"selected": true,
"svg": {
- "path": "M375 125H125V375H208.3V208.3H375V125ZM125 875V625H208.3V791.7H375V875H125ZM625 125V208.3H791.7V375H875V125H625ZM791.7 625H875V875H625V791.7H791.7V625ZM291.7 291.7H458.3V458.3H291.7V291.7ZM291.7 541.7H458.3V708.3H291.7V541.7ZM708.3 291.7H541.7V458.3H708.3V291.7ZM541.7 541.7H708.3V708.3H541.7V541.7Z",
+ "path": "M45.6 194.7C45.6 156.3 45.6 117.7 45.6 79.2C45.6 55.2 55 45.6 78.9 45.6C156.4 45.6 233.9 45.6 311.4 45.6C331.6 45.6 343.1 55.3 343.6 71.6C344.1 88.8 332.2 99.1 311.2 99.1C245.5 99.1 179.7 99.4 113.7 98.9C102.2 98.9 98.7 101.6 98.7 113.6C99.4 180 98.9 246.3 99.1 312.7C99.1 327 93.9 338 80 342.7C68.9 346.3 59.1 343 51.6 333.9C46.2 327.5 45.5 320 45.5 311.9C45.5 272.8 45.5 233.8 45.5 194.7ZM806.9 45.6C845.9 45.6 885 45.6 924.1 45.6C946.9 45.6 956.4 55.5 956.6 78.4C956.6 155.9 956.6 233.4 956.6 310.9C956.6 332 946.4 343.9 929.4 343.6C912.5 343.3 903.1 331.4 903.1 310C903.1 244.7 902.7 179.5 903.4 114.2C903.4 101.4 899.7 98.6 887.5 98.8C821.7 99.4 755.9 99.1 690 98.9C671.7 98.9 660.3 90.2 658.6 75.6C656.6 58.4 668.6 45.6 688.3 45.5C727.8 45.2 767.5 45.5 807 45.5ZM193 956.6C155 956.6 117.2 956.6 79.2 956.6C55.3 956.6 45.8 947 45.6 923.1C45.6 845.6 45.6 768.1 45.6 690.6C45.6 670.5 55.3 658.9 71.7 658.4C88.9 658.1 99.1 670 99.2 690.9C99.2 756.7 99.5 822.5 99.1 888.3C99.1 899.8 101.9 903.3 113.7 903.1C179.1 902.5 244.2 902.8 309.5 902.8C332.2 902.8 344.1 912.5 343.8 930C343.4 946.9 331.7 956.2 310.2 956.2C271.1 956.2 232 956.2 193 956.2ZM807.3 956.6C768.9 956.6 730.3 956.6 691.9 956.6C670.5 956.6 658.6 947 658.4 930.2C658.3 913.1 670.2 903.1 691.3 903.1C756.6 903.1 821.7 902.7 887 903.4C900.8 903.6 903.4 899.2 903.3 886.6C902.7 821.3 903.1 756.1 903 690.8C903 678.8 905.6 668.6 916.4 662C935.3 650.5 956.6 664.5 956.6 689.1C956.9 740.3 956.6 791.7 956.6 843C956.6 870.3 956.7 897.7 956.6 925C956.4 946.6 946.4 956.4 924.5 956.6C885.5 956.7 846.4 956.6 807.3 956.6ZM240 357C240 326.9 240 296.6 240 266.4C240 246.9 246.7 240.2 265.9 240.2C326.9 240.2 387.7 240.2 448.6 240.2C466.7 240.2 474.2 247.5 474.4 265.6C474.5 326.4 474.5 387.3 474.4 448.1C474.4 466.1 466.6 474.1 448.9 474.2C387.7 474.4 326.3 474.4 265 474.2C247.8 474.2 240.5 466.4 240.3 449.1C240.2 418.4 240.3 387.8 240.3 357.2ZM762.2 356.6C762.2 386.7 762.2 417 762.2 447.2C762.2 467 755 474.2 735.3 474.2C675.3 474.2 615.3 474.2 555.3 474.2C535.6 474.2 528 466.7 528 447.3C528 386.9 528 326.6 528 266.1C528 247.2 535.3 240.2 554.5 240.2C615 240.2 675.3 240.2 735.8 240.2C755.6 240.2 762 246.9 762.2 267.2C762.2 297 762.2 326.7 762.2 356.6ZM357 762C326.9 762 296.6 762 266.4 762C247 762 240 755.3 240 736.3C240 675.5 240 614.5 240 553.8C240 535.6 247.5 528.1 265.5 528C326.4 527.8 387.2 527.8 448.1 528C466.7 528 474.2 535.8 474.2 554.8C474.2 615.3 474.2 675.6 474.2 736.1C474.2 755 466.9 762 447.7 762C417.5 762 387.2 762 357 762ZM762.2 644.4C762.2 674.5 762.2 704.8 762.2 735C762.2 755.3 755.6 762 735.8 762C675.3 762 615 762 554.5 762C535.5 762 528.1 754.8 528.1 735.9C528.1 675.5 528.1 615.2 528.1 554.7C528.1 535.3 535.6 528 555.5 527.8C615.5 527.8 675.5 527.8 735.5 527.8C755.2 527.8 762.2 535.2 762.3 555C762.3 584.8 762.3 614.5 762.3 644.4ZM432.2 358.3C432.2 379.7 431.9 401.1 432.2 422.5C432.2 429.8 430 432.3 422.5 432.3C378.8 432 335 432 291.3 432.3C283.4 432.3 281.6 429.4 281.7 422.2C281.9 378.9 282 335.6 281.7 292.3C281.7 284.5 284.1 281.7 292.2 281.7C335.5 282 378.8 282 422 281.7C430.3 281.7 432.5 284.7 432.3 292.5C431.9 314.4 432.3 336.3 432.3 358.1ZM720.3 358C720.3 378.9 720 400 720.3 420.9C720.5 429.1 718.4 432.5 709.4 432.5C666.6 432 623.6 432 580.8 432.5C572.2 432.5 569.5 430 569.7 421.4C570 378.6 570 335.6 569.7 292.8C569.7 285.2 571.6 281.9 579.8 282C623.1 282.3 666.4 282.3 709.7 282C717.7 282 720.3 284.5 720.2 292.5C719.7 314.4 720.2 336.3 720.2 358.1ZM281.9 644.4C281.9 623.4 282.2 602.3 281.9 581.4C281.9 573.4 283.4 569.7 292.7 569.7C335.9 570.2 379.2 570 422.5 569.7C429.7 569.7 432.5 571.7 432.5 579.4C432.2 623.1 432.2 666.9 432.5 710.6C432.5 718.3 429.5 720.3 422.5 720.2C379.2 720 335.9 719.8 292.7 720.2C283.3 720.2 281.9 716.2 282 708.4C282.3 687 282 665.6 282 644.2ZM720.3 646.1C720.3 667.5 720.2 688.9 720.3 710.3C720.3 717.5 718.1 720.3 710.6 720.2C666.9 719.8 623.1 719.8 579.4 720.2C571.7 720.2 569.7 717.3 569.7 710.2C570 666.4 569.8 622.7 569.7 578.9C569.7 572.2 571.9 569.7 578.8 569.7C623 569.8 667.2 570 711.3 569.7C719.7 569.7 720.2 574.1 720.2 580.5C720.2 602.3 720.2 624.2 720.2 646.1Z",
"width": 1000
},
"search": [
@@ -419,13 +419,13 @@
]
},
{
- "uid": "3fc8e0aa838bb20075b9cd7eff0b5450",
+ "uid": "b51bd7bd0303b8a3f24c02a8ec32cf11",
"css": "control-panel-fio-names",
"code": 59414,
"src": "custom_icons",
"selected": true,
"svg": {
- "path": "M890.6 187.5H320.3C316 187.5 312.5 191 312.5 195.3V250C312.5 254.3 316 257.8 320.3 257.8H890.6C894.9 257.8 898.4 254.3 898.4 250V195.3C898.4 191 894.9 187.5 890.6 187.5ZM890.6 464.8H320.3C316 464.8 312.5 468.4 312.5 472.7V527.3C312.5 531.6 316 535.2 320.3 535.2H890.6C894.9 535.2 898.4 531.6 898.4 527.3V472.7C898.4 468.4 894.9 464.8 890.6 464.8ZM890.6 742.2H320.3C316 742.2 312.5 745.7 312.5 750V804.7C312.5 809 316 812.5 320.3 812.5H890.6C894.9 812.5 898.4 809 898.4 804.7V750C898.4 745.7 894.9 742.2 890.6 742.2ZM101.6 222.7C101.6 229.8 103 236.9 105.7 243.6 108.5 250.2 112.5 256.2 117.6 261.3 122.7 266.4 128.7 270.4 135.3 273.2 142 275.9 149.1 277.3 156.3 277.3 163.4 277.3 170.5 275.9 177.2 273.2 183.8 270.4 189.8 266.4 194.9 261.3 200 256.2 204 250.2 206.8 243.6 209.5 236.9 210.9 229.8 210.9 222.7 210.9 215.5 209.5 208.4 206.8 201.7 204 195.1 200 189.1 194.9 184 189.8 178.9 183.8 174.9 177.2 172.1 170.5 169.4 163.4 168 156.3 168 149.1 168 142 169.4 135.3 172.1 128.7 174.9 122.7 178.9 117.6 184 112.5 189.1 108.5 195.1 105.7 201.7 103 208.4 101.6 215.5 101.6 222.7ZM101.6 500C101.6 507.2 103 514.3 105.7 520.9 108.5 527.6 112.5 533.6 117.6 538.7 122.7 543.8 128.7 547.8 135.3 550.5 142 553.3 149.1 554.7 156.3 554.7 163.4 554.7 170.5 553.3 177.2 550.5 183.8 547.8 189.8 543.8 194.9 538.7 200 533.6 204 527.6 206.8 520.9 209.5 514.3 210.9 507.2 210.9 500 210.9 492.8 209.5 485.7 206.8 479.1 204 472.4 200 466.4 194.9 461.3 189.8 456.2 183.8 452.2 177.2 449.5 170.5 446.7 163.4 445.3 156.3 445.3 149.1 445.3 142 446.7 135.3 449.5 128.7 452.2 122.7 456.2 117.6 461.3 112.5 466.4 108.5 472.4 105.7 479.1 103 485.7 101.6 492.8 101.6 500ZM101.6 777.3C101.6 784.5 103 791.6 105.7 798.3 108.5 804.9 112.5 810.9 117.6 816 122.7 821.1 128.7 825.1 135.3 827.9 142 830.6 149.1 832 156.3 832 163.4 832 170.5 830.6 177.2 827.9 183.8 825.1 189.8 821.1 194.9 816 200 810.9 204 804.9 206.8 798.3 209.5 791.6 210.9 784.5 210.9 777.3 210.9 770.2 209.5 763 206.8 756.4 204 749.8 200 743.8 194.9 738.7 189.8 733.6 183.8 729.6 177.2 726.8 170.5 724.1 163.4 722.7 156.3 722.7 149.1 722.7 142 724.1 135.3 726.8 128.7 729.6 122.7 733.6 117.6 738.7 112.5 743.8 108.5 749.8 105.7 756.4 103 763 101.6 770.2 101.6 777.3Z",
+ "path": "M26.6 158.1C28.7 149.5 31.1 141.1 36.1 133.4C49.8 112 76.3 101.4 101.1 107.5C125.5 113.4 143.9 135 146.3 159.8C149.2 192.3 127 221.2 95.3 225.8C63.3 230.3 34.1 209.5 27.5 177.5C27.3 176.7 27.7 175.6 26.4 175.2L26.4 158.3ZM26.1 487.2C28.4 482 29.2 476.3 31.7 471.1C43.9 445.9 70.2 432.5 97.8 437.5C123.4 442.2 143.3 464.4 145.9 491.3C149.1 523.3 125.3 552.3 93.3 556.1C60.9 559.8 31.1 537.2 26.9 505C26.9 504.4 26.4 503.9 26.1 503.3C26.1 498 26.1 492.7 26.1 487.3ZM26.6 826.4C28.7 817.8 31.1 809.4 36.1 801.7C49.8 780.3 76.3 769.7 101.3 775.8C125.6 781.7 144.1 803.1 146.4 828.1C149.4 859.7 127.7 888.6 97.3 893.1C64.1 898.1 34.4 878.1 27.7 846.3C27.3 844.7 28 842.8 26.6 841.4L26.6 826.4ZM621.9 205.3C518.4 205.3 415.2 205.3 311.7 205.3C285.6 205.3 265.8 188.4 265.5 166.1C265.3 144.5 285.6 127.2 311.4 127C379.1 127 446.7 127 514.4 127C653.1 127 791.7 127 930.5 127C953.6 127 971.3 138.4 976.7 156.6C984.1 181.4 962.7 205.3 933.1 205.3C829.4 205.3 725.6 205.3 621.9 205.3ZM621.9 535.5C518.4 535.5 415.2 535.5 311.7 535.5C285.6 535.5 265.8 518.6 265.5 496.3C265.3 474.7 285.6 457.3 311.4 457.2C379.1 457.2 446.7 457.2 514.4 457.2C653.1 457.2 791.7 457.2 930.5 457.2C953.6 457.2 971.3 468.6 976.7 486.7C984.1 511.6 962.7 535.5 933.1 535.5C829.4 535.5 725.6 535.5 621.9 535.5ZM621.9 873.3C518.4 873.3 415.2 873.3 311.7 873.3C285.6 873.3 265.8 856.4 265.5 834.1C265.3 812.5 285.6 795.2 311.4 795C379.1 795 446.7 795 514.4 795C653.1 795 791.7 795 930.5 795C953.6 795 971.3 806.4 976.7 824.5C984.1 849.4 962.7 873.3 933.1 873.3C829.4 873.3 725.6 873.3 621.9 873.3Z",
"width": 1000
},
"search": [
@@ -521,6 +521,20 @@
"css": "doc-text",
"code": 61686,
"src": "fontawesome"
+ },
+ {
+ "uid": "86ba526dcd6c786ce97288cf10a9d37a",
+ "css": "chart",
+ "code": 59399,
+ "src": "custom_icons",
+ "selected": true,
+ "svg": {
+ "path": "M670.9 644.1C652 648.6 634.4 644.4 617.3 636.3C582.8 619.8 548 603.6 513.4 587.2C479.1 571.1 444.7 555 410.3 538.8C406.7 537 405 537.3 403 540.8C384.7 571.6 366.4 602.2 348 632.8C336.6 651.9 324.8 670.9 313.4 690.2C293.3 723.6 273.1 757 253.1 790.6C244.5 805 236.6 819.7 226.9 833.3C215 850.2 187.3 852 171.7 837C157.8 823.6 155.9 805.2 166.6 787.3C180.6 764.1 194.7 740.8 208.6 717.5C224.5 691.1 240.3 664.5 256.1 638.1C272.3 611.1 288.6 584.1 304.7 556.9C317.7 535.2 329.8 513.1 343.9 492C361.1 466.1 397.5 453.9 427 463.8C445.9 470 463.3 479.8 481.2 488.3C523.6 508 565.8 528 608 547.8C621.9 554.4 635.8 560.6 649.5 567.3C654.1 569.5 656.2 568.9 659.1 564.5C727.3 459.8 795.9 355.5 864.1 250.6C874.7 234.4 893.9 227.7 910.2 232.5C930.9 238.8 942.3 265.9 932 285.2C929.1 290.9 925.5 296.3 921.9 301.6C855.3 403.3 788.9 504.8 722.3 606.6C712.3 622 699.8 634.4 682.2 640.9C678.4 642.3 674.4 643 670.6 643.9ZM985.3 940.5L985.3 940.6C985.3 960.1 969.5 975.9 950 975.9L69.8 975.9C50.3 975.9 34.5 960.1 34.5 940.6L34.5 940.5C34.5 921 50.3 905.2 69.8 905.2L950 905.2C969.5 905.2 985.3 921 985.3 940.5ZM69.5 24.2L69.7 24.2C89.2 24.2 105 40 105 59.5L105 939.7C105 959.2 89.2 975 69.7 975L69.5 975C50 975 34.2 959.2 34.2 939.7L34.2 59.5C34.2 40 50 24.2 69.5 24.2Z",
+ "width": 1000
+ },
+ "search": [
+ "chart"
+ ]
}
]
}
\ No newline at end of file
diff --git a/src/components/modals/HelpModal.tsx b/src/components/modals/HelpModal.tsx
index c46b405b897..5a2719c2936 100644
--- a/src/components/modals/HelpModal.tsx
+++ b/src/components/modals/HelpModal.tsx
@@ -11,6 +11,7 @@ import { lstrings } from '../../locales/strings'
import { config } from '../../theme/appConfig'
import { useSelector } from '../../types/reactRedux'
import type { NavigationBase } from '../../types/routerTypes'
+import { isMaestro } from '../../util/maestro'
import { openBrowserUri } from '../../util/WebUtils'
import { ChatBubblesIcon } from '../icons/ThemedIcons'
import { Airship } from '../services/AirshipInstance'
@@ -60,7 +61,9 @@ export const HelpModal: React.FC = (props: Props) => {
}, [])
const styles = getStyles(theme)
- const versionText = `${lstrings.help_version} ${versionNumber}`
+ const versionText = `${lstrings.help_version} ${versionNumber}${
+ isMaestro() ? '-m' : ''
+ }`
const buildText = `${lstrings.help_build} ${buildNumber}`
const helpModalTitle = sprintf(
lstrings.help_modal_title_thanks,
diff --git a/src/components/modals/TextInputModal.tsx b/src/components/modals/TextInputModal.tsx
index 30bcf0840d7..a8660007aee 100644
--- a/src/components/modals/TextInputModal.tsx
+++ b/src/components/modals/TextInputModal.tsx
@@ -4,14 +4,13 @@
*/
import * as React from 'react'
-import { Platform, View } from 'react-native'
+import { Platform, StyleSheet, View } from 'react-native'
import type { AirshipBridge } from 'react-native-airship'
import { lstrings } from '../../locales/strings'
import { ModalButtons } from '../buttons/ModalButtons'
-import { styled } from '../hoc/styled'
+import { AlertCardUi4 } from '../cards/AlertCard'
import { showError } from '../services/AirshipInstance'
-import { Alert } from '../themed/Alert'
import { Paragraph } from '../themed/EdgeText'
import {
type FilledTextInputReturnKeyType,
@@ -57,7 +56,7 @@ interface Props {
secureTextEntry?: boolean
}
-export function TextInputModal(props: Props) {
+export const TextInputModal: React.FC = props => {
const {
autoCapitalize,
autoFocus = true,
@@ -82,12 +81,12 @@ export function TextInputModal(props: Props) {
const [errorMessage, setErrorMessage] = React.useState()
const [text, setText] = React.useState(initialValue)
- const handleChangeText = (text: string) => {
+ const handleChangeText = (text: string): void => {
setText(text)
setErrorMessage(undefined)
}
- const handleSubmit = () => {
+ const handleSubmit = (): void => {
if (onSubmit == null) {
bridge.resolve(text)
return
@@ -100,7 +99,7 @@ export function TextInputModal(props: Props) {
bridge.resolve(text)
}
},
- error => {
+ (error: unknown) => {
showError(error)
}
)
@@ -115,19 +114,20 @@ export function TextInputModal(props: Props) {
bridge.resolve(undefined)
}}
>
-
+
{typeof message === 'string' ? (
{message}
) : (
<>{message}>
)}
{warningMessage != null ? (
-
) : null}
-
+
)
}
-const StyledInnerView = styled(View)<{ fullHeight: boolean }>(() => props => ({
- flexShrink: 1,
- flexGrow: props.fullHeight ? 1 : undefined
-}))
+const styles = StyleSheet.create({
+ innerView: {
+ flexShrink: 1
+ },
+ fullHeight: {
+ flexGrow: 1
+ }
+})
diff --git a/src/components/navigation/AlertDropdown.tsx b/src/components/navigation/AlertDropdown.tsx
index b6418f54227..1cf0078b9fd 100644
--- a/src/components/navigation/AlertDropdown.tsx
+++ b/src/components/navigation/AlertDropdown.tsx
@@ -36,7 +36,7 @@ interface Props {
onPress?: () => void | Promise
}
-export function AlertDropdown(props: Props) {
+export const AlertDropdown: React.FC = props => {
const {
bridge,
error,
@@ -95,7 +95,7 @@ export function AlertDropdown(props: Props) {
{message}
-
+
void | Promise
}
-export const NotificationCenterRow = (props: Props) => {
+export const NotificationCenterRow: React.FC = props => {
const theme = useTheme()
const styles = getStyles(theme)
@@ -71,7 +71,7 @@ interface NotificationCenterCardProps {
onClose?: () => void | Promise
}
-const NotificationCenterCard = (props: NotificationCenterCardProps) => {
+const NotificationCenterCard: React.FC = props => {
const theme = useTheme()
const styles = getStyles(theme)
@@ -98,15 +98,24 @@ const NotificationCenterCard = (props: NotificationCenterCardProps) => {
-
+ {/* Font scaling is disabled so every card renders at the same text
+ size. Long titles and messages wrap up to their line limit and
+ then truncate with an ellipsis instead of shrinking. */}
+
{title}
- {toLocaleTime(date)}
+
+ {toLocaleTime(date)}
+
{message}
@@ -114,11 +123,7 @@ const NotificationCenterCard = (props: NotificationCenterCardProps) => {
{onClose == null ? null : (
-
+
)}
diff --git a/src/components/scenes/CreateWalletSelectCryptoScene.tsx b/src/components/scenes/CreateWalletSelectCryptoScene.tsx
index 1f9b1918acb..e2ae42d404a 100644
--- a/src/components/scenes/CreateWalletSelectCryptoScene.tsx
+++ b/src/components/scenes/CreateWalletSelectCryptoScene.tsx
@@ -22,10 +22,8 @@ import {
import { useDispatch, useSelector } from '../../types/reactRedux'
import type { EdgeAppSceneProps, NavigationBase } from '../../types/routerTypes'
import type { EdgeAsset } from '../../types/types'
-import { isMaestro } from '../../util/maestro'
import { logEvent } from '../../util/tracking'
import { EdgeButton } from '../buttons/EdgeButton'
-import { SceneButtons } from '../buttons/SceneButtons'
import { EdgeAnim } from '../common/EdgeAnim'
import { SceneWrapper } from '../common/SceneWrapper'
import { SearchIconAnimated } from '../icons/ThemedIcons'
@@ -137,7 +135,6 @@ const CreateWalletSelectCryptoComponent: React.FC = (props: Props) => {
}
return out
})
-
const findMainnetItem = (pluginId: string): MainWalletCreateItem => {
const newItem = createWalletList.find(item => item.pluginId === pluginId)
return newItem as MainWalletCreateItem
@@ -423,13 +420,12 @@ const CreateWalletSelectCryptoComponent: React.FC = (props: Props) => {
exit={{ type: 'fadeOut', duration: 300 }}
accessible={false}
>
-
)
@@ -465,7 +461,6 @@ const CreateWalletSelectCryptoComponent: React.FC = (props: Props) => {
contentContainerStyle={{
...insetStyle,
paddingTop: 0,
- paddingBottom: insetStyle.paddingBottom + theme.rem(5),
marginHorizontal: theme.rem(0.5)
}}
data={filteredCreateWalletList}
diff --git a/src/components/scenes/GettingStartedScene.tsx b/src/components/scenes/GettingStartedScene.tsx
index 3083e8f27f3..642fda3df1b 100644
--- a/src/components/scenes/GettingStartedScene.tsx
+++ b/src/components/scenes/GettingStartedScene.tsx
@@ -533,7 +533,7 @@ const getStyles = cacheStyles((theme: Theme) => ({
alignItems: 'center'
},
tertiaryText: {
- color: theme.textInputTextColorDisabled
+ color: theme.iconTappable
},
tappableText: {
color: theme.iconTappable
diff --git a/src/components/scenes/PrivacySettingsScene.tsx b/src/components/scenes/PrivacySettingsScene.tsx
index 49576dcf008..b435bba57bf 100644
--- a/src/components/scenes/PrivacySettingsScene.tsx
+++ b/src/components/scenes/PrivacySettingsScene.tsx
@@ -34,15 +34,21 @@ export const PrivacySettingsScene: React.FC = props => {
const account = useSelector(state => state.core.account)
const { currencyConfig } = account
- // Get list of pluginIds that support network privacy
+ // Get list of pluginIds that support network privacy, sorted
+ // alphabetically by display name
const supportedPluginIds = React.useMemo(() => {
- return Object.keys(currencyConfig).filter(pluginId => {
+ const pluginIds = Object.keys(currencyConfig).filter(pluginId => {
const config = currencyConfig[pluginId]
const defaultSetting = asMaybePrivateNetworkingSetting(
config.currencyInfo.defaultSettings
)
return defaultSetting != null
})
+ return pluginIds.sort((a, b) =>
+ currencyConfig[a].currencyInfo.displayName.localeCompare(
+ currencyConfig[b].currencyInfo.displayName
+ )
+ )
}, [currencyConfig])
return (
diff --git a/src/components/scenes/Staking/StakeModifyScene.tsx b/src/components/scenes/Staking/StakeModifyScene.tsx
index 092c7760b95..99f7f1775f1 100644
--- a/src/components/scenes/Staking/StakeModifyScene.tsx
+++ b/src/components/scenes/Staking/StakeModifyScene.tsx
@@ -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,
@@ -79,6 +80,7 @@ const StakeModifySceneComponent: React.FC = props => {
const { modification, title, stakePlugin, stakePolicy } = route.params
const dispatch = useDispatch()
const {
+ claimLanguage,
stakePolicyId,
stakeWarning,
unstakeWarning,
@@ -227,15 +229,26 @@ const StakeModifySceneComponent: React.FC = 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(() => {
@@ -284,7 +297,8 @@ const StakeModifySceneComponent: React.FC = props => {
const message = {
stake: lstrings.stake_change_stake_success,
unstake: lstrings.stake_change_unstake_success,
- claim: lstrings.stake_change_claim_success,
+ claim:
+ claimLanguage?.successMessage ?? lstrings.stake_change_claim_success,
unstakeExact: ''
}
@@ -329,8 +343,9 @@ const StakeModifySceneComponent: React.FC = 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)
@@ -479,7 +494,8 @@ const StakeModifySceneComponent: React.FC = props => {
allocationType === 'stake'
? sprintf(lstrings.stake_amount_s_stake, quoteCurrencyCode)
: isClaim
- ? sprintf(lstrings.stake_amount_claim, quoteCurrencyCode)
+ ? claimLanguage?.amountLabel ??
+ sprintf(lstrings.stake_amount_claim, quoteCurrencyCode)
: sprintf(lstrings.stake_amount_s_unstake, quoteCurrencyCode)
const nativeAmount = zeroString(quoteAllocation?.nativeAmount)
diff --git a/src/components/scenes/Staking/StakeOverviewScene.tsx b/src/components/scenes/Staking/StakeOverviewScene.tsx
index e9a290d74f6..fd8777dc602 100644
--- a/src/components/scenes/Staking/StakeOverviewScene.tsx
+++ b/src/components/scenes/Staking/StakeOverviewScene.tsx
@@ -109,6 +109,10 @@ const StakeOverviewSceneComponent: React.FC = props => {
? { stakeAssetUris: [], rewardAssetUris: [] }
: getPolicyIconUris(account.currencyConfig, stakePolicy)
+ // A policy may replace the default reward wording of the claim action
+ const claimLabel =
+ stakePolicy?.claimLanguage?.actionLabel ?? lstrings.stake_claim_rewards
+
// Hooks
const [stakeAllocations, setStakeAllocations] = React.useState<
@@ -177,7 +181,7 @@ const StakeOverviewSceneComponent: React.FC = props => {
if (stakePolicy == null) return
const sceneTitleMap = {
stake: getPolicyTitleName(stakePolicy, countryCode),
- claim: lstrings.stake_claim_rewards,
+ claim: claimLabel,
unstake: lstrings.stake_unstake,
unstakeAndClaim: lstrings.stake_unstake_claim,
unstakeExact: '' // Only for internal use
@@ -211,16 +215,14 @@ const StakeOverviewSceneComponent: React.FC = props => {
}): React.ReactElement => {
const { allocationType, currencyCode, nativeAmount, pluginId, tokenId } =
item
+ const claimablePolicyLabel = stakePolicy?.claimLanguage?.positionLabel
const titleBase =
allocationType === 'staked'
- ? lstrings.stake_s_staked
+ ? sprintf(lstrings.stake_s_staked, currencyCode)
: allocationType === 'earned'
- ? lstrings.stake_s_earned
- : lstrings.stake_s_unstaked
- const title = `${sprintf(
- titleBase,
- currencyCode
- )}${getAllocationLocktimeMessage(item)}`
+ ? claimablePolicyLabel ?? sprintf(lstrings.stake_s_earned, currencyCode)
+ : sprintf(lstrings.stake_s_unstaked, currencyCode)
+ const title = `${titleBase}${getAllocationLocktimeMessage(item)}`
const denomination = displayDenomMap[currencyCode]
// This is not the wallet we are staking from, but the asset being staked.
@@ -312,7 +314,7 @@ const StakeOverviewSceneComponent: React.FC = props => {
stakePolicy.hideClaimAction === true
? undefined
: {
- label: lstrings.stake_claim_rewards,
+ label: claimLabel,
disabled: !canClaim,
onPress: handleModifyPress('claim')
}
diff --git a/src/components/themed/SideMenu.tsx b/src/components/themed/SideMenu.tsx
index 80ead5cbdcd..0dece84236f 100644
--- a/src/components/themed/SideMenu.tsx
+++ b/src/components/themed/SideMenu.tsx
@@ -312,7 +312,7 @@ export function SideMenuComponent(props: Props): React.ReactElement {
handlePress: () => {
handleMarketsPress()
},
- iconNameFontAwesome: 'chart-line',
+ iconName: 'chart',
title: lstrings.title_markets
},
// Only show gift card menu option if Phaze API key is configured
diff --git a/src/components/themed/TransactionListTop.tsx b/src/components/themed/TransactionListTop.tsx
index d8332b3b2cf..96b08f93a0f 100644
--- a/src/components/themed/TransactionListTop.tsx
+++ b/src/components/themed/TransactionListTop.tsx
@@ -41,6 +41,7 @@ import type {
WalletsTabSceneProps
} from '../../types/routerTypes'
import { CryptoAmount } from '../../util/CryptoAmount'
+import { getCryptoText } from '../../util/cryptoTextUtils'
import { isKeysOnlyPlugin } from '../../util/CurrencyInfoHelpers'
import { triggerHaptic } from '../../util/haptic'
import {
@@ -55,6 +56,7 @@ import { getUkCompliantString } from '../../util/ukComplianceUtils'
import {
convertNativeToDenomination,
DECIMAL_PRECISION,
+ getDenomFromIsoCode,
removeIsoPrefix,
zeroString
} from '../../util/utils'
@@ -587,12 +589,16 @@ export const TransactionListTop: React.FC = props => {
const nativeLocked = add(fioStatus.locked, lockedNativeAmount)
if (nativeLocked === '0') return null
- const stakingCryptoAmount = convertNativeToDenomination(
- displayDenomination.multiplier
- )(nativeLocked)
- const stakingCryptoAmountFormat = formatNumber(
- add(stakingCryptoAmount, '0')
- )
+ // Truncate to an exchange-rate-appropriate number of decimals, so the
+ // whole "locked" message stays readable on narrow screens:
+ const stakingCryptoText = getCryptoText({
+ currencyCode: displayDenomination.name,
+ displayDenomination,
+ exchangeDenomination,
+ exchangeRate,
+ fiatDenomination: getDenomFromIsoCode(defaultIsoFiat),
+ nativeAmount: nativeLocked
+ })
const stakingExchangeAmount = convertNativeToDenomination(
exchangeDenomination.multiplier
@@ -608,7 +614,7 @@ export const TransactionListTop: React.FC = props => {
{sprintf(
lstrings.staking_status,
- stakingCryptoAmountFormat + ' ' + displayDenomination.name,
+ stakingCryptoText,
fiatSymbol + stakingFiatBalanceFormat + ' ' + defaultFiat
)}
@@ -909,15 +915,12 @@ const getStyles = cacheStyles((theme: Theme) => ({
// Staking Box
stakingBoxContainer: {
height: theme.rem(1.25),
- minWidth: theme.rem(18),
- maxWidth: '70%',
alignItems: 'center',
- flexDirection: 'row',
- justifyContent: 'space-between'
+ flexDirection: 'row'
},
stakingStatusText: {
+ flexShrink: 1,
color: theme.secondaryText,
- maxWidth: '70%',
fontSize: theme.rem(1)
}
}))
diff --git a/src/components/themed/WalletListCurrencyRow.tsx b/src/components/themed/WalletListCurrencyRow.tsx
index e7f1c9abeb3..1c365e221ff 100644
--- a/src/components/themed/WalletListCurrencyRow.tsx
+++ b/src/components/themed/WalletListCurrencyRow.tsx
@@ -38,9 +38,7 @@ interface Props {
) => Promise | void
}
-const WalletListCurrencyRowComponent = (
- props: Props
-): React.ReactElement | null => {
+const WalletListCurrencyRowComponent: React.FC = props => {
const {
customAsset,
token,
@@ -211,6 +209,7 @@ const WalletListCurrencyRowComponent = (
tokenId={tokenId}
currencyConfig={wallet.currencyConfig}
hideBalance={hideBalance}
+ autoPrecision={false}
style={styles.secondaryText}
/>
diff --git a/src/components/tiles/EditableAmountTile.tsx b/src/components/tiles/EditableAmountTile.tsx
index fc45bba2bfe..8f02c132061 100644
--- a/src/components/tiles/EditableAmountTile.tsx
+++ b/src/components/tiles/EditableAmountTile.tsx
@@ -29,7 +29,7 @@ interface Props {
onPress: () => void
}
-export const EditableAmountTile = (props: Props) => {
+export const EditableAmountTile: React.FC = props => {
let cryptoAmountSyntax
let cryptoAmountStyle
let fiatAmountSyntax
@@ -74,8 +74,8 @@ export const EditableAmountTile = (props: Props) => {
exchangeAmount
)
cryptoAmountSyntax = `${displayAmount ?? '0'} ${displayDenomination.name}`
- if (fiatAmount) {
- fiatAmountSyntax = `${fiatSymbol} ${
+ if (fiatAmount !== '') {
+ fiatAmountSyntax = `${fiatSymbol}${
toFixed(round(fiatAmount, -2), 2, 2) ?? '0'
}`
}
@@ -119,7 +119,7 @@ export const EditableAmountTile = (props: Props) => {
{cryptoAmountSyntax}
{fiatAmountSyntax == null ? null : (
- {fiatAmountSyntax}
+ {`(${fiatAmountSyntax})`}
)}
diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts
index a6006046045..1c1c8b67e5e 100644
--- a/src/locales/en_US.ts
+++ b/src/locales/en_US.ts
@@ -229,7 +229,7 @@ const strings = {
fragment_error_report_id_copied: 'Error report ID copied',
request_minimum_notification_title: 'Minimum Balance Required',
request_xrp_minimum_notification_body_1xrp:
- 'Ripple (XRP) wallets require a 1 XRP minimum balance. You must deposit at least 1 XRP to this address before this wallet will show a balance or transactions. 1 XRP will be unspendable for the lifetime of this wallet address.',
+ 'A minimum balance of 1 XRP is required for an address on the XRP Ledger to be active. The 1 XRP is an unspendable reserve on the network. The reserve is met as soon as the address balance reaches 1 XRP or more. Any amount above this reserve is available for transactions.',
request_xrp_minimum_notification_alert_body_1xrp:
'This wallet will always require a 1 XRP minimum',
request_xlm_minimum_notification_body:
@@ -1733,6 +1733,10 @@ const strings = {
stake_change_stake_success: 'Funds successfully staked',
stake_change_unstake_success: 'Funds successfully unstaked',
stake_change_claim_success: 'Claim transactions sent successfully',
+ stake_reclaim_1s: 'Reclaim %1$s',
+ stake_amount_reclaim_1s: 'Amount of %1$s to Reclaim',
+ stake_reclaimable_1s: '%1$s Pending Reclaim',
+ stake_change_reclaim_success: 'Reclaim transaction sent successfully',
stake_disabled_slider: 'Enter Amount',
stake_warning_multiple_transactions:
'Staking requires multiple transactions to confirm and may take 20 seconds or more to complete',
@@ -1745,6 +1749,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:
diff --git a/src/locales/strings/enUS.json b/src/locales/strings/enUS.json
index 39c1368be78..685fd7577ea 100644
--- a/src/locales/strings/enUS.json
+++ b/src/locales/strings/enUS.json
@@ -141,7 +141,7 @@
"fragment_event_id": "Event ID",
"fragment_error_report_id_copied": "Error report ID copied",
"request_minimum_notification_title": "Minimum Balance Required",
- "request_xrp_minimum_notification_body_1xrp": "Ripple (XRP) wallets require a 1 XRP minimum balance. You must deposit at least 1 XRP to this address before this wallet will show a balance or transactions. 1 XRP will be unspendable for the lifetime of this wallet address.",
+ "request_xrp_minimum_notification_body_1xrp": "A minimum balance of 1 XRP is required for an address on the XRP Ledger to be active. The 1 XRP is an unspendable reserve on the network. The reserve is met as soon as the address balance reaches 1 XRP or more. Any amount above this reserve is available for transactions.",
"request_xrp_minimum_notification_alert_body_1xrp": "This wallet will always require a 1 XRP minimum",
"request_xlm_minimum_notification_body": "Stellar (XLM) wallets require a 1 XLM minimum balance. You must deposit at least 1 XLM to this address before this wallet will show a balance or transactions. 1 XLM will be unspendable for the lifetime of this wallet address.",
"request_xlm_minimum_notification_alert_body": "This wallet will always require a 1 XLM minimum",
@@ -1360,6 +1360,10 @@
"stake_change_stake_success": "Funds successfully staked",
"stake_change_unstake_success": "Funds successfully unstaked",
"stake_change_claim_success": "Claim transactions sent successfully",
+ "stake_reclaim_1s": "Reclaim %1$s",
+ "stake_amount_reclaim_1s": "Amount of %1$s to Reclaim",
+ "stake_reclaimable_1s": "%1$s Pending Reclaim",
+ "stake_change_reclaim_success": "Reclaim transaction sent successfully",
"stake_disabled_slider": "Enter Amount",
"stake_warning_multiple_transactions": "Staking requires multiple transactions to confirm and may take 20 seconds or more to complete",
"stake_warning_stake": "Staking funds will block you from claiming rewards for 16 hours and withdrawing your staked funds for 36 hours.\n\nEvery interaction (stake, unstake, claim rewards) will reset both timers.",
@@ -1368,6 +1372,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.",
diff --git a/src/plugins/stake-plugins/currency/tronStakePlugin.ts b/src/plugins/stake-plugins/currency/tronStakePlugin.ts
index aa0e0e303b2..7f34767450f 100644
--- a/src/plugins/stake-plugins/currency/tronStakePlugin.ts
+++ b/src/plugins/stake-plugins/currency/tronStakePlugin.ts
@@ -1,6 +1,7 @@
import { add, gt, lt } from 'biggystring'
import { asDate, asMaybe, asObject, asString } from 'cleaners'
import type { EdgeSpendInfo, EdgeTransaction } from 'edge-core-js'
+import { sprintf } from 'sprintf-js'
import { lstrings } from '../../../locales/strings'
import {
@@ -10,6 +11,7 @@ import {
type PositionAllocation,
type QuoteAllocation,
StakeBelowLimitError,
+ type StakeClaimLanguage,
type StakePlugin,
type StakePolicy,
type StakePolicyFilter,
@@ -19,6 +21,7 @@ import {
} from '../types'
const MIN_TRX_STAKE = '1000000' // 1 TRX
+const TRX_CURRENCY_CODE = 'TRX'
const WITHDRAW_PREFIX = 'WITHDRAWEXPIREUNFREEZE_'
const stakeProviderInfo: StakeProviderInfo = {
@@ -27,8 +30,20 @@ const stakeProviderInfo: StakeProviderInfo = {
stakeProviderId: 'tronResources'
}
+// Freezing TRX buys bandwidth and energy, so there is no yield and no reward to
+// claim. The claim action runs WithdrawExpireUnfreeze, which moves the user's
+// own unfrozen TRX back to their spendable balance once Tron's post-unfreeze
+// delay has passed.
+const claimLanguage: StakeClaimLanguage = {
+ actionLabel: sprintf(lstrings.stake_reclaim_1s, TRX_CURRENCY_CODE),
+ amountLabel: sprintf(lstrings.stake_amount_reclaim_1s, TRX_CURRENCY_CODE),
+ positionLabel: sprintf(lstrings.stake_reclaimable_1s, TRX_CURRENCY_CODE),
+ successMessage: lstrings.stake_change_reclaim_success
+}
+
const policyDefault = {
apy: 0,
+ claimLanguage,
stakeProviderInfo,
stakeWarning: null,
unstakeWarning: null,
@@ -152,7 +167,7 @@ export const makeTronStakePlugin = async (
const out: StakePolicy[] = []
for (const policy of policies) {
- if (policy.deprecated && filter?.wallet != null) {
+ if (policy.deprecated === true && filter?.wallet != null) {
const deprecatedPolicyBalance =
filter.wallet.stakingStatus.stakedAmounts.find(
stakedAmount =>
@@ -319,7 +334,7 @@ export const makeTronStakePlugin = async (
}
)
- const approve = async () => {
+ const approve = async (): Promise => {
const signedTx = await wallet.signTx(edgeTransaction)
const broadcastedTx = await wallet.broadcastTx(signedTx)
await wallet.saveTx(broadcastedTx)
@@ -435,7 +450,8 @@ const fetchChangeQuoteV1 = async (
}
],
otherParams: {
- type: policy.deprecated ? 'remove' : isStake ? 'addV2' : 'removeV2',
+ type:
+ policy.deprecated === true ? 'remove' : isStake ? 'addV2' : 'removeV2',
params: { nativeAmount, resource }
}
}
@@ -458,7 +474,7 @@ const fetchChangeQuoteV1 = async (
}
]
- const approve = async () => {
+ const approve = async (): Promise => {
const signedTx = await wallet.signTx(edgeTransaction)
const broadcastedTx = await wallet.broadcastTx(signedTx)
await wallet.saveTx(broadcastedTx)
@@ -498,7 +514,7 @@ const fetchStakePositionV1 = async (
locktime
}
],
- canStake: !policy.deprecated && gt(balanceTrx, '0'),
+ canStake: policy.deprecated !== true && gt(balanceTrx, '0'),
canUnstake: locktime != null ? new Date() >= new Date(locktime) : true,
canUnstakeAndClaim: false,
canClaim: false
diff --git a/src/plugins/stake-plugins/types.ts b/src/plugins/stake-plugins/types.ts
index c744f7ffb11..662c80fac4f 100644
--- a/src/plugins/stake-plugins/types.ts
+++ b/src/plugins/stake-plugins/types.ts
@@ -76,6 +76,24 @@ export interface StakeProviderInfo {
stakeProviderId: string
}
+/**
+ * Display strings for the claim action, already formatted for display. A policy
+ * provides these to replace the default reward wording.
+ */
+export interface StakeClaimLanguage {
+ // Claim button label and claim scene title
+ actionLabel: string
+
+ // Claim amount tile title
+ amountLabel: string
+
+ // Title of the claimable position row
+ positionLabel: string
+
+ // Shown once the claim transaction is sent
+ successMessage: string
+}
+
export interface StakePolicy {
// Internal policy id, unique across all stake policies offered by Edge
stakePolicyId: string
@@ -96,6 +114,12 @@ export interface StakePolicy {
hideUnstakeAction?: boolean
hideUnstakeAndClaimAction?: boolean
+ // Replaces the default reward wording of the claim action. Policies such as
+ // Tron resource freezing pay no yield and hold no third-party reward: their
+ // claim only returns the user's own previously staked funds to the spendable
+ // balance, so calling it a reward misrepresents what the user receives.
+ claimLanguage?: StakeClaimLanguage
+
// Staking policy properties
isLiquidStaking?: boolean
diff --git a/src/util/stakeErrorUtils.ts b/src/util/stakeErrorUtils.ts
new file mode 100644
index 00000000000..3a4fdc122bf
--- /dev/null
+++ b/src/util/stakeErrorUtils.ts
@@ -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