Skip to content

Comments

fix: enable P/X chain support for Keystone QR code connections#709

Open
MoonBoi9001 wants to merge 7 commits intoava-labs:mainfrom
MoonBoi9001:fix/708-keystone-qr-avalanche-xp-keys
Open

fix: enable P/X chain support for Keystone QR code connections#709
MoonBoi9001 wants to merge 7 commits intoava-labs:mainfrom
MoonBoi9001:fix/708-keystone-qr-avalanche-xp-keys

Conversation

@MoonBoi9001
Copy link

@MoonBoi9001 MoonBoi9001 commented Jan 19, 2026

Summary

Closes #708

The QR code scanner was only extracting the first key from Keystone devices, ignoring the Avalanche X/P key (44'/9000'/0') even when present in Keystone 3 Pro QR data. This caused P/X chain operations to fail with "network not supported" errors.

  • Extract all keys from QR codes, not just the first one
  • Parse both EVM (44'/60') and Avalanche (44'/9000') derivation paths
  • Set QR code as the default connection method
  • Update UI text to clarify device support and add i18n
  • Reorder dropdown to show QR option first

Gracefully falls back to EVM-only support if the QR code doesn't include an Avalanche key.

🤖 Generated with Claude Code

The QR code scanner was only extracting the first key, ignoring the
Avalanche X/P key (44'/9000'/0') even when present in Keystone 3 Pro
QR data. This caused P/X chain operations to fail with "network not
supported" errors for users connecting via QR code.

Changes:
- Extract all keys from QR codes, not just the first one
- Parse both EVM (44'/60') and Avalanche (44'/9000') derivation paths
- Set QR code as the default connection method (recommended for all devices)
- Update UI text to clarify device support and add i18n

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@MoonBoi9001
Copy link
Author

@gergelylovas @meeh0w wonder if you can take a look at this pr?

@meeh0w
Copy link
Member

meeh0w commented Jan 30, 2026

@MoonBoi9001 I'll review & test next week 🙏

@MoonBoi9001
Copy link
Author

Amazing! TYVM 🙌

Copy link
Member

@meeh0w meeh0w left a comment

Choose a reason for hiding this comment

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

I only left two comments so far.

I did not manage to test it since my Keystone 3 Pro is now on a beta firmware which...doesn't work very well when trying to onboard using QR codes :)

I've contacted the Keystone team and will recheck this PR once they provide me with an updated firmware version.

Comment on lines 79 to 89
for (const index of startingIndexes) {
const avmKey = await getAddressPublicKeyFromXPub(
extendedPublicKeyHex,
index,
);
keys.push({
index,
vm: 'AVM',
key: buildAddressPublicKey(avmKey, index, 'AVM'),
});
}
Copy link
Member

Choose a reason for hiding this comment

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

In case of EVM this would be correct - you'd be creating multiple accounts from a single XPUB (m/44'/60'/0').

In case of X/P chains, though, we want to import minNumberOfKeys extended public keys (one for each account). What you're doing here is creating multiple (minNumberOfKeys) addresses for a single X/P account (i.e. extended public key (`m/44'/9000'/0')).

In Core, the account model for X/P chains looks as follows:

  • Account 1: gets the extended public key for m/44'/9000'/0' and from that we derive a single receive address to display in the UI (m/44'/9000'/0'/0/0).
  • Account 2: gets the extended public key for m/44'/9000'/1' and from that we derive a single receive address to display in the UI (m/44'/9000'/1'/0/0).
  • etc..

However, if the user has funds on other addresses for a given account (e.g. has funds spread across m/44'/9000'/0'/0/0, m/44'/9000'/0'/0/1, ``m/44'/9000'/0'/0/2`) - those funds would still be usable (given an XPUB for the account, we try to discover all addresses with activity/funds on them and make them spendable).

So given an account N, you want to import the following to make X/P chains work:

  • m/44'/9000'/N' (to be able to find all funds under this xpub)
  • m/44'/9000'/N'/0/0 (to be able to receive funds)

Copy link
Author

Choose a reason for hiding this comment

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

Addressed in d259e24

buildExtendedPublicKey(xpub, EVM_BASE_DERIVATION_PATH),
);
evmAddressPublicKeys = await getAddressPublicKeys(xpub);
} else if (path?.includes("44'/9000'")) {
Copy link
Member

Choose a reason for hiding this comment

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

Please:

  1. use constants for the derivation path prefixes
  2. create a util function to recognize keys based on their derivation paths

such that when I read the if statement, I immediately know which key I'm processing without having to memorize the derivation paths for given chains. Example:

if (isEvmXpub(key)) {
  // ...
} else if (isAvalancheXpub(key)) {
  // ...
}
// etc.

Copy link
Author

Choose a reason for hiding this comment

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

Addressed in bbadb7b, with additional cleanup:

  • 2754146: extracted predicates to @core/common
  • a55b65f: return path from getter to avoid non-null assertions
  • 971860a: added unit tests for the shared predicates

MoonBoi9001 and others added 5 commits February 2, 2026 11:43
Each Avalanche X/P xpub represents a distinct account, so derive one
address (index 0) per xpub instead of multiple addresses from a single
xpub. Use existing @core/common utilities for path parsing instead of
hardcoded strings.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Improves readability by using isEvmXpub() and isAvalancheXpub()
helper functions instead of inline startsWith checks.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Moves isEvmDerivationPath and isAvalancheDerivationPath to shared
utilities to eliminate duplication across Keystone handlers.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changes isAvalancheXpub to getAvalanchePath, returning the path string
instead of boolean. This eliminates the need for non-null assertions
when extracting the account index from the path.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests isEvmDerivationPath and isAvalancheDerivationPath functions
following AAA pattern and FIRST principles.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@MoonBoi9001
Copy link
Author

Thanks for the review @meeh0w! The X/P derivation fix makes much more sense now. Also took the opportunity to clean up the code a bit.

Keystone QR codes return paths without the 'm/' prefix (e.g., "44'/60'/0'"
instead of "m/44'/60'/0'"). Updated predicates to accept both formats.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Member

@meeh0w meeh0w left a comment

Choose a reason for hiding this comment

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

Onboarding looks good, but it still doesn't allow for sending these funds using QR codes only.

So far we relied on the presence of Avalanche public keys to determine what kind of device was onboarded:

const hasXPSupport = addressPublicKeys.some(({ derivationPath }) =>
isAvalancheDerivationPath(derivationPath),
);
const walletId = await this.walletService.init({
secretType: hasXPSupport ? SecretType.Keystone3Pro : SecretType.Keystone,
extendedPublicKeys,

Later on we relied on this info (SecretType) to determine if the user should use USB or QR codes to sign transactions.

With your changes we can no longer do that, since QR code will still extract the Avalanche keys, and so the UI will still prompt you to connect the device.

This is a good start (huge thank you! ❤️), but requires more changes.

Personally I would pause for a little bit, since Keystone team is about to release a new version of their firmware as they updated @keystonehq/hw-app-avalanche. With their changes, users will be able to Account 2 (and higher), as those were not usable for now (see #739); and also sign transactions involving multiple addresses from single account.

We also need to rethink how to determine which connection type to use -- should we ask the user each time when they sign a transaction? Or maybe add a setting somewhere in the UI?

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.

QR code connection should extract Avalanche X/P key for full chain support

2 participants