Skip to content

CP-13334: Reuse app connection screen for add ledger account#3571

Open
ruijialin-avalabs wants to merge 37 commits intomainfrom
cp-13334
Open

CP-13334: Reuse app connection screen for add ledger account#3571
ruijialin-avalabs wants to merge 37 commits intomainfrom
cp-13334

Conversation

@ruijialin-avalabs
Copy link
Contributor

@ruijialin-avalabs ruijialin-avalabs commented Feb 12, 2026

Description

iOS: 7482
Android: 7483

Ticket: CP-13334

  • update addAccount flow for ledger to go through appConnectionScreen to derived addresses and update wallet secret.
  • Replaces BiometricsSDK-based Ledger device info retrieval with a persisted Zustand store for better performance and cleaner architecture. This enables reusing AppConnectionScreen for both onboarding and adding accounts.

Key Changes

  • New Zustand store (packages/core-mobile/app/new/features/ledger/store.ts)

    • Persists Ledger device info (device ID, name, derivation path) mapped by wallet ID
    • Replaces async BiometricsSDK lookups with synchronous store access
    • Auto-syncs on wallet creation, update, and deletion
  • Removed hooks

    • useObserveLedgerState - Eliminated app polling from WalletCard (no longer gates "Add Account")
    • useLedgerDeviceInfo - Replaced with useLedgerWalletMap().getLedgerInfoByWalletId()
  • Unified AppConnectionScreen

    • Now handles both onboarding (AppConnectionOnboardingScreen) and adding accounts (AppConnectionAddAccountScreen)
    • Both screens pass device info and completion handlers to shared base screen
  • Simplified LedgerSetupContext

    • Focuses only on setup flow state (device connection, derivation path, wallet creation status)
    • Device info now managed separately in store

Migration

Store automatically persists to MMKV. Existing wallets will populate the store on next device connection.

Screenshots/Videos

IMG_0822.mov

Testing

Dev Testing (if applicable)

  • click add account in my wallet screen to initiate the app connection flow to connect Avalanche and optional Solana to create account and update wallet secret

Checklist

Please check all that apply (if applicable)

  • I have performed a self-review of my code
  • I have verified the code works
  • I have included screenshots / videos of android and ios
  • I have added testing steps
  • I have added/updated necessary unit tests
  • I have updated the documentation

# Conflicts:
#	packages/core-mobile/app/new/features/ledger/hooks/useLedgerWallet.ts

# Conflicts:
#	packages/core-mobile/app/new/common/components/WalletCard.tsx
@ruijialin-avalabs
Copy link
Contributor Author

@copilot review

Copy link

Copilot AI commented Feb 12, 2026

@ruijialin-avalabs I've opened a new pull request, #3572, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI review requested due to automatic review settings February 12, 2026 18:29
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors Ledger integration to reuse the existing app connection screen for adding new accounts to Ledger wallets, rather than creating separate flows.

Changes:

  • Refactored AppConnectionScreen to be reusable for both onboarding and adding accounts
  • Updated Ledger wallet storage to include device metadata and derivation path type
  • Created new hook useSetLedgerAddress to consolidate address setting logic

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/core-mobile/app/services/ledger/types.ts Updated wallet creation/update option interfaces
packages/core-mobile/app/new/routes/(signedIn)/_layout.tsx Added new route for add account flow
packages/core-mobile/app/new/routes/(signedIn)/(modals)/addAccountAppConnection/*.tsx New route components for adding Ledger accounts
packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/appConnection.tsx Updated to use renamed onboarding screen
packages/core-mobile/app/new/features/ledger/utils/index.ts Added getOppositeKeys utility and updated schema
packages/core-mobile/app/new/features/ledger/store.ts Enhanced Ledger wallet mapping with device info and derivation path
packages/core-mobile/app/new/features/ledger/screens/*.tsx Refactored screens to support both onboarding and account addition
packages/core-mobile/app/new/features/ledger/hooks/*.ts New and updated hooks for Ledger wallet management
packages/core-mobile/app/new/features/ledger/contexts/LedgerSetupContext.tsx Simplified context by moving wallet creation logic to hooks
packages/core-mobile/app/new/features/ledger/components/LedgerAppConnection.tsx Made component reusable with configurable title and loading states
packages/core-mobile/app/new/common/hooks/useManageWallet.ts Added navigation to Ledger account addition flow
packages/core-mobile/app/new/common/components/WalletCard.tsx Removed Ledger-specific app connection logic

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

Copilot AI commented Feb 12, 2026

@ruijialin-avalabs I've opened a new pull request, #3575, to work on those changes. Once the pull request is ready, I'll request review from you.

B0Y3R-AVA
B0Y3R-AVA previously approved these changes Feb 12, 2026
Copy link
Contributor

@B0Y3R-AVA B0Y3R-AVA left a comment

Choose a reason for hiding this comment

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

Nice Work!

isUpdatingWallet={isUpdatingWallet}
resetSetup={resetSetup}
disconnectDevice={disconnectDevice}
accountIndex={0}
Copy link
Contributor

Choose a reason for hiding this comment

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

mind adding an inline comment here next to accountIndex specifying that we're intentionally setting it to zero here as this screen is used for importing the wallet for the first time

Copilot AI review requested due to automatic review settings February 12, 2026 21:25
Copilot AI review requested due to automatic review settings February 13, 2026 16:32
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

packages/core-mobile/app/new/features/ledger/store.ts:59

  • In the removeLedgerWallet function, spreading the ledgerWalletMap before deleting creates a shallow copy, but then the delete operator still mutates this copy. While this works, it's cleaner to use object destructuring or filtering to create a new object without the key. For example: const { [walletId]: _, ...newLedgerWalletMap } = get().ledgerWalletMap would be more functional and clear.
      removeLedgerWallet: (walletId: walletId) => {
        const newLedgerWalletMap = { ...get().ledgerWalletMap }
        delete newLedgerWalletMap[walletId]
        set({
          ledgerWalletMap: newLedgerWalletMap
        })

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings February 13, 2026 20:28
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings February 13, 2026 21:57
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings February 13, 2026 22:06
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings February 16, 2026 13:20
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (2)

packages/core-mobile/app/new/features/ledger/hooks/useLedgerWallet.ts:102

  • The connectToDevice function in useLedgerWallet has been changed to only accept a deviceId parameter, but in LedgerSetupContext it's wrapped to also accept deviceName. However, the actual connection to the device via LedgerService.ensureConnection only uses the deviceId. The deviceName parameter is not passed to the service, which means the device name is only being stored in the context state but not used during the actual connection. Verify that this is intentional and that device name is not needed for the connection process.
  const connectToDevice = useCallback(async (deviceId: string) => {
    setIsConnecting(true)
    try {
      await LedgerService.ensureConnection(deviceId)
      Logger.info('Connected to Ledger device')
    } catch (error) {
      Logger.error('Failed to connect to device', error)
      throw error
    } finally {
      setIsConnecting(false)
    }
  }, [])

packages/core-mobile/app/new/features/ledger/store.ts:59

  • In the removeLedgerWallet function, the object is being spread before deletion (const newLedgerWalletMap = { ...get().ledgerWalletMap }). While this creates a shallow copy, the mutation happens on line 56 with delete newLedgerWalletMap[walletId]. This is correct, but consider using a more functional approach like destructuring to avoid the mutation pattern: const { [walletId]: _, ...newLedgerWalletMap } = get().ledgerWalletMap.
      removeLedgerWallet: (walletId: walletId) => {
        const newLedgerWalletMap = { ...get().ledgerWalletMap }
        delete newLedgerWalletMap[walletId]
        set({
          ledgerWalletMap: newLedgerWalletMap
        })

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings February 16, 2026 17:33
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (2)

packages/core-mobile/app/new/features/ledger/screens/SolanaConnectionScreen.tsx:54

  • The isUpdatingWallet variable is included in the dependency array but it's also checked inside the cleanup function. This creates a potential issue where the cleanup runs based on the captured value of isUpdatingWallet at render time, but the actual check happens when the component unmounts. If isUpdatingWallet changes after render but before unmount, the cleanup logic may not behave as intended. Consider removing isUpdatingWallet from the dependency array or restructuring the cleanup logic to use a ref to track the actual updating state.
  // Cleanup: Stop polling when component unmounts (unless wallet update is in progress)
  useEffect(() => {
    return () => {
      // Only stop polling if we're not in the middle of wallet update
      // If wallet update succeeded, the connection should remain for the wallet to use
      if (!isUpdatingWallet) {
        Logger.info('AppConnectionScreen unmounting, stopping app polling')
        LedgerService.stopAppPolling()
      }
    }
  }, [isUpdatingWallet])

packages/core-mobile/app/new/features/ledger/store.ts:59

  • The removeLedgerWallet function correctly creates a new object with the spread operator { ...get().ledgerWalletMap } before mutating it. This is good practice for immutability. However, consider whether this should also disconnect the device if it's currently connected, similar to how wallet deletion works elsewhere in the codebase.
      removeLedgerWallet: (walletId: walletId) => {
        const newLedgerWalletMap = { ...get().ledgerWalletMap }
        delete newLedgerWalletMap[walletId]
        set({
          ledgerWalletMap: newLedgerWalletMap
        })

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +105 to +114
useEffect(() => {
return () => {
// Only stop polling if we're not in the middle of wallet creation
// If wallet creation succeeded, the connection should remain for the wallet to use
if (!isCreatingWallet) {
if (!isUpdatingWallet) {
Logger.info('AppConnectionScreen unmounting, stopping app polling')
LedgerService.stopAppPolling()
}
}
}, [isCreatingWallet])
}, [isUpdatingWallet])
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

The isUpdatingWallet variable is included in the dependency array but it's also checked inside the cleanup function. This creates a potential issue where the cleanup runs based on the captured value of isUpdatingWallet at render time, but the actual check happens when the component unmounts. If isUpdatingWallet changes after render but before unmount, the cleanup logic may not behave as intended. Consider removing isUpdatingWallet from the dependency array or restructuring the cleanup logic to use a ref to track the actual updating state.

Copilot uses AI. Check for mistakes.
const handleConnectAvalanche = useCallback(async () => {
try {
if (!deviceId) {
throw new Error('No device ID found')
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

The code checks if deviceId is falsy but doesn't provide any recovery mechanism or user feedback when this occurs. When deviceId is undefined or null at the start of the flow (before device connection), the buttons are disabled but the user sees no indication of what's wrong or what they need to do. Consider adding a loading state indicator or error message when device connection is pending, especially for the add account flow where the device info should already be available from the store.

Suggested change
throw new Error('No device ID found')
Alert.alert(
'No Ledger Device',
'No Ledger device is connected. Please connect your Ledger device and try again.',
[{ text: 'OK' }]
)
return

Copilot uses AI. Check for mistakes.
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