Skip to content

[Wallet] - Extend connect-progress messages to OKX, OneKey, Ledger, Keystone #1793

@gbarkhatov

Description

@gbarkhatov

Background

We recently shipped staged "Connecting…" progress for the Unisat BTC wallet in PR #1792. Before that change, the wallet-connect modal showed a single opaque "Connecting Unisat" spinner the whole way through. After it, the modal walks the user through each phase ("Connecting Unisat" / "Checking version" / "Switching to Signet" / etc.) and surfaces an actionable description below the title when the wallet is waiting on the user (e.g. "Approve the connection request in your Unisat extension").

The PR added the plumbing once and used it only for Unisat. Every other BTC wallet still shows the old single-string Connecting <name> message. This issue is the follow-up to extend the same UX to OKX, OneKey, Ledger (v1), Ledger (v2), and Keystone. AppKit is intentionally not in scope.

What's already in place (do not redo)

PR #1792 already shipped:

  • ProgressReporter type in packages/babylon-wallet-connector/src/core/types.ts(message?: string, description?: string) => void.
  • IProvider.connectWallet accepts an optional onProgress: ProgressReporter. Providers that don't pass it simply ignore it.
  • WalletConnector.connect creates a reporter wired to its connecting event and threads it into Wallet.connectprovider.connectWallet(onProgress).
  • connecting event signature is (message?, description?). useWalletConnectors forwards both args into displayLoader.
  • LoaderScreen renders a title and an optional secondary description text below it.

The work in this issue is only inside each provider's connectWallet method. No infrastructure changes are needed.

What needs to happen for each wallet

The pattern is identical for all five providers:

  1. Import ProgressReporter from @/core/types.
  2. Change connectWallet = async (): Promise<void> => {...} to connectWallet = async (onProgress?: ProgressReporter): Promise<void> => {...}.
  3. Call onProgress?.(title, description?) at each meaningful phase boundary inside that function (and inside any helper it calls, by threading onProgress through as an argument).
  4. Keep the string literals inline in the provider — that's the existing convention in this package and is what Unisat does. Do not move these to the vault's copy.ts.

Per-wallet stage maps and gotchas below.

1. OKX — packages/babylon-wallet-connector/src/core/wallets/btc/okx/provider.ts

OKX has only a single interactive RPC (this.provider.connect()) which returns { address, compressedPublicKey } in one call, so there is essentially one stage to surface — the approval prompt itself. There is no separate version check, no switchChain flow (OKX picks the right provider object at construction time via wallet[providerName]).

# Trigger Title Description
1 Before this.provider.connect() (file lines 48–51) Connecting OKX Approve the connection request in your OKX extension

Notes:

  • The default Connecting OKX is already emitted by WalletConnector.ts before the provider runs, but it has no description. Calling onProgress?.(...) at the start of connectWallet upgrades it to title + description.
  • Optional follow-up (not required for this issue): wrap this.provider.connect() in withTimeout like Unisat does, with a "did not respond" recovery message. Today OKX can hang forever if the extension is wedged.

2. OneKey — packages/babylon-wallet-connector/src/core/wallets/btc/onekey/provider.ts

OneKey's connect runs three steps: this.provider.connectWallet() (interactive), then this.provider.getAddress(), then this.provider.getPublicKeyHex(). The single-address-mode error is already caught and surfaced as WALLET_CONFIG_REQUIRED — leave that logic untouched.

# Trigger Title Description
1 Before this.provider.connectWallet() (file lines 33–35) Connecting OneKey Approve the connection request in your OneKey extension
2 After connectWallet resolves, before getAddress() (file line 61) Finalizing connection (none)

Notes:

  • OneKey has a getNetwork/mapOneKeyNetwork capability but currently does not align the wallet with config.network during connect (unlike Unisat's ensureExpectedChain). Adding such a check is a separate improvement and out of scope for this issue — file it as its own follow-up if desired.
  • Optional follow-up: same withTimeout wrap as OKX.

3. Ledger v1 — packages/babylon-wallet-connector/src/core/wallets/btc/ledger/provider.ts

Ledger is a hardware device, so the user has to plug it in, unlock, open the BTC app, and confirm the address on the physical screen. This is the wallet that benefits most from stage messages because the user has multiple offline actions to take.

# Trigger Title Description
1 Before this.createAppClient() (file line 103) Connecting Ledger Plug in your Ledger, unlock it, and open the Bitcoin app
2 After transport opens, before app.getMasterFingerprint() (file line 106) Reading device (none)
3 Before this.getTaprootAccount(...) which calls app.getWalletAddress(..., true) (file line 117) Verify address on device Confirm the address shown on your Ledger screen

Notes:

  • getWalletAddress(...) is called with showOnScreen=true, which is the moment the user must press both buttons on the Ledger to approve. That's why phase 3 needs its own description.
  • getWalletPolicy(...) currently calls app.getExtendedPubkey(derivationPath) a second time (line 71 is reached via line 113 after we already fetched it at line 111). That's a pre-existing minor inefficiency, not part of this issue.

4. Ledger v2 — packages/babylon-wallet-connector/src/core/wallets/btc/ledger-v2/provider.ts

Same shape as v1 plus an explicit firmware version check up front (getBbnVersion(app.transport) at file line 161). The Native SegWit / Taproot purpose decision is set in the constructor, so no extra UI stage there.

# Trigger Title Description
1 Before this.createAppClient() (file line 158) Connecting Ledger Plug in your Ledger, unlock it, and open the Bitcoin app
2 Before getBbnVersion(app.transport) (file line 161) Checking Ledger firmware (none)
3 After firmware check, before app.getMasterFingerprint() (file line 168) Reading device (none)
4 Before this.getLedgerAccount(...) (file line 179) Verify address on device Confirm the address shown on your Ledger screen

Notes:

  • Phase 2 should appear briefly before the firmware version error (if firmware < v2) so the user can see why the connect failed even though they were never asked to approve anything.

5. Keystone — packages/babylon-wallet-connector/src/core/wallets/btc/keystone/provider.ts

Keystone is QR-based: the SDK pops its own scanner overlay with its own copy. While that overlay is open the wallet-connector's loader sits behind it, so stage messages matter less than for the other wallets — but we should still surface the "scan a QR" state up front and a brief "Reading account" after.

# Trigger Title Description
1 Before keystoneContainer.read([...]) (file line 55) Connecting Keystone Scan the QR code shown on your Keystone device
2 After QR read succeeds, before dataSdk.parseAccount(...) (file line 88) Reading account (none)

Notes:

  • The Keystone SDK's own read() call already supplies a title and description for the overlay it owns (file lines 56–68). Our onProgress messages are for the wallet-connector loader behind that overlay — they're what the user sees if they dismiss or before the overlay paints.
  • The decodedResult.status === ReadStatus.canceled and other error branches already throw typed WalletErrors — leave those alone.

Acceptance criteria

  • Each of the five providers above emits at least one onProgress(title, description) call covering the "user must act" phase (extension popup approval, plug-in-and-unlock, QR scan).
  • Hardware-wallet providers (Ledger v1, v2, Keystone) additionally emit a non-description intermediate stage so the loader text changes visibly during the multi-step flow.
  • No regressions:
    • Existing error paths (CONNECTION_REJECTED, WALLET_CONFIG_REQUIRED for OneKey, CONNECTION_CANCELED / QR_READ_ERROR for Keystone, firmware-too-low for Ledger v2) still surface unchanged.
    • Other BTC providers not touched in this issue (Unisat, AppKit, injectable) keep their current behaviour.
  • pnpm --filter @babylonlabs-io/wallet-connector run build and pnpm --filter @babylonlabs-io/wallet-connector run lint are clean.

Manual test plan

For each of OKX, OneKey, Ledger (whichever version is active), Keystone, run pnpm --filter vault run dev and open Connect → BTC → :

  • OKX: title flips from default Connecting OKX to the same title with the new description "Approve the connection request in your OKX extension". Approve in extension → connected.
  • OneKey (happy path): see Connecting OneKey + description, approve, then briefly Finalizing connection, then connected.
  • OneKey (single-address-mode off in wallet settings): existing error dialog still appears with the existing wording. No regression.
  • Ledger v1 / v2 (happy path): loader walks through Connecting Ledger (with plug-in instructions) → Reading deviceVerify address on device (with confirm-on-screen instructions). Press both buttons on the device → connected.
  • Ledger v2 with old firmware: loader briefly shows Checking Ledger firmware, then the firmware-too-low error is surfaced via the existing error dialog.
  • Keystone (happy path): before the SDK overlay paints, loader shows Connecting Keystone with QR-scan instructions; after the scan completes, briefly Reading account; then connected.
  • Keystone (canceled QR scan): existing CONNECTION_CANCELED error path unchanged.

Reference

  • Original Unisat implementation that established the pattern, type system, and loader UI: PR #1792.
  • The Unisat provider is the worked example to copy from: packages/babylon-wallet-connector/src/core/wallets/btc/unisat/provider.ts — see how onProgress is threaded into ensureExpectedChain for an example of passing the reporter into a helper.

Out of scope

  • AppKit (Reown) BTC connector.
  • Wrapping interactive calls in withTimeout for OKX / OneKey / Ledger / Keystone. Worth doing separately; not required for stage messages.
  • Adding a ensureExpectedChain-style network alignment to OneKey. Separate enhancement.
  • Any changes to Wallet.ts, WalletConnector.ts, State.context.tsx, Screen.tsx, Loader/index.tsx, useWalletConnectors.tsx — these were finalised in PR #1792.
  • Cancel / back UI inside the loader. The loader stays non-interactive.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions