Skip to content

Import hardware wallet via QR#1093

Draft
jinhojang6 wants to merge 8 commits intomainfrom
wallet/hardware-wallet
Draft

Import hardware wallet via QR#1093
jinhojang6 wants to merge 8 commits intomainfrom
wallet/hardware-wallet

Conversation

@jinhojang6
Copy link
Copy Markdown
Collaborator

@jinhojang6 jinhojang6 commented Apr 6, 2026

Summary

Adds a new onboarding entry point that lets users import any ERC-4527 compatible air-gapped wallet by scanning an animated QR. The signing flow is out of scope for this PR.

  • New route /onboarding/import-hardware with an inline jsQR + UR fountain decoder, plus a confirm step that names the wallet
  • New tRPC procedure wallet.importHardware and a hardware-qr wallet type in WalletMeta. getSigningKey throws WALLET_IS_WATCH_ONLY for these
  • Entry buttons added to /onboarding and /onboarding/import
  • Built on top of @qrkit/core + @qrkit/react + @qrkit/bc-ur based on the guidelines from the Keycard team
Screenshot 2026-04-06 at 10 41 40 PM Screenshot 2026-04-06 at 10 41 52 PM Screenshot 2026-04-06 at 10 42 52 PM

How to test

You don't need a physical device. The AirGap Vault mobile app (iOS / Android) can simulate any air-gapped wallet:

  1. Install AirGap Vault (or any hardware wallet imported mobile app), add an Ethereum account.
  2. In the wallet extension: Onboarding → Hardware wallet (QR).
  3. In AirGap Vault: tap the account → Connect to wallet → choose the Metamask QR option. A QR appears.
  4. Point your laptop camera at the phone. The the address appears, name the wallet, and import.

Relevant issue

#929

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 6, 2026

🦋 Changeset detected

Latest commit: b3bc808

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
wallet Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
status-api Ready Ready Preview, Comment Apr 17, 2026 10:56am
status-components Ready Ready Preview, Comment Apr 17, 2026 10:56am
status-network-hub Ready Ready Preview, Comment Apr 17, 2026 10:56am
status-network-website Ready Ready Preview, Comment Apr 17, 2026 10:56am
status-portfolio Ready Ready Preview, Comment Apr 17, 2026 10:56am
status-website Ready Ready Preview, Comment Apr 17, 2026 10:56am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
community-dapp-new Ignored Ignored Preview Apr 17, 2026 10:56am

Request Review

@status-im-auto
Copy link
Copy Markdown
Member

status-im-auto commented Apr 6, 2026

Jenkins Builds

Click to see older builds (4)
Commit #️⃣ Finished (UTC) Duration Platform Result
✔️ 8069336 1 2026-04-06 13:27:59 ~1 min wallet 📦zip
✔️ 49abb3c 2 2026-04-06 13:45:09 ~1 min wallet 📦zip
✔️ 41f5f03 3 2026-04-08 13:30:51 ~1 min wallet 📦zip
✔️ f790efc 4 2026-04-16 19:16:41 ~2 min wallet 📦zip
Commit #️⃣ Finished (UTC) Duration Platform Result
✔️ 7307342 5 2026-04-16 19:39:08 ~1 min wallet 📦zip
✔️ 7307342 5 2026-04-16 19:39:52 ~2 min connector 📦zip
✔️ b3bc808 6 2026-04-17 10:56:25 ~1 min wallet 📦zip

@jinhojang6 jinhojang6 changed the title feat(wallet): import hardware wallet via QR Import hardware wallet via QR Apr 8, 2026
@jinhojang6 jinhojang6 force-pushed the wallet/hardware-wallet branch from 49abb3c to 41f5f03 Compare April 8, 2026 13:28
@mmlado
Copy link
Copy Markdown

mmlado commented Apr 9, 2026

Really nice work on this. The flow is clean, the hook usage is correct, and handling the multi-account case with the account select screen is exactly the right approach.

A few things that might be useful.

The @qrkit/core>@noble/secp256k1 override in your pnpm config came from @qrkit/core. It was pulling in @noble/secp256k1 alongside your existing deps. Just released 0.4.1 that replaces it with @noble/curves, which @scure/bip32 already brings in transitively. After upgrading you can remove the override.

account.device carries the name the hardware wallet reports (e.g. "Keycard Shell", "GapSign"). You could use it to pre-fill the wallet name input and the vendor field instead of defaulting to "Hardware Wallet" / "air-gapped". Small thing but it's a nicer experience when the wallet already knows its own name.

If you want to simplify the camera side a bit, useQRScanner from @qrkit/react wraps the whole useEffect with getUserMedia, requestAnimationFrame, jsQR, and the cleanup. It takes a videoRef and calls receivePart for you, leaving just the useURDecoder setup and the onScan callback. You could also drop jsqr from your direct deps since it's bundled inside @qrkit/react. Either way the current approach works perfectly fine.

On the animated QR latch, useURDecoder already guards against onScan firing twice internally, and your done = true flag handles the loop side correctly. Just flagging it since it's a common gotcha with animated QRs.

One more thing for later: since you're filtering on chains: ['evm'] it won't affect you now, but @qrkit/core also derives BTC addresses from crypto-multi-accounts exports. When you add BTC support, chains: ['evm', 'btc'] is all you need.

@mmlado
Copy link
Copy Markdown

mmlado commented Apr 17, 2026

Great work overall. The flow hangs together well and useQRScanner + parseConnection are wired up exactly as intended.

One thing worth fixing before merge: the importHardware mutation hardcodes derivationPath: "m/44'/60'/0'/0/0" for every imported account, but parseConnection already returns the correct path per account on the Account object. Pass account.derivationPath through to importHardware and store it. Otherwise Ledger Live accounts (which use m/44'/60'/x'/0/0) will store the wrong path, which will silently break once the signing flow is wired up.

Also a small one: the "@qrkit/core>@noble/secp256k1" override in package.json can be removed. @qrkit/core no longer depends on @noble/secp256k1 at all (replaced with @noble/curves), so the override has no effect.

@jinhojang6
Copy link
Copy Markdown
Collaborator Author

jinhojang6 commented Apr 17, 2026

@mmlado Thanks for the review!

  • Override: done. bumped @qrkit/core and @qrkit/react to ^0.4.1 and removed the override.

-derivationPath: I tried this, but parseConnection returnsEvmAccount[] when filtered by chains: ['evm'] and EvmAccount exposes address, publicKey, sourceFingerprint, etc. without derivationPath. Am I missing something?

@mmlado
Copy link
Copy Markdown

mmlado commented Apr 17, 2026

-derivationPath: I tried this, but parseConnection returnsEvmAccount[] when filtered by chains: ['evm'] and EvmAccount exposes address, publicKey, sourceFingerprint, etc. without derivationPath. Am I missing something?

My bad on the derivationPath comment. I was sure it was already there, clearly wasn't. Sorry for the noise.

Good news: it has shipped in @qrkit/core@0.5.0. Both EvmAccount and BtcAccount now expose derivationPath, reconstructed from the origin keypath in the QR plus the derived /0/0 suffix. You can replace the hardcoded "m/44'/60'/0'/0/0" with account.derivationPath once you bump the dependency.

@mmlado
Copy link
Copy Markdown

mmlado commented Apr 17, 2026

One small thing. This is adding a new feature, hardware wallet import via QR, new route, new tRPC procedure, new wallet type. A minor bump in version in changeset might be warranted instead of the patch.

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

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

3 participants