Skip to content

Commit 46e0ac0

Browse files
authored
Merge pull request #5706 from EdgeApp/jon/fix/same-addr-swap
Jon/fix/same addr swap
2 parents 4655aeb + b181189 commit 46e0ac0

File tree

5 files changed

+108
-6
lines changed

5 files changed

+108
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- added: `parseMarkdown` supports bold text (`**strong**`)
88
- added: `PendingTxModal` to route to pending txs for acceleration
99
- added: Added "Report Error" button to all `AlertDropdown`s from `showError`.
10+
- added: Handle `SwapAddressError` by auto-selecting an existing wallet with the same address or splitting if needed
1011
- changed: Duress mode copy
1112
- changed: Increased tappable area for the close button of `NotificationCard`
1213
- changed: Replaced 'react-native-camera' with 'react-native-vision-camera'

src/components/modals/ButtonsModal.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ export interface ButtonModalProps<Buttons> {
5454
* Build a custom modal component if you need form fields, check boxes,
5555
* or other interactive elements.
5656
*/
57-
export function ButtonsModal<Buttons extends Record<string, ButtonInfo>>(
58-
props: ButtonModalProps<Buttons>
59-
) {
57+
export const ButtonsModal: React.FC<
58+
ButtonModalProps<Record<string, ButtonInfo>>
59+
> = props => {
6060
const {
6161
bridge,
6262
title,
@@ -91,7 +91,7 @@ export function ButtonsModal<Buttons extends Record<string, ButtonInfo>>(
9191
result => {
9292
if (result) bridge.resolve(key)
9393
},
94-
error => {
94+
(error: unknown) => {
9595
showError(error)
9696
}
9797
)

src/components/scenes/SwapProcessingScene.tsx

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { captureException } from '@sentry/react-native'
22
import {
33
asMaybeInsufficientFundsError,
44
asMaybeSwapAboveLimitError,
5+
asMaybeSwapAddressError,
56
asMaybeSwapBelowLimitError,
67
asMaybeSwapCurrencyError,
78
asMaybeSwapPermissionError,
@@ -18,10 +19,13 @@ import { lstrings } from '../../locales/strings'
1819
import { useSelector } from '../../types/reactRedux'
1920
import type { NavigationBase, SwapTabSceneProps } from '../../types/routerTypes'
2021
import { getCurrencyCode } from '../../util/CurrencyInfoHelpers'
22+
import { getWalletName } from '../../util/CurrencyWalletHelpers'
2123
import { convertNativeToDisplay, zeroString } from '../../util/utils'
24+
import { ButtonsModal } from '../modals/ButtonsModal'
2225
import { showInsufficientFeesModal } from '../modals/InsufficientFeesModal'
2326
import { showPendingTxModal } from '../modals/PendingTxModal'
2427
import { CancellableProcessingScene } from '../progress-indicators/CancellableProcessingScene'
28+
import { Airship } from '../services/AirshipInstance'
2529
import type { SwapErrorDisplayInfo } from './SwapCreateScene'
2630

2731
export interface SwapProcessingParams {
@@ -33,7 +37,7 @@ export interface SwapProcessingParams {
3337

3438
type Props = SwapTabSceneProps<'swapProcessing'>
3539

36-
export function SwapProcessingScene(props: Props) {
40+
export const SwapProcessingScene: React.FC<Props> = (props: Props) => {
3741
const { route, navigation } = props
3842
const { swapRequest, swapRequestOptions, onCancel, onDone } = route.params
3943

@@ -61,6 +65,95 @@ export function SwapProcessingScene(props: Props) {
6165
navigation: NavigationBase,
6266
error: unknown
6367
): Promise<void> => {
68+
// Handle same-address requirement for swap flows requiring a split:
69+
const addressError = asMaybeSwapAddressError(error)
70+
if (addressError != null && addressError.reason === 'mustMatch') {
71+
try {
72+
const fromWallet = swapRequest.fromWallet
73+
const fromAddresses = await fromWallet.getAddresses({ tokenId: null })
74+
const fromAddress = fromAddresses[0]?.publicAddress
75+
const targetPluginId = swapRequest.toWallet.currencyInfo.pluginId
76+
77+
let matchingWalletId: string | undefined
78+
for (const walletId of Object.keys(account.currencyWallets)) {
79+
const wallet = account.currencyWallets[walletId]
80+
if (wallet.currencyInfo.pluginId === targetPluginId) {
81+
const toAddresses = await wallet.getAddresses({ tokenId: null })
82+
const publicAddress = toAddresses[0]?.publicAddress
83+
if (
84+
fromAddress != null &&
85+
publicAddress != null &&
86+
fromAddress.toLowerCase() === publicAddress.toLowerCase()
87+
) {
88+
matchingWalletId = walletId
89+
break
90+
}
91+
}
92+
}
93+
94+
let finalToWalletId: string = swapRequest.toWallet.id
95+
let finalToWallet = swapRequest.toWallet
96+
let isWalletCreated = false
97+
if (matchingWalletId == null) {
98+
// If not found, split from the source chain wallet to the destination
99+
// chain wallet type:
100+
isWalletCreated = true
101+
const splitFromWallet = fromWallet
102+
const targetWalletType =
103+
account.currencyConfig[targetPluginId]?.currencyInfo.walletType
104+
if (targetWalletType == null)
105+
throw new Error('Target wallet type unavailable')
106+
107+
const splitWalletId = await account.splitWalletInfo(
108+
splitFromWallet.id,
109+
targetWalletType
110+
)
111+
const newWallet = await account.waitForCurrencyWallet(splitWalletId)
112+
finalToWalletId = newWallet.id
113+
finalToWallet = newWallet
114+
} else {
115+
finalToWalletId = matchingWalletId
116+
finalToWallet = account.currencyWallets[matchingWalletId]
117+
}
118+
119+
// Navigate back to swap create with the correct wallet selected:
120+
navigation.navigate('swapTab', {
121+
screen: 'swapCreate',
122+
params: {
123+
fromWalletId: fromWallet.id,
124+
fromTokenId: swapRequest.fromTokenId,
125+
toWalletId: finalToWalletId,
126+
toTokenId: swapRequest.toTokenId
127+
}
128+
})
129+
130+
// Show modal with OK button:
131+
const name = getWalletName(finalToWallet)
132+
const fromCurrencyCode = getCurrencyCode(
133+
fromWallet,
134+
swapRequest.fromTokenId
135+
)
136+
const toCurrencyCode = getCurrencyCode(
137+
finalToWallet,
138+
swapRequest.toTokenId
139+
)
140+
const template = isWalletCreated
141+
? lstrings.ss_same_address_upgrade_created_3s
142+
: lstrings.ss_same_address_upgrade_selected_3s
143+
await Airship.show<string | undefined>(bridge => (
144+
<ButtonsModal
145+
bridge={bridge}
146+
title={lstrings.exchange_generic_error_title}
147+
message={sprintf(template, fromCurrencyCode, toCurrencyCode, name)}
148+
buttons={{ ok: { label: lstrings.string_ok_cap } }}
149+
/>
150+
))
151+
return
152+
} catch (e) {
153+
// Fall through to generic error handling if something goes wrong
154+
}
155+
}
156+
64157
// Check for pending transaction error first
65158
if (
66159
error != null &&
@@ -185,7 +278,7 @@ function processSwapQuoteError({
185278
}
186279

187280
const belowLimit = asMaybeSwapBelowLimitError(error)
188-
if (belowLimit) {
281+
if (belowLimit != null) {
189282
const currentCurrencyDenomination =
190283
belowLimit.direction === 'to' ? toDenomination : fromDenomination
191284

src/locales/en_US.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,10 @@ const strings = {
10691069
open_settings: 'Open Settings',
10701070
ss_geolock: 'Location restricted. Unable to complete exchange.',
10711071
ss_unable: 'No enabled exchanges support %1$s to %2$s.',
1072+
ss_same_address_upgrade_created_3s:
1073+
'%1$s→%2$s upgrades require the same address for both wallets. %3$s has been created. Please try your upgrade again.',
1074+
ss_same_address_upgrade_selected_3s:
1075+
'%1$s→%2$s upgrades require the same address for both wallets. %3$s has been selected. Please try your upgrade again.',
10721076
account: 'Account',
10731077
forget_account_title: 'Forget Account',
10741078
forget_account_message_common:
@@ -1548,6 +1552,7 @@ const strings = {
15481552
request_balance: 'You have %s',
15491553

15501554
// Crypto Exchange Scene
1555+
exchange_same_address_upgrade: 'Asset Upgrade',
15511556
exchange_generic_error_title: 'Exchange Error',
15521557
exchange_insufficient_funds_title: 'Insufficient Funds',
15531558
exchange_insufficient_funds_message:

src/locales/strings/enUS.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,8 @@
846846
"open_settings": "Open Settings",
847847
"ss_geolock": "Location restricted. Unable to complete exchange.",
848848
"ss_unable": "No enabled exchanges support %1$s to %2$s.",
849+
"ss_same_address_upgrade_created_3s": "%1$s→%2$s upgrades require the same address for both wallets. %3$s has been created. Please try your upgrade again.",
850+
"ss_same_address_upgrade_selected_3s": "%1$s→%2$s upgrades require the same address for both wallets. %3$s has been selected. Please try your upgrade again.",
849851
"account": "Account",
850852
"forget_account_title": "Forget Account",
851853
"forget_account_message_common": "Are you sure you want to forget the account %1$s from this device? PIN login will no longer work and you will need to login with your username and password.\n\nIf 2FA is enabled for this account, you will need to either enter your 2FA code or be locked out for at least 7 days (up to 18 months) before being able to regain access.",
@@ -1211,6 +1213,7 @@
12111213
"send_address_expire_title": "Payment Address Expiration Time",
12121214
"send_address_expired_error_message": "Payment Address Expired",
12131215
"request_balance": "You have %s",
1216+
"exchange_same_address_upgrade": "Asset Upgrade",
12141217
"exchange_generic_error_title": "Exchange Error",
12151218
"exchange_insufficient_funds_title": "Insufficient Funds",
12161219
"exchange_insufficient_funds_message": "Entered amount plus fees exceeds wallet balance.",

0 commit comments

Comments
 (0)