Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased (develop)

- added: The receive scene shows a wallet's cached address immediately on a warm login. On rotating-address chains it marks the address provisional with an inline "checking for your latest address" row and swaps in the confirmed address once the engine loads.
- changed: Gate action-queue balance-effect checks and the login FIO address refresh on engine readiness, so wallets emitted from the core's new wallet cache (before their engines load) cannot mis-evaluate balance effects or crash the FIO refresh.
- changed: Opening any wallet-scoped scene asks the core to prioritize that wallet's engine startup in the post-login queue.

## 4.50.0 (staging)

- added: Changelly swap provider
Expand Down
1 change: 0 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,6 @@ export default [
'src/components/services/DeepLinkingManager.tsx',
'src/components/services/EdgeContextCallbackManager.tsx',

'src/components/services/FioService.ts',
'src/components/services/LoanManagerService.ts',
'src/components/services/NetworkActivity.ts',
'src/components/services/PasswordReminderService.ts',
Expand Down
62 changes: 61 additions & 1 deletion src/__tests__/scenes/RequestScene.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { describe, expect, it } from '@jest/globals'
import { render } from '@testing-library/react-native'
import { act, render } from '@testing-library/react-native'
import * as React from 'react'

import { RequestSceneComponent } from '../../components/scenes/RequestScene'
import { getTheme } from '../../components/services/ThemeContext'
import { lstrings } from '../../locales/strings'
import { btcCurrencyInfo } from '../../util/fake/fakeBtcInfo'
import { makeFakeCurrencyConfig } from '../../util/fake/fakeCurrencyConfig'
import { FakeProviders } from '../../util/fake/FakeProviders'
Expand Down Expand Up @@ -97,4 +98,63 @@ describe('Request', () => {
expect(rendered.toJSON()).toMatchSnapshot()
rendered.unmount()
})

it('shows the provisional affordance on a rotating chain while the engine has not confirmed', async () => {
jest.useRealTimers()
// Rotating chain (btcCurrencyInfo has no hasStableAddresses). The
// allowCached query returns the cached address at once; the plain
// engine-confirmed query never resolves, so the affordance should
// appear after the grace window and stay.
const fakeWallet: any = {
id: 'w-rotating',
currencyInfo: btcCurrencyInfo,
currencyConfig: makeFakeCurrencyConfig(btcCurrencyInfo),
balanceMap: new Map([[null, '1234']]),
on: () => () => {},
getAddresses: async (opts: any) => {
if (opts.allowCached === true) {
return [{ addressType: 'publicAddress', publicAddress: 'cachedaddr' }]
}
return await new Promise(() => {})
},
encodeUri: async () => 'bitcoin:cachedaddr'
}

const rendered = render(
<FakeProviders>
<RequestSceneComponent
{...fakeEdgeAppSceneProps('request', {
tokenId: null,
walletId: 'w-rotating'
})}
isConnected={false}
isLightAccount={false}
fioAddressesExist={false}
currencyCode="BTC"
wallet={fakeWallet}
exchangeSecondaryToPrimaryRatio={{} as any}
displayDenomination={{ multiplier: '100000000', name: 'BTC' }}
theme={getTheme()}
refreshAllFioAddresses={async () => {}}
onSelectWallet={async (walletId, currencyCode) => {}}
toggleAccountBalanceVisibility={async () => {}}
showBalance
/>
</FakeProviders>
)

// Flush getAddressItems' awaited provisional query so the cached
// address is on screen, then let the grace window elapse (the
// engine-confirmed query never resolves), all inside act() so the
// timer-driven setState is applied:
await act(async () => {
await Promise.resolve()
await new Promise(resolve => setTimeout(resolve, 600))
})

expect(
rendered.getByText(lstrings.request_provisional_address)
).toBeTruthy()
rendered.unmount()
})
})
10 changes: 10 additions & 0 deletions src/__tests__/scenes/__snapshots__/RequestScene.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2082,6 +2082,16 @@ exports[`Request should render with loaded props 1`] = `
</Text>
</View>
</View>
<View
style={
{
"alignItems": "center",
"flexDirection": "row",
"height": 39,
"marginTop": 11,
}
}
/>
</View>
<View
collapsable={false}
Expand Down
10 changes: 10 additions & 0 deletions src/components/hoc/withWallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ export function withWallet<Props extends { wallet: EdgeCurrencyWallet }>(
const currencyWallets = useWatch(account, 'currencyWallets')
const wallet = currencyWallets[route.params.walletId]

// Opening a wallet-scoped scene is the "the user wants this wallet"
// signal: waitForCurrencyWallet moves the wallet's engine startup to
// the front of the core's post-login queue. Rejections (a deleted or
// broken wallet) are already handled by the effect below:
const { walletId } = route.params
React.useEffect(() => {
if (account.waitForCurrencyWallet == null) return
account.waitForCurrencyWallet(walletId).catch(() => {})
}, [account, walletId])

React.useEffect(() => {
if (wallet == null) navigation.goBack()
}, [navigation, wallet])
Expand Down
Loading