Skip to content

feat: unified Sign-with-Extension picker across all signing surfaces - #987

Merged
feruzm merged 3 commits into
developfrom
feat/sign-with-extension-picker
Jun 22, 2026
Merged

feat: unified Sign-with-Extension picker across all signing surfaces#987
feruzm merged 3 commits into
developfrom
feat/sign-with-extension-picker

Conversation

@feruzm

@feruzm feruzm commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

When a Keychain user signs (transfers, wallet ops, permissions, etc.), the single "Sign with Keychain" button could be silently hijacked by Hive Keeper, because Keeper aliases window.hive_keychain and the broadcast auto-detects Keeper-first. With one account on Keychain and another on Keeper, the wrong wallet would answer.

This brings the unified "Sign with Extension" detection and picker that login and the auth-upgrade dialog already use to the remaining single-button signing surfaces, and makes the routing respect the user's explicit choice per account.

Changes

  • Per-username preference. The saved extension is now stored per username (a {username: extId} map) instead of one global value, falling back to the legacy global key so existing users are not reset. A user keeps Keychain for one account and Keeper for another.
  • Pickers on the remaining surfaces. KeyOrHot and the wallet operations signer (wallet-operations-sign.tsx, the transfer dialog in the screenshot) now call getDetectedExtensions(), show the shared ExtensionChooser when more than one is installed, persist the choice for the active account, then sign with it. A single extension signs directly. Private key, Hivesigner, MetaMask and mobile/in-app paths are unchanged.
  • Legacy helpers honor the choice. utils/keychain.ts per-op helpers now resolve the chosen instance via resolveKeychainInstance(account) instead of reading raw window.hive_keychain, so a Keychain choice routes to the real Keychain (never Keeper) and a Keeper choice uses the live window.hive.

Detection is hybrid by necessity: only Keeper implements the unified window.hive registry; Keychain and Peak Vault are detected via their legacy globals, and Keeper's window.hive_keychain self-alias is filtered out so it is never offered as a phantom Keychain.

Tests

  • Per-username preference and legacy-global fallback.
  • resolveKeychainInstance routes to the account's chosen extension, never Keeper-first when a Keychain choice exists.
  • KeyOrHot picker persists the chosen extension (per username) before onKc, and opens the chooser without auto-signing when more than one extension is present.
  • Updated the auth-upgrade dialog spec for the per-username signature.

Typecheck adds no new errors over develop; all affected specs pass.

Notes

The funds-transfer flow already used the auth-upgrade picker; this closes the remaining single-button surfaces so the behavior is uniform everywhere.

Summary by CodeRabbit

  • New Features

    • Users can now select and save a preferred Hive extension per account, eliminating conflicts across different accounts.
    • Added extension selection UI when multiple Hive extensions are detected during login and signing.
  • Improvements

    • Enhanced extension detection at sign-in and transaction time for more reliable wallet integration.
    • Signing flows now automatically resolve the correct extension based on user preference and availability.

Extend the existing Sign-with-Extension detection and picker (already used by
login and the auth-upgrade dialog) to the remaining single-button signing
surfaces, so a "Keychain" choice is never silently routed to Hive Keeper, which
aliases window.hive_keychain.

- Make the saved extension preference per-username instead of one global value,
  with a fallback to the legacy global key so existing users are not reset. A
  user with one account on Keychain and another on Keeper now keeps the right
  wallet for each.
- KeyOrHot and the wallet operations signer now detect all installed extensions
  (Keeper, Keychain, Peak Vault), show the shared ExtensionChooser when more than
  one is present, persist the choice for the active account, then sign with it.
  A single extension signs directly; private key, Hivesigner, MetaMask and
  mobile/in-app paths are unchanged.
- Route the legacy per-op keychain helpers (utils/keychain.ts) through the chosen
  instance via resolveKeychainInstance instead of raw window.hive_keychain, so
  the preference is honored there too rather than resolving Keeper-first.
- Tests: per-username preference and legacy fallback, resolveKeychainInstance
  routing, and the KeyOrHot picker persisting the chosen extension before signing.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 512e1f0cb7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +100 to +102
<ExtensionChooser
extensions={detectedExtensions}
onSelect={handleChooseExtension}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Filter Peak Vault from owner-key extension choices

When KeyOrHot is rendered for owner-authority flows such as the key-rotation confirmation (authority="owner"), this chooser still passes every detected extension through. If a user has Keychain plus Peak Vault and selects Peak Vault, the handler persists peakvault and calls onKc, but broadcastWithExtension rejects owner operations for Peak Vault, so the flow fails even though a compatible extension was available. Filter or disable unsupported extensions when authority === "owner".

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 46d471c. KeyOrHot now excludes Peak Vault from the chooser and the single-extension direct path when authority === "owner", applied at both render time and click time. I also labeled account-recovery's signer authority="owner" (it broadcasts change_recovery_account, which the SDK authority map marks owner-only), so the same filter covers that flow. manage-keys-dialog is conditionally-owner (depends on whether owner keys are touched), so it stays dynamic; Peak Vault there still surfaces the clear "owner not supported" rejection if mis-picked rather than signing wrong. Added owner-exclusion tests (chooser and single-extension).

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@feruzm, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 15 minutes and 24 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0eb3b8cd-eb42-404d-9063-e5a547a8dfa0

📥 Commits

Reviewing files that changed from the base of the PR and between 512e1f0 and 46c990b.

📒 Files selected for processing (7)
  • apps/web/src/app/(dynamicPages)/profile/[username]/permissions/_components/account-recovery.tsx
  • apps/web/src/features/shared/auth-upgrade/auth-upgrade-dialog.tsx
  • apps/web/src/features/shared/key-or-hot/index.tsx
  • apps/web/src/specs/features/shared/auth-upgrade-dialog.spec.tsx
  • apps/web/src/specs/features/shared/key-or-hot-metamask.spec.tsx
  • apps/web/src/specs/utils/hive-extensions.spec.ts
  • apps/web/src/utils/hive-extensions.ts
📝 Walkthrough

Walkthrough

Extension preference storage migrates from a single global localStorage key to a per-username map. setPreferredExtensionId and getPreferredExtensionId gain a username parameter, with legacy-key fallback. A new resolveKeychainInstance helper replaces direct window.hive_keychain reads in keychain.ts. Three UI components (KeyOrHot, WalletOperationsSign, AuthUpgradeDialog) gain a multi-extension chooser flow that detects installed extensions at click-time and persists the selection per user.

Changes

Per-user Hive extension preference and multi-extension chooser UI

Layer / File(s) Summary
Per-user preference storage and resolveKeychainInstance
apps/web/src/utils/hive-extensions.ts
Replaces the single global preference key with a per-username JSON map in localStorage. Updates setPreferredExtensionId(username, id) and getPreferredExtensionId(username?) with legacy-key fallback. Adds resolveKeychainInstance(account?) to resolve the correct provider. Updates signBufferWithExtension and broadcastWithExtension to read and clear preferences per account.
keychain.ts: resolve via resolveKeychainInstance
apps/web/src/utils/keychain.ts
Updates handshake() and all request functions (signBuffer through witnessProxy) to obtain the Keychain instance via resolveKeychainInstance(account) instead of directly reading window.hive_keychain.
Auth/login callsites: thread username into preference API
apps/web/src/features/shared/auth-upgrade/auth-upgrade-dialog.tsx, apps/web/src/features/shared/login/login.tsx
AuthUpgradeDialog.handleChooseExtension conditionally passes activeUser.username to setPreferredExtensionId. Login.handleExtensionLogin and handleSelectExtension pass (username, extId) instead of only extId.
KeyOrHot: multi-extension chooser UI
apps/web/src/features/shared/key-or-hot/index.tsx
Adds choosing state, re-detection on click, per-user persistence, ExtensionChooser with back button, and a dynamic keychain button icon/label derived from detected extensions.
WalletOperationsSign: unified extension-chooser signing UI
apps/web/src/features/wallet/operations/wallet-operations-sign.tsx
Adds choosing state, doExtensionSign/handleExtensionSign/handleChooseExtension. Replaces the old disabled keychain button with a unified showExtensionBtn button and a conditional ExtensionChooser chooser view.
Tests: per-user preference and chooser flows
apps/web/src/specs/features/shared/auth-upgrade-dialog.spec.tsx, apps/web/src/specs/features/shared/key-or-hot-metamask.spec.tsx, apps/web/src/specs/utils/hive-extensions.spec.ts
Updates mock signatures and assertions for the (username, id) API. Adds KeyOrHot chooser tests for single and multiple detected extensions. Adds a per-username preference suite to hive-extensions.spec covering isolation, legacy fallback, and resolveKeychainInstance behavior.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant KeyOrHot
  participant hive_extensions
  participant localStorage

  User->>KeyOrHot: Click "Sign with Keychain"
  KeyOrHot->>hive_extensions: getDetectedExtensions()
  alt Multiple extensions detected
    hive_extensions-->>KeyOrHot: [keychain, hive-keeper]
    KeyOrHot->>KeyOrHot: setChoosing(true) → render ExtensionChooser
    User->>KeyOrHot: Select "Keychain"
    KeyOrHot->>hive_extensions: setPreferredExtensionId(username, "keychain")
    hive_extensions->>localStorage: write PREFERRED_EXTENSION_MAP_KEY[username]
    KeyOrHot->>KeyOrHot: onKc()
  else Single extension detected
    hive_extensions-->>KeyOrHot: [hive-keeper]
    KeyOrHot->>hive_extensions: setPreferredExtensionId(username, "hive-keeper")
    hive_extensions->>localStorage: write PREFERRED_EXTENSION_MAP_KEY[username]
    KeyOrHot->>KeyOrHot: onKc()
  end
Loading
sequenceDiagram
  participant signBufferWithExtension
  participant getPreferredExtensionId
  participant localStorage
  participant resolveKeychainInstance
  participant window_hive_keychain

  signBufferWithExtension->>getPreferredExtensionId: getPreferredExtensionId(account)
  getPreferredExtensionId->>localStorage: read PREFERRED_EXTENSION_MAP_KEY[account]
  alt Per-user entry found
    localStorage-->>getPreferredExtensionId: "keychain"
  else Fallback
    getPreferredExtensionId->>localStorage: read legacy PREFERRED_EXTENSION_KEY
  end
  getPreferredExtensionId-->>signBufferWithExtension: preferredId
  signBufferWithExtension->>resolveKeychainInstance: resolveKeychainInstance(account)
  resolveKeychainInstance->>localStorage: getPreferredExtensionId(account)
  resolveKeychainInstance-->>signBufferWithExtension: KeyChainImpl instance
  alt Stale preference (extension gone)
    signBufferWithExtension->>getPreferredExtensionId: setPreferredExtensionId(account, null)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ecency/vision-next#874: Modifies auth-upgrade-dialog.tsx to persist the chosen extension via setPreferredExtensionId, directly overlapping with this PR's callsite update in the same component.
  • ecency/vision-next#980: Changes hive-extensions.ts Keeper/Keychain instance resolution logic via window.hive.isKeeper, overlapping with the core extension-resolution code paths rewritten by resolveKeychainInstance in this PR.

Poem

🐇 Hopping through the browser maze,
Each user keeps their own keychain haze.
No more one-size global stash—
Alice picks Keychain, Bob picks hash!
A map in localStorage snug and neat,
Per-user prefs make signing sweet. 🗝️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective: implementing a unified Sign-with-Extension picker across signing surfaces, which is the core change throughout the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sign-with-extension-picker

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/specs/features/shared/key-or-hot-metamask.spec.tsx`:
- Around line 116-128: The test in the "with one extension, persists it for the
active user BEFORE signing" spec currently verifies that setPreferredExtensionId
was called with correct arguments and that onKc was called once, but does not
explicitly assert the order in which these calls occurred. To properly catch the
regression described in the comment, add explicit invocation-order assertions
between the setPreferredExtensionId mock and the props.onKc mock calls to verify
that the extension persistence happens before the onKc callback is triggered.
This ensures that downstream broadcast receives the persisted extension before
any auto-detection logic in onKc can hijack the user's selection.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7223d394-8c5c-4b73-ab13-04c6d6973732

📥 Commits

Reviewing files that changed from the base of the PR and between 413f0da and 512e1f0.

📒 Files selected for processing (9)
  • apps/web/src/features/shared/auth-upgrade/auth-upgrade-dialog.tsx
  • apps/web/src/features/shared/key-or-hot/index.tsx
  • apps/web/src/features/shared/login/login.tsx
  • apps/web/src/features/wallet/operations/wallet-operations-sign.tsx
  • apps/web/src/specs/features/shared/auth-upgrade-dialog.spec.tsx
  • apps/web/src/specs/features/shared/key-or-hot-metamask.spec.tsx
  • apps/web/src/specs/utils/hive-extensions.spec.ts
  • apps/web/src/utils/hive-extensions.ts
  • apps/web/src/utils/keychain.ts

Comment thread apps/web/src/specs/features/shared/key-or-hot-metamask.spec.tsx
@greptile-apps

greptile-apps Bot commented Jun 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR unifies extension detection and per-account preference persistence across all signing surfaces. Previously a single "Sign with Keychain" button could be silently hijacked by Hive Keeper (which aliases window.hive_keychain) because the broadcast auto-detected Keeper-first; users with one account on Keychain and another on Keeper would get the wrong wallet.

  • Per-username preferences: setPreferredExtensionId / getPreferredExtensionId now store a {username → extId} map in localStorage, with a read-only fallback to the legacy global key so existing users aren't reset.
  • Chooser on all surfaces: KeyOrHot, WalletOperationSign, and AuthUpgradeDialog now call getDetectedExtensions(), show ExtensionChooser when >1 extension is installed, persist the choice per account, and sign with the resolved extension. Peak Vault is filtered from owner-authority flows since it lacks the owner signing API.
  • Legacy helpers fixed: Every window.hive_keychain read in utils/keychain.ts is replaced with resolveKeychainInstance(account), which honors the account's saved preference so a "keychain" choice never silently falls through to Keeper.

Confidence Score: 5/5

Safe to merge. The change is additive and well-scoped: per-user localStorage preference is isolated from the existing auto-detect fallback, every signing surface now persists the chosen extension before calling the broadcast path, and all routing changes are covered by new or updated tests.

All three signing surfaces (KeyOrHot, WalletOperationSign, AuthUpgradeDialog) correctly persist the extension choice per username before triggering the broadcast. The previous review finding about the single-extension path in AuthUpgradeDialog not persisting is addressed. The resolveKeychainInstance routing is deterministic (keychain → real Keychain, never Keeper alias), and the stale-preference self-healing logic carries over to the per-user map. No data-loss or wrong-wallet paths were found.

No files require special attention.

Important Files Changed

Filename Overview
apps/web/src/utils/hive-extensions.ts Core of the PR: adds per-username preference map, readPreferenceMap() with value validation, resolveKeychainInstance(), and updates signBufferWithExtension/broadcastWithExtension to pass account to getPreferredExtensionId. Well-structured with stale-preference self-healing.
apps/web/src/utils/keychain.ts All window.hive_keychain reads replaced with resolveKeychainInstance(account), correctly routing legacy helpers to the user's chosen extension.
apps/web/src/features/shared/key-or-hot/index.tsx Adds choosing state and ExtensionChooser; filters Peak Vault for owner authority; re-reads extensions at click time; properly persists per-username choice before calling onKc.
apps/web/src/features/wallet/operations/wallet-operations-sign.tsx Adds unified extension picker (choosing state, ExtensionChooser, handleExtensionSign/handleChooseExtension) mirroring KeyOrHot. Wallet ops are active-authority so no Peak Vault filtering needed.
apps/web/src/features/shared/auth-upgrade/auth-upgrade-dialog.tsx Addresses previous review comment: single-extension path now persists the lone extension per-username before resolveAuthUpgrade. Peak Vault correctly filtered for owner-authority requests.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User clicks Sign with Extension] --> B{Re-read getDetectedExtensions at click time}
    B -->|0 extensions| C[Sign via Keychain Mobile / in-app path]
    B -->|1 extension| D[Persist ext for username\nsetPreferredExtensionId]
    D --> E[Call onKc / doExtensionSign]
    B -->|2+ extensions| F[setChoosing true\nShow ExtensionChooser]
    F --> G{authority === owner?}
    G -->|Yes| H[Filter out Peak Vault\nfrom chooser list]
    G -->|No| I[Show all detected extensions]
    H --> J[User picks extension]
    I --> J
    J --> K[setChoosing false\nPersist ext for username]
    K --> E
    E --> L{Broadcast adapter resolves extension}
    L --> M[getPreferredExtensionId username]
    M -->|keychain| N[getKeychainInstance\nnever Keeper alias]
    M -->|hive-keeper| O[getHiveKeeperInstance\nwindow.hive]
    M -->|peakvault| P[getPeakVaultInstance]
    M -->|null| Q[Keeper-first auto-detect fallback]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[User clicks Sign with Extension] --> B{Re-read getDetectedExtensions at click time}
    B -->|0 extensions| C[Sign via Keychain Mobile / in-app path]
    B -->|1 extension| D[Persist ext for username\nsetPreferredExtensionId]
    D --> E[Call onKc / doExtensionSign]
    B -->|2+ extensions| F[setChoosing true\nShow ExtensionChooser]
    F --> G{authority === owner?}
    G -->|Yes| H[Filter out Peak Vault\nfrom chooser list]
    G -->|No| I[Show all detected extensions]
    H --> J[User picks extension]
    I --> J
    J --> K[setChoosing false\nPersist ext for username]
    K --> E
    E --> L{Broadcast adapter resolves extension}
    L --> M[getPreferredExtensionId username]
    M -->|keychain| N[getKeychainInstance\nnever Keeper alias]
    M -->|hive-keeper| O[getHiveKeeperInstance\nwindow.hive]
    M -->|peakvault| P[getPeakVaultInstance]
    M -->|null| Q[Keeper-first auto-detect fallback]
Loading

Reviews (3): Last reviewed commit: "fix: persist the lone extension on the a..." | Re-trigger Greptile

Comment thread apps/web/src/utils/hive-extensions.ts
feruzm added 2 commits June 22, 2026 05:53
…der test)

- KeyOrHot excludes Peak Vault from the chooser and the single-extension path for
  owner-authority flows (Peak Vault can't sign owner ops; broadcastWithExtension
  rejects them), at both render and click time. Also label account-recovery's
  signer authority="owner" (change_recovery_account is owner-only per the SDK
  authority map) so the same filter applies there.
- Validate stored preference values against the known extension-id union on read
  (per-user map and legacy global) so a corrupted or hand-edited localStorage
  entry never routes signing; it falls through to auto-detect.
- Regression test now asserts the chosen extension is persisted BEFORE onKc via
  invocationCallOrder; add owner Peak-Vault-exclusion and corrupted-value tests.
…path

The auth-upgrade dialog persisted the choice only when the user went through the
chooser; its single-extension direct path resolved without saving, so a
Keychain-only user who later installed Keeper could be hijacked on the next
active-authority sign. Persist the lone extension there too, matching KeyOrHot
and WalletOperationSign, and exclude Peak Vault for owner-authority requests (the
dialog can be invoked for owner ops such as change_recovery_account).
@feruzm

feruzm commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

Good catch, closed in this push. The auth-upgrade dialog's single-extension path now persists the lone extension before resolving (setPreferredExtensionId(username, detectedExtensions[0].id)), matching KeyOrHot and WalletOperationSign, so a Keychain-only user who later installs Keeper won't be hijacked on the next sign. I also excluded Peak Vault from that dialog's chooser for owner-authority requests, since it can be invoked for owner ops (e.g. change_recovery_account). Added a single-extension-persist test.

@feruzm
feruzm merged commit f2d0ef2 into develop Jun 22, 2026
4 checks passed
@feruzm
feruzm deleted the feat/sign-with-extension-picker branch June 22, 2026 06:21
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.

1 participant