Skip to content

Conversation

@samholmes
Copy link
Contributor

@samholmes samholmes commented Nov 28, 2025

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Requirements

If you have made any visual changes to the GUI. Make sure you have:

  • Tested on iOS device
  • Tested on Android device
  • Tested on small-screen device (iPod Touch)
  • Tested on large-screen device (tablet)

Note

Speeds up login by caching local settings, using optimized settings files with fallback, refactoring init flow, and cleaning denomination settings; also updates review-trigger logic and related UI/actions.

  • Performance & Settings I/O
    • Add cached getLocalAccountSettings with single-read promise and resetLocalAccountSettingsCache on logout.
    • Read/write settings to Settings-optimized.json (local and synced), falling back to Settings.json; avoid writing defaults.
    • Add one-time migrateDenominationSettings to remove default denoms and set denominationSettingsOptimized flag.
  • Login Flow Refactor
    • Split initializeAccount into initializeAccountInner, navigateToNewAccountFlow, navigateToExistingAccountHome, finalizeAccountInit, and completeAccountInit.
    • Defer biometrics setup to background via UI/SETTINGS/SET_TOUCH_ID_SUPPORT.
    • Use cached local settings during init; adjust survey/security alert handling.
  • Review Trigger Logic
    • Switch to cached settings access; improve legacy swapCountData.json migration and parsing; tests updated to reset cache and expect optimized filename writes.
  • Reducers & Actions
    • Simplify LOGIN denomination handling; add UI/SETTINGS/SET_TOUCH_ID_SUPPORT; safer SET_DENOMINATION_KEY merge; update synced settings read/write with optimized files.
  • UI/Scenes
    • Minor typing/error-handling tweaks in WalletListScene and test scene.
    • Remove scam-warning prompts from WalletListMenuActions and WcConnectionsScene before certain actions.
  • Tests & Snapshots
    • Update tests to use cache reset and optimized filenames; snapshot includes denominationSettingsOptimized.

Written by Cursor Bugbot for commit 5d92708. This will update automatically on new commits. Configure here.


TEMPORARY COMMIT - DO NOT MERGE

Adds 'Settings-optimized.json' as a new settings file for testing:
- Read: tries optimized file first, falls back to original Settings.json
- Write: always writes to optimized file only

This preserves the original Settings.json (with bloated denomination data)
while the optimized build uses a separate file. Allows comparing
loginWithKey and initializeAccount performance on the same account:
- "Before" build (develop): reads/writes Settings.json
- "After" build (this branch): reads optimized first, falls back to
  original, writes only to optimized

Remove this commit before merging PR.
The showScamWarningModal function was already disabled (always returns
false), making these calls unnecessary async overhead during login and
other operations.

Removes calls from:
- initializeAccount (firstLogin)
- WcConnectionsScene (firstWalletConnect)
- WalletListMenuActions (firstPrivateKeyView)
When Settings.json doesn't exist (new account or first login), return
default values without writing them to disk. Defaults are derived from
cleaners, so they don't need to be persisted. Settings will be written
when the user explicitly changes a value.

This eliminates unnecessary disk I/O during the login flow.
When local Settings.json doesn't exist (new account), return default
values without writing them to disk. Defaults are derived from cleaners,
so they don't need to be persisted. Settings will be written when values
actually change.

This eliminates unnecessary disk I/O during the login flow.
Move biometric checks (isTouchEnabled, getSupportedBiometryType) from
blocking awaits to background Promise execution. This removes two
blocking native module calls from the critical login path.

The biometric state is loaded asynchronously and dispatched via a new
UI/SETTINGS/SET_TOUCH_ID_SUPPORT action when available. The UI will
use default values (false) until the background check completes.

Also removes the redundant refreshTouchId call - this is already called
by edge-login-ui-rn in submitLogin() before the onLogin callback, so
the call here was unnecessary.
Add a migration that removes default denomination values from the synced
settings file. This migration:

1. Runs once per account (tracked via denominationSettingsOptimized flag)
2. Compares each saved denomination to the default from currencyInfo
3. Removes entries that match the default (they're derived on-demand)
4. Reduces settings file size for existing accounts

The migration runs in the background after ACCOUNT_INIT_COMPLETE to
avoid blocking the login flow. For existing accounts with many saved
denominations, this significantly reduces the settings file size.
Remove the expensive nested loop that merged default denomination
settings with synced settings. Since default denominations are no longer
populated in the LOGIN reducer, we can use synced settings directly.

The synced settings contain only user customizations. Default
denominations are derived on-demand from currencyInfo via selectors.
Remove the expensive loop that populated denomination defaults for all
currency plugins and tokens during LOGIN. This loop iterated through
every plugin and token to set default denominations in Redux state.

Denomination defaults are already available from currencyInfo and can be
derived on-demand via selectors (selectDisplayDenom already has fallback
logic to currencyConfig). This change eliminates unnecessary work during
the login critical path.
Add promise-based caching for local account settings to ensure only
one disk read occurs even with concurrent callers during login.

- Add getLocalAccountSettings() cached getter in LocalSettingsActions
- Add resetLocalAccountSettingsCache() called on logout
- Update cache on write to keep reads consistent
- Keep optimized settings filenames for A/B performance comparison
- Remove duplicate perf measurement sharing same end marker
- Fix ESLint issues in RequestReviewActions and ReviewTriggerTestScene

This reduces login_readLocalAccountSettings time from ~870ms to ~500ms
by eliminating redundant disk reads during the initialization flow.
@samholmes samholmes force-pushed the sam/optimize-initializeAccount branch from 5956aee to 5d92708 Compare December 2, 2025 00:09
.changeUserSettings(userSettings)
.catch((error: unknown) => {
showError(error)
.catch((err: unknown) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why rename this? If we had to standardize on one name, I would prefer "error" over "err" or "e".


for (const plugin of Object.keys(defaultDenominationSettings)) {
// @ts-expect-error
// @ts-expect-error - Dynamic object property assignment for denomination merging
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comments are the wrong fix. The mergedDenominationSettings should be typed as const mergedDenominationSettings: DenominationSettings = {}. Then the error itself goes away.

// @ts-expect-error - Dynamic object property assignment for denomination merging
mergedDenominationSettings[plugin] = {}
// @ts-expect-error
// @ts-expect-error - Dynamic object property access for denomination merging
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The solution to this error is a simple ?? []

Comment on lines 299 to 301
// @ts-expect-error - Dynamic object property assignment for denomination merging
mergedDenominationSettings[plugin][code] = {
// @ts-expect-error
// @ts-expect-error - Dynamic object property access for denomination merging
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The solution to these errors is:

          mergedDenominationSettings[plugin][code] = {
            ...defaultDenominationSettings[plugin]?.[code],
            ...syncedDenominationSettings[plugin]?.[code],
            name: '',
            multiplier: '',
            symbol: ''
          }

I know we are just going to delete this code, but I'd rather not get in the habit of doubling down on @ts-expect-error comments when they are trivial to fix.

Comment on lines 117 to 119
swapCount: Number.isNaN(parseInt(swapCountData.swapCount))
? 0
: parseInt(swapCountData.swapCount)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the double-parse. Why not:

const swapCount = parseInt(swapCountData.swapCount)
const migratedData: ReviewTriggerData = {
        ...initReviewTriggerData(),
        swapCount: Number.isNaN(swapCount) ? 0 : swapCount

interface Props extends WalletsTabSceneProps<'walletList'> {}

export function WalletListScene(props: Props) {
export function WalletListScene(props: Props): React.JSX.Element {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be export const WalletListScene: React.FC<Props> = props => {

Comment on lines 263 to 275
Promise.all([isTouchEnabled(account), getSupportedBiometryType()])
.then(([touchEnabled, supportedType]) => {
dispatch({
type: 'UI/SETTINGS/SET_TOUCH_ID_SUPPORT',
data: {
isTouchEnabled: touchEnabled,
isTouchSupported: supportedType !== false
}
})
})
.catch(() => {
// Fail silently - biometric state will remain at defaults
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not what we agreed in our meeting. Since these states are only used in the settings scene, we want to remove them from global redux, and make them local settings scene state. In the settings scene, it would be:

const touchState = useAsyncValue(() =>
  Promise.all([isTouchEnabled(account), getSupportedBiometryType()]),
  [account]
 )

const PER_WALLET_TIMEOUT = 5000
const MIN_CREATE_WALLET_TIMEOUT = 20000

type SyncedSettings = ReturnType<typeof readSyncedSettings> extends Promise<
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type is already exported as SyncedAccountSettings.

// We don't want to pester the user with too many interrupting flows.
let hideSurvey = false
// Step 1: Common early initialization
const { syncedSettings, referralPromise } = await initializeAccountInner(
Copy link
Contributor

@swansontec swansontec Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we no longer need to modify the settings, but instead dispatch them straight to redux, we should get rid of the ACCOUNT_INIT_COMPLETE action. The items in that payload are:

  1. SyncedSettings (which we have)
  2. pinLoginEnabled, which is only used on the settings scene, and therefore should be removed from redux.
  3. The selected wallet (walletId and currencyCode), which @peachbits has completely removed as part of his currency code PR.
  4. The account local settings, which we load as part of the init process.

By eliminating 2 & 3, and putting 1 straight into the LOGIN action, this only leaves 4. As mentioned elsewhere, we need to move 4 into redux anyhow, so perhaps we should just load it an stick it into the LOGIN action as well. At this point, everything we need to show the home scene or wallet list will be available in redux, and therefore we can navigate to those scenes immediately after dispatching LOGIN. That's the goal of the whole PR, after all.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't like factoring out initializeAccountInner and finalizeAccountInit, since they are part of every login flow, and it makes sense to see them inline. I do like the way you factored out navigateToNewAccountFlow and navigateToExistingAccountHome, because those are different between the two cases.


let readSettingsFromDisk = false
// Use a promise to ensure only one disk read happens, even with concurrent callers
let readSettingsPromise: Promise<LocalAccountSettings> | null = null
Copy link
Contributor

@swansontec swansontec Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the wrong direction. Paul tried to move some stuff out of redux as an experiment, but after several postmortems and failed projects, we now agree that this was a mistake. Redux is the perfect place for globally-available information like account settings, and the local settings definitely belong in there.

The correct solution is to delete the let localAccountSettings cache, and then initializeAccount directly reads the settings from disk and sticks them in redux (which it is already doing). After that, nobody else ever reads the settings from disk, but we do write to disk when making changes.

To make the settings load go faster, as we should probably be doing Promise.all([readSyncedSettings, readLocalAccountSettings]), and then dispatching these to redux as early as possible, as part of the LOGIN action.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants