Skip to content

Conversation

@n13
Copy link
Collaborator

@n13 n13 commented Jan 8, 2026

Overview

  • Improvements to multiple account handling (actually works now)
  • UX for add hardware wallet - feature flag protected
  • UX for multiple mnemonics in settings
  • UX for add / import additional wallets
  • Send overlay for scanning QR codes in the case of hardware wallet
  • Testing facilities to test partially signed transactions
  • Added UR scanning library in Rust - UR is a standard for moving QR codes

Demo

Demo: https://youtu.be/r1nDWzD6-x8

Caveats

The send overlay is super complicated now, follow up PR will refactor this to make it smaller and factor our things into a hardware wallet service.

Will be fixed here:
#356

n13 added 30 commits December 14, 2025 16:53
this helps the hardware wallet.
there may be another way to do this but for now dummy signature works well.
upgrade dependencies to pointy castle 4.0.0 to be compatible with the latest polkadart version.
also upgrade human checksum dart to v1.1.0 tag so we are compatible with pointycastle.
The HW wallet will need to blake2 the output before signing.
These are useful as a reference implementation
removed uneeded file, fix doc comment, formatting
Alles was nicht erlaubt ist ist verboten!
Item(value: _WalletMoreAction.importWallet, label: 'Import wallet'),
];

if (FeatureFlags.isFeatureEnabled('keystone_hardware_wallet')) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's make the flag an enum

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I just wanted to fit in the system. As I am concerned we could just have some booleans. Not sure why we have this fancy system with strings.

Not related to this PR - should be follow up PR refactor.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Okay, we can do either way, using string just ain't safe enough

@@ -1,5 +1,6 @@
class AppConstants {
static const globalDebug = false;
static const debugHardwareWallet = false;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this used at all?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes. Not sure we should leave this debug code in there now that it works. Hmm...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Moved this to the end of the file and added a comment since this is quite intricate.

@dewabisma
Copy link
Collaborator

dewabisma commented Jan 8, 2026

GEMINI PRO 3 REVIEW

The branch n13/hardware_wallet_support implements comprehensive support for Keystone hardware wallets using an air-gapped QR code flow. The changes span the Flutter mobile app, the Dart SDK, and the Rust bridge.

Summary of Changes

  1. Mobile App UI (mobile-app/lib/features/main/screens/...):

    • New Screens: AddHardwareAccountScreen for importing hardware accounts via QR code.
    • Updated Flows: AccountsScreen now handles multiple wallets and grouping by wallet index. Added SelectWalletForRecoveryPhraseScreen to handle multiple wallets.
    • Signing Flow: SendProgressOverlay now supports a hardware signing state (hardwareSign, hardwareScan). It displays the unsigned transaction as a QR code (UR encoded) and scans the signature back.
    • Settings: Added "Disconnect Wallet" functionality in AccountSettingsScreen.
  2. SDK Core (quantus_sdk/lib/src/...):

    • QuantusPayloadParser: New class to parse SCALE-encoded transaction payloads into human-readable details (toAddress, amount, isReversible), essential for the hardware wallet display.
    • QuantusSigningPayload: Extends SigningPayload to ensure the raw payload is returned (not hashed) for hardware signing, matching the polkadart update.
    • UR Support: Added ur.rs (Rust) and corresponding Dart bindings to handle Uniform Resources (UR) encoding/decoding for QR codes.
  3. Rust Bridge (quantus_sdk/rust/...):

    • Exposed crypto constants (public_key_bytes, etc.).
    • Added ur.rs wrapping quantus-ur for efficient QR encoding.

Code Review

Positives:

  • Architecture: Logic for parsing payloads and handling UR encoding is cleanly separated in the SDK and Rust layer.
  • Safety: The feature is correctly flagged behind keystone_hardware_wallet in feature_flags.dart, ensuring it doesn't leak into production before readiness.
  • Testing: Added unit tests for the Rust UR implementation (ur.rs) and payload parser.

Issues / Suggestions:

  1. Unused Import:
    In mobile-app/lib/features/main/screens/send/qr_scanner_screen.dart, line 2:

    import 'package:quantus_sdk/quantus_sdk.dart' as crypto;

    This alias crypto appears to be unused, as the file also imports the package without an alias.

  2. Debug Code:
    In mobile-app/lib/features/main/screens/add_hardware_account_screen.dart, _fillDebugAddress contains a hardcoded address. It is correctly guarded by if (kDebugMode) in the build method, so this is acceptable for development but good to be aware of.

  3. Code Consistency:
    In SendProgressOverlay.dart, the check if (account.accountType == AccountType.keystone || AppConstants.debugHardwareWallet) correctly redirects the flow. The debug simulation button is also correctly guarded by AppConstants.debugHardwareWallet.

Recommendation

The implementation looks solid. I recommend removing the unused import in qr_scanner_screen.dart before merging.

The branch n13/hardware_wallet_support implements hardware wallet (Keystone) support with a new QR-code-based signing flow.

Key Changes

  • Mobile App:
    • New Screens: AddHardwareAccountScreen for importing wallets via QR and SendProgressOverlay updates for the "sign and scan" flow.
    • Multi-wallet Support: AccountsScreen now groups accounts by wallet index.
    • Feature Flag: Protected by keystone_hardware_wallet.
  • SDK:
    • Payload Parsing: QuantusPayloadParser decodes SCALE payloads for display on the hardware device.
    • UR Support: New Rust bridge (ur.rs) for Uniform Resources QR encoding/decoding.

Review Actions

I found one minor issue and fixed it:

  • Removed unused import: import 'package:quantus_sdk/quantus_sdk.dart' as crypto; in qr_scanner_screen.dart was redundant.

The rest of the code looks well-structured, with clear separation between UI, SDK logic, and Rust crypto primitives. The feature is safely flagged for incremental rollout.

Copy link
Collaborator

@dewabisma dewabisma left a comment

Choose a reason for hiding this comment

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

I have manually reviewed it and also use gemini pro 3 to review.

@n13 n13 requested a review from dewabisma January 8, 2026 06:28
}

int getSelectedWalletIndex(List<Account> accounts) {
final selectedWallet = _selectedWalletIndex ?? (accounts.isNotEmpty ? accounts.first.walletIndex : 0);
Copy link
Collaborator

@dewabisma dewabisma Jan 8, 2026

Choose a reason for hiding this comment

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

I mean can we not use ternary? We can do if else instead.

if (_selectedWalletIndex != null) {
return _selectedWalletIndex
} else if (accounts.isNotEmpty) {
return accounts.first.walletIndex
} else {
return 0
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

whats all this hate for ternary?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I mean to me this if statement is less clear than the original... I have to think about it more

The original is simply this

if it's defined, return it

if it's not defined, return some sort of reasonable default...

if there are wallets, return the first wallet index

if not, return 0 - but this case never happens here, thats just there to make this function return an int thats' not optional.

Copy link
Collaborator

@dewabisma dewabisma left a comment

Choose a reason for hiding this comment

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

LGTM! Just have one non blocking request for change

@n13 n13 merged commit af9dedd into main Jan 8, 2026
1 check passed
@dewabisma dewabisma deleted the n13/hardware_wallet_support branch January 9, 2026 04:13
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.

3 participants