Skip to content

Conversation

mikesposito
Copy link
Member

@mikesposito mikesposito commented Jun 25, 2025

Explanation

Dependent on:

This PR removes all code related to the QRKeyring from KeystoneHQ, which we intend to deprecate in favor of the new QRKeyring implementation in the MetaMask accounts monorepo, that fully supports our Keyring type.

References

Changelog

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed, highlighting breaking changes as necessary
  • I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes

@mikesposito mikesposito requested review from a team as code owners June 25, 2025 11:26
@mikesposito mikesposito marked this pull request as draft June 25, 2025 11:31
@mikesposito
Copy link
Member Author

@metamaskbot publish-preview

Copy link
Contributor

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/account-tree-controller": "0.4.0-preview-d7c00b1",
  "@metamask-previews/accounts-controller": "31.0.0-preview-d7c00b1",
  "@metamask-previews/address-book-controller": "6.1.0-preview-d7c00b1",
  "@metamask-previews/announcement-controller": "7.0.3-preview-d7c00b1",
  "@metamask-previews/app-metadata-controller": "1.0.0-preview-d7c00b1",
  "@metamask-previews/approval-controller": "7.1.3-preview-d7c00b1",
  "@metamask-previews/assets-controllers": "69.0.0-preview-d7c00b1",
  "@metamask-previews/base-controller": "8.0.1-preview-d7c00b1",
  "@metamask-previews/bridge-controller": "33.0.1-preview-d7c00b1",
  "@metamask-previews/bridge-status-controller": "32.0.0-preview-d7c00b1",
  "@metamask-previews/build-utils": "3.0.3-preview-d7c00b1",
  "@metamask-previews/chain-agnostic-permission": "1.0.0-preview-d7c00b1",
  "@metamask-previews/composable-controller": "11.0.0-preview-d7c00b1",
  "@metamask-previews/controller-utils": "11.10.0-preview-d7c00b1",
  "@metamask-previews/delegation-controller": "0.5.0-preview-d7c00b1",
  "@metamask-previews/earn-controller": "2.0.0-preview-d7c00b1",
  "@metamask-previews/eip1193-permission-middleware": "1.0.0-preview-d7c00b1",
  "@metamask-previews/ens-controller": "17.0.0-preview-d7c00b1",
  "@metamask-previews/error-reporting-service": "2.0.0-preview-d7c00b1",
  "@metamask-previews/eth-json-rpc-provider": "4.1.8-preview-d7c00b1",
  "@metamask-previews/foundryup": "1.0.0-preview-d7c00b1",
  "@metamask-previews/gas-fee-controller": "24.0.0-preview-d7c00b1",
  "@metamask-previews/json-rpc-engine": "10.0.3-preview-d7c00b1",
  "@metamask-previews/json-rpc-middleware-stream": "8.0.7-preview-d7c00b1",
  "@metamask-previews/keyring-controller": "22.0.2-preview-d7c00b1",
  "@metamask-previews/logging-controller": "6.0.4-preview-d7c00b1",
  "@metamask-previews/message-manager": "12.0.1-preview-d7c00b1",
  "@metamask-previews/multichain-api-middleware": "1.0.0-preview-d7c00b1",
  "@metamask-previews/multichain-network-controller": "0.9.0-preview-d7c00b1",
  "@metamask-previews/multichain-transactions-controller": "3.0.0-preview-d7c00b1",
  "@metamask-previews/name-controller": "8.0.3-preview-d7c00b1",
  "@metamask-previews/network-controller": "24.0.0-preview-d7c00b1",
  "@metamask-previews/notification-services-controller": "12.0.0-preview-d7c00b1",
  "@metamask-previews/permission-controller": "11.0.6-preview-d7c00b1",
  "@metamask-previews/permission-log-controller": "3.0.3-preview-d7c00b1",
  "@metamask-previews/phishing-controller": "12.6.0-preview-d7c00b1",
  "@metamask-previews/polling-controller": "14.0.0-preview-d7c00b1",
  "@metamask-previews/preferences-controller": "18.2.0-preview-d7c00b1",
  "@metamask-previews/profile-sync-controller": "19.0.0-preview-d7c00b1",
  "@metamask-previews/rate-limit-controller": "6.0.3-preview-d7c00b1",
  "@metamask-previews/remote-feature-flag-controller": "1.6.0-preview-d7c00b1",
  "@metamask-previews/sample-controllers": "1.0.0-preview-d7c00b1",
  "@metamask-previews/seedless-onboarding-controller": "1.0.0-preview-d7c00b1",
  "@metamask-previews/selected-network-controller": "23.0.0-preview-d7c00b1",
  "@metamask-previews/signature-controller": "31.0.0-preview-d7c00b1",
  "@metamask-previews/token-search-discovery-controller": "3.3.0-preview-d7c00b1",
  "@metamask-previews/transaction-controller": "58.1.0-preview-d7c00b1",
  "@metamask-previews/user-operation-controller": "37.0.0-preview-d7c00b1"
}

Comment on lines -1884 to -1886
await withController(
// @ts-expect-error QRKeyring is not yet compatible with Keyring type.
{ keyringBuilders: [keyringBuilderFactory(QRKeyring)] },
Copy link
Member Author

Choose a reason for hiding this comment

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

Some of these test scenarios were adding QRKeyring to builders, but without doing anything with it. These changes here may be a little confusing because of indentation change, but the only lines removed should be the ones related to keyringBuilders.

github-merge-queue bot pushed a commit to MetaMask/accounts that referenced this pull request Aug 12, 2025
<!--
Thanks for your contribution! Take a moment to answer these questions so
that reviewers have the information they need to properly understand
your changes:

* What is the current state of things and why does it need to change?
* What is the solution your changes offer and how does it work?

Are there any issues or other links reviewers should consult to
understand this pull request better? For instance:

* Fixes #12345
* See: #67890
-->

This is a new Keyring implementation compatible with QR-based wallets,
meant to replace `@keystonehq/metamask-airgapped-keyring`.

The goals are:
  - Internalize the package
  - Simplify internal logic (e.g. no event-based logic)
- Remove the need of KeyringController special methods (See methods
nuked in MetaMask/core#6031)
- Provide a consumer-injectable transport bridge to allow clients to
control QR scanning implementation
  - Improve API ergonomics
  - Allow clients to easily support multiple QR devices per user

This package can be tested on Extension through this PR:
MetaMask/metamask-extension#33893

* Fixes #144

## How does it work?

The QrKeyring in this package is split into two main components:
- `QrKeyring`: The main keyring class that exposes the Keyring API. Some
of its logic is implemented in internal utility classes:
- `Device`: A stateful internal wrapper for the keyring's public keys
derivation logic and account management. In the code it's implemented by
a separate class, but it can be seen as an internal component of the
keyring, and It is not exported from the package.
- `QrKeyringBridge`: The interface that a transport bridge must
implement: it allows the client to control how QR codes are scanned.
It's supposed to be implemented by a class that is injected by the
client: when using KeyringController, it can be injected through a
custom keyring builder (See [this keyring
builder](https://github.com/MetaMask/metamask-extension/blob/75576f2d31432563e34fbaa66c7783b77aa5124d/app/scripts/lib/qr-keyring-builder-factory.ts)
that will be used by Extension).
- `QrKeyringScannerBridge`: A generic bridge that allows the client to
pass function hooks at construction time, which will be called when the
keyring needs to scan a QR code.

### Device pairing flow

Pairing is the process of initializing the keyring with a device, which
is done by submitting the device details to the keyring in the form of a
serialized UR, which usually comes from the "sync" QR scanned on the
device. The client can trigger this process in three ways:
- At construction time, by passing a `SerializedUR` object to the
`QrKeyring` constructor.
- In this case, the scan is done before the keyring is constructed, and
thus the keyring will directly receive the QR contents.
- By calling the `QrKeyring.pairDevice()` method, which allows the
client to pass a `SerializedUR` object at any time.
- Same as above, the scan is done outside the keyring, and the keyring
will directly receive the QR contents.
- By calling the `QrKeyring.getFirstPage()` method, which will trigger a
scan request if no device is paired yet.
- In this case, the keyring will request a scan through the bridge, and
the client will implement the scan logic.

#### Extension uses `getFirstPage()` to trigger the pairing flow, which
will then request a scan through the bridge

Like for other Hardare wallets, the pairing flow is initiated in
[`connectHardware()`](https://github.com/MetaMask/metamask-extension/blob/75576f2d31432563e34fbaa66c7783b77aa5124d/app/scripts/metamask-controller.js#L5191)
method. See the sequence diagram below for the pairing flow on
Extension, and see [the Extension `requestScan`
callback](https://github.com/MetaMask/metamask-extension/blob/75576f2d31432563e34fbaa66c7783b77aa5124d/app/scripts/metamask-controller.js#L1112)
for the implementation of the scan logic.

```mermaid
sequenceDiagram
        autonumber
        participant A as MM Extension
        create participant B as QrKeyring
        A->>B: getFirstPage()
        alt if not initialized
        create participant C as Bridge
        B->>C: requestScan(PAIRING)
        C-->>+A: 
        note over A,C: The client controls the bridge and implements the scan logic
        A-->>-C: 
        destroy C
        C->>B: return CBOR 
        create participant D as Int. derivation logic
        B->>D: init(CBOR)
        end
        B->>D: accountAtIndex(0..4)
        destroy D
        D->>B: return accounts
        destroy B
        B->>A: return accounts
```

#### Mobile

TBD - Likely using `.pairDevice()` given the current implementation of
QR interactions on mobile.

### Signing flow

```mermaid
sequenceDiagram
        autonumber
        participant A as Client
        create participant B as QrKeyring
        A->>B: sign{Transaction, PersonalMessage, TypedData}()
        alt not initialized
        B->>A: Fail
        end
        create participant C as Bridge
        B->>C: requestScan(SIGN)
        C-->>+A: 
        note over A,C: The client controls the bridge and implements the scan logic
        A-->>-C: 
        destroy C
        C->>B: return signature CBOR 
        destroy B
        B->>A: return hex signature
```

### Account addition flow
No scan required after the initial pairing.

```mermaid
sequenceDiagram
        autonumber
        participant A as Client
        alt non sequential account addition 
        create participant B as QrKeyring
        A->>B: setAccountToUnlock( i )
        end
        A->>B: addAccounts( n )
        loop n-times
        create participant D as Int. derivation logic
        B->>D: accountAtIndex( i )
        destroy D
        D->>B: return account
        end
        B->>A: return created accounts
```


## Examples

<!--
Are there any examples of this change being used in another repository?

When considering changes to the MetaMask module template, it's strongly
preferred that the change be experimented with in another repository
first. This gives reviewers a better sense of how the change works,
making it less likely the change will need to be reverted or adjusted
later.
-->
@mikesposito mikesposito marked this pull request as ready for review August 19, 2025 11:09
@mikesposito mikesposito requested a review from a team as a code owner August 19, 2025 11:09
Copy link

socket-security bot commented Aug 19, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​types/​lodash@​4.17.20 ⏵ 4.17.71001007887100

View full report

Copy link
Contributor

@mcmire mcmire left a comment

Choose a reason for hiding this comment

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

Woohoo, nice!

@mikesposito mikesposito enabled auto-merge (squash) August 20, 2025 09:33
@mikesposito mikesposito merged commit 2dac0ae into main Aug 20, 2025
227 checks passed
@mikesposito mikesposito deleted the mikesposito/chore/remove-qr-related-code branch August 20, 2025 09:39
github-merge-queue bot pushed a commit to MetaMask/metamask-extension that referenced this pull request Sep 1, 2025
<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->
dependent on:
  - ~~MetaMask/accounts#60
  - ~~MetaMask/core#6031

## **Description**

<!--
Write a short description of the changes included in this pull request,
also include relevant motivation and context. Have in mind the following
questions:
1. What is the reason for the change?
2. What is the improvement/solution?
-->
This PR uses the `@metamask/eth-qr-keyring` package to handle QR code
keyring functionality, replacing the previous implementation from
`@keystonehq/metamask-airgapped-keyring`.

The change should be transparent to users, as the package is a drop-in
replacement. Though, under the hood, it allows to greatly simplify code
related to QRKeyring-related scanning and signing, and should improve
the overall ergonomics of interactions with QRKeyring.

Moreover, this PR adds the first e2e tests for QR code hardware wallets,
covering:
  - Pairing a QR code hardware wallet
  - Adding multiple accounts from the QR code hardware wallet

[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/33893?quickstart=1)

## **Related issues**

Fixes:

## **Manual testing steps**

### **Does it work with new HW devices?**
1. Checkout to this branch, and run `yarn && yarn webpack` or `yarn &&
yarn start` to build the extension.
2. Install the extension in your browser, and pair a QR-based hardware
wallet (e.g. a Keystone device, or install AirGap Vault or imToken on an
iOS/Android device).
3. Add some (3) accounts to the QRKeyring, and ensure that you can see
them in the extension.
4. Go to `https://metamask.github.io/test-dapp/`, connect to the
extension.
5. Ensure that you can:
  - Sign transactions
  - Sign typed data
  - Sign personal messages
6. Remove one QR account
7. Forget the QR device

### **Does it work with HW devices already paired?**
1. Checkout to main, and run `yarn && yarn webpack` or `yarn && yarn
start` to build the extension.
2. Install the extension in your browser, and pair a QR-based hardware
wallet (e.g. a Keystone device, or install AirGap Vault on an
iOS/Android device).
3. Add some (3) accounts to the QRKeyring, and ensure that you can see
them in the extension.
4. Checkout to this branch, and run `yarn && yarn webpack` or `yarn &&
yarn start` to build the extension.
5. Go to `chrome://extensions/`, and reload the extension.
6. Login to the extension, and ensure that you can see the same accounts
as before.

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->

## **Pre-merge author checklist**

- [ ] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.

---------

Co-authored-by: MetaMask Bot <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Refactor keyring-controller to remove QR keyring methods
2 participants