LCW-1128-Add-support-for-Trezor (#2565)

* feat: add support for Trezor hardware wallet integration Restore Wallets * feat: add Trezor hardware wallet support, implement RLP decoding utility, and update transaction signing logic * feat: add bitcoin trezor signing * chore: update Trezor Connect dependency to specific commit hash in pubspec_base.yaml * feat: add trezor support for litecoin * feat: add colorFilter for device manufacturer icons to align with theme styles * refactor: optimize Trezor callback handling * feat: add BitBox and Trezor support for Bitcoin and Litecoin, update transaction signing logic, and improve modularity in hardware wallet services * refactor: simplify constructor assertions in `BaseBottomSheetWidget` and cleanup Trezor chainId handling logic * feat: conditionally render device manufacturers based on platform and hide SeedSigner for now

Konstantin Ullrich committed Oct 11, 2025 at 01:12 UTC 8b76391a2e20316f06203d7eb43b348def161c43
38 files changed +994 -182
assets/images/hardware_wallet/device_trezor_safe_5.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32">
2 + <path fill="#000" fill-rule="evenodd" d="M7 6a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4v12.191a9 9 0 0 1-1.204 4.498l-3.353 5.81A3 3 0 0 1 17.845 30h-3.69a3 3 0 0 1-2.598-1.5l-3.353-5.811A9 9 0 0 1 7 18.19zm4-2a2 2 0 0 0-2 2v12.191a7 7 0 0 0 .937 3.498l3.352 5.81a1 1 0 0 0 .866.501h3.69a1 1 0 0 0 .866-.5l3.352-5.81a7 7 0 0 0 .937-3.5V6a2 2 0 0 0-2-2z" clip-rule="evenodd"/>
3 +</svg>
cw_bitcoin/lib/electrum_wallet.dart
+1 -4
@@ -47,7 +47,6 @@ import 'package:cw_core/unspent_coin_type.dart';
47 import 'package:cw_core/output_info.dart';
48 import 'package:flutter/foundation.dart';
49 import 'package:hive/hive.dart';
50 -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
50 import 'package:mobx/mobx.dart';
51 import 'package:rxdart/subjects.dart';
52 import 'package:sp_scanner/sp_scanner.dart';
@@ -163,7 +162,7 @@ abstract class ElectrumWalletBase
162 [HardwareWalletType? hardwareWalletType]) {
163 switch (network) {
164 case LitecoinNetwork.mainnet:
166 - if (hardwareWalletType == HardwareWalletType.ledger)
165 + if ([HardwareWalletType.ledger, HardwareWalletType.trezor].contains(hardwareWalletType))
166 return Bip44Conf.litecoinMainNet.altKeyNetVer;
167 return null;
168 default:
@@ -1423,8 +1422,6 @@ abstract class ElectrumWalletBase
1422
1423 HardwareWalletService? hardwareWalletService;
1424
1426 - void setLedgerConnection(ledger.LedgerConnection connection) => throw UnimplementedError();
1427 -
1425 Future<BtcTransaction> buildHardwareWalletTransaction({
1426 required List<BitcoinBaseOutput> outputs,
1427 required BigInt fee,
cw_bitcoin/lib/hardware/bitbox_service.dart
+39 -8
@@ -6,7 +6,9 @@ import 'dart:typed_data';
6 import 'package:bitbox_flutter/bitbox_manager.dart';
7 import 'package:bitcoin_base/bitcoin_base.dart';
8 import 'package:blockchain_utils/blockchain_utils.dart';
9 +import 'package:cw_bitcoin/electrum_wallet.dart';
10 import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
11 +import 'package:cw_bitcoin/psbt/transaction_builder.dart';
12 import 'package:cw_bitcoin/psbt/v0_deserialize.dart';
13 import 'package:cw_bitcoin/psbt/v0_finalizer.dart';
14 import 'package:cw_bitcoin/utils.dart';
@@ -17,7 +19,8 @@ import 'package:ledger_bitcoin/psbt.dart';
19 class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWalletService {
20 BitcoinBitboxService(this.manager);
21
20 - final int bitboxCoinType = 0; // https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L40
22 + // https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L40
23 + static const int bitboxCoinType = 0;
24 final BitboxManager manager;
25
26 @override
@@ -25,9 +28,11 @@ class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWal
28 final accounts = <HardwareAccountData>[];
29 final indexRange = List.generate(limit, (i) => i + index);
30
31 + // https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L252
32 + final keyType = 1; // xPUB
33 for (final i in indexRange) {
34 final derivationPath = "m/84'/0'/$i'";
30 - final xPub = await manager.getBTCXPub(bitboxCoinType, derivationPath, 1);
35 + final xPub = await manager.getBTCXPub(bitboxCoinType, derivationPath, keyType);
36 final hd = Bip32Slip10Secp256k1.fromExtendedKey(xPub).childKey(Bip32KeyIndex(0));
37 final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
38
@@ -42,7 +47,6 @@ class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWal
47 return accounts;
48 }
49
45 -
50 @override
51 Future<Uint8List> signTransaction({required String transaction}) async {
52 final psbt = PsbtV2()..deserialize(base64Decode(transaction));
@@ -56,27 +60,31 @@ class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWal
60 }
61
62 @override
59 - Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) async {
60 - throw UnimplementedError(); // ToDo (Konsti)
61 - }
63 + Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) =>
64 + manager.signBTCMessage(bitboxCoinType, derivationPath ?? "m/84'/0'/0'/0/0", message);
65
66 @override
67 Future<Uint8List> getMasterFingerprint() => manager.getMasterFingerprint();
68 }
69
67 -class LitecoinBitboxService extends HardwareWalletService {
70 +class LitecoinBitboxService extends HardwareWalletService with BitcoinHardwareWalletService, LitecoinHardwareWalletService {
71 LitecoinBitboxService(this.manager);
72
73 final BitboxManager manager;
74
75 + // https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L40
76 + static const int bitboxCoinType = 2;
77 +
78 @override
79 Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
80 final accounts = <HardwareAccountData>[];
81 final indexRange = List.generate(limit, (i) => i + index);
82
83 + // https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L252
84 + final keyType = 1; // xPUB
85 for (final i in indexRange) {
86 final derivationPath = "m/84'/2'/$i'";
79 - final xPub = await manager.getBTCXPub(0, derivationPath, 1);
87 + final xPub = await manager.getBTCXPub(bitboxCoinType, derivationPath, keyType);
88 final hd = Bip32Slip10Secp256k1.fromExtendedKey(xPub).childKey(Bip32KeyIndex(0));
89 final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet);
90
@@ -90,4 +98,27 @@ class LitecoinBitboxService extends HardwareWalletService {
98
99 return accounts;
100 }
101 +
102 + @override
103 + Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) =>
104 + manager.signBTCMessage(bitboxCoinType, derivationPath ?? "m/84'/0'/0'/0/0", message);
105 +
106 + @override
107 + Future<String> signLitecoinTransaction({
108 + required List<BitcoinBaseOutput> outputs,
109 + required List<PSBTReadyUtxoWithAddress> inputs,
110 + required Map<String, PublicKeyWithDerivationPath> publicKeys,
111 + }) async {
112 + final psbt = PSBTTransactionBuild(inputs: inputs, outputs: outputs, cwOutputs: []).psbt;
113 +
114 + final signedPsbt = await manager.signBTCPsbt(bitboxCoinType, base64Encode(psbt.asPsbtV0()));
115 + final transactionRes = PsbtV2()
116 + ..deserializeV0(base64Decode(signedPsbt))
117 + ..finalizeV0();
118 +
119 + return hex.encode(transactionRes.extract());
120 + }
121 +
122 + @override
123 + Future<Uint8List> getMasterFingerprint() => manager.getMasterFingerprint();
124 }
cw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dart
+13 -1
@@ -1,5 +1,17 @@
1 import 'dart:typed_data';
2
3 +import 'package:cw_bitcoin/electrum_wallet.dart';
4 +import 'package:bitcoin_base/bitcoin_base.dart';
5 +import 'package:cw_bitcoin/psbt/transaction_builder.dart';
6 +
7 mixin BitcoinHardwareWalletService {
4 - Future<Uint8List> getMasterFingerprint();
8 + Future<Uint8List> getMasterFingerprint() async => Uint8List.fromList([0, 0, 0, 0]);
9 +}
10 +
11 +mixin LitecoinHardwareWalletService on BitcoinHardwareWalletService {
12 + Future<String> signLitecoinTransaction({
13 + required List<BitcoinBaseOutput> outputs,
14 + required List<PSBTReadyUtxoWithAddress> inputs,
15 + required Map<String, PublicKeyWithDerivationPath> publicKeys,
16 + });
17 }
cw_bitcoin/lib/hardware/litecoin_ledger_service.dart
+47 -4
@@ -1,22 +1,26 @@
1 import 'dart:async';
2 +import 'dart:typed_data';
3
4 import 'package:bitcoin_base/bitcoin_base.dart';
5 import 'package:blockchain_utils/blockchain_utils.dart';
6 +import 'package:cw_bitcoin/electrum_wallet.dart';
7 +import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
8 +import 'package:cw_bitcoin/psbt/transaction_builder.dart';
9 import 'package:cw_bitcoin/utils.dart';
10 import 'package:cw_core/hardware/hardware_account_data.dart';
11 import 'package:cw_core/hardware/hardware_wallet_service.dart';
12 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
13 import 'package:ledger_litecoin/ledger_litecoin.dart';
14
11 -class LitecoinLedgerService extends HardwareWalletService {
12 - LitecoinLedgerService(this.ledgerConnection);
15 +class LitecoinLedgerService extends HardwareWalletService with BitcoinHardwareWalletService, LitecoinHardwareWalletService {
16 + LitecoinLedgerService(this.ledgerConnection)
17 + : litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
18
19 final LedgerConnection ledgerConnection;
20 + final LitecoinLedgerApp litecoinLedgerApp;
21
22 @override
23 Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
18 - final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
19 -
24 await litecoinLedgerApp.getVersion();
25
26 final accounts = <HardwareAccountData>[];
@@ -42,4 +46,43 @@ class LitecoinLedgerService extends HardwareWalletService {
46
47 return accounts;
48 }
49 +
50 + @override
51 + Future<String> signLitecoinTransaction({
52 + required List<BitcoinBaseOutput> outputs,
53 + required List<PSBTReadyUtxoWithAddress> inputs,
54 + required Map<String, PublicKeyWithDerivationPath> publicKeys,
55 + }) {
56 +
57 + final readyInputs = <LedgerTransaction>[];
58 + for (final utxo in inputs) {
59 + final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
60 +
61 + readyInputs.add(LedgerTransaction(
62 + rawTx: utxo.rawTx,
63 + outputIndex: utxo.utxo.vout,
64 + ownerPublicKey: Uint8List.fromList(hex.decode(publicKeyAndDerivationPath.publicKey)),
65 + ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
66 + ));
67 + }
68 +
69 + // Check if we have the key to one of the output addresses to hide change on the device
70 + String? changePath;
71 + for (final output in outputs) {
72 + final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()];
73 + if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath;
74 + }
75 +
76 + return litecoinLedgerApp.createTransaction(
77 + inputs: readyInputs,
78 + outputs: outputs
79 + .map((e) => TransactionOutput.fromBigInt((e as BitcoinOutput).value,
80 + Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
81 + .toList(),
82 + changePath: changePath,
83 + sigHashType: 0x01,
84 + additionals: ["bech32"],
85 + isSegWit: true,
86 + useTrustedInputForSegwit: true);
87 + }
88 }
cw_bitcoin/lib/hardware/trezor_service.dart new
+179
@@ -0,0 +1,179 @@
1 +import 'dart:async';
2 +import 'dart:convert';
3 +import 'dart:typed_data';
4 +
5 +import 'package:bitcoin_base/bitcoin_base.dart';
6 +import 'package:blockchain_utils/blockchain_utils.dart';
7 +import 'package:cw_bitcoin/electrum_wallet.dart';
8 +import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
9 +import 'package:cw_bitcoin/psbt/transaction_builder.dart';
10 +import 'package:cw_bitcoin/utils.dart';
11 +import 'package:cw_core/hardware/hardware_account_data.dart';
12 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
13 +import 'package:ledger_bitcoin/psbt.dart';
14 +import 'package:trezor_connect/trezor_connect.dart';
15 +
16 +class BitcoinTrezorService extends HardwareWalletService with BitcoinHardwareWalletService {
17 + BitcoinTrezorService(this.connect);
18 +
19 + final TrezorConnect connect;
20 +
21 + @override
22 + Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
23 + final indexRange = List.generate(limit, (i) => i + index);
24 + final requestParams = <TrezorGetPublicKeyParams>[];
25 +
26 + for (final i in indexRange) {
27 + requestParams.add(TrezorGetPublicKeyParams(path: "m/84'/0'/$i'"));
28 + }
29 +
30 + final accounts = await connect.getPublicKeyBundle(requestParams);
31 +
32 + return accounts?.map((account) {
33 + final hd = Bip32Slip10Secp256k1.fromExtendedKey(account.xpub).childKey(Bip32KeyIndex(0));
34 + final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
35 + return HardwareAccountData(
36 + address: address,
37 + xpub: account.xpub,
38 + accountIndex: account.path[2] - 0x80000000, // unharden the path to get the index
39 + derivationPath: account.serializedPath,
40 + );
41 + }).toList() ??
42 + [];
43 + }
44 +
45 + @override
46 + Future<Uint8List> signTransaction({required String transaction}) async {
47 + final psbt = PsbtV2()..deserialize(base64Decode(transaction));
48 +
49 + final inputs = <TrezorTxInput>[];
50 + final inputCount = psbt.getGlobalInputCount();
51 + for (var i = 0; i < inputCount; i++) {
52 + final inputTxRaw = psbt.getInputNonWitnessUtxo(i);
53 + final inputTx = BtcTransaction.fromRaw(hex.encode(inputTxRaw!));
54 + final inputOutputIndex = psbt.getInputOutputIndex(i);
55 +
56 + final publicKeys = psbt.inputMaps[i].keys.where((e) => e.startsWith("06"));
57 + final pubkey = Uint8List.fromList(hex.decode(publicKeys.first.substring(2)));
58 +
59 + inputs.add(TrezorTxInput(
60 + prevHash: hex.encode(psbt.getInputPreviousTxid(i).reversed.toList()),
61 + prevIndex: inputOutputIndex,
62 + amount: inputTx.outputs[inputOutputIndex].amount.toInt(),
63 + addressPath: psbt.getInputBip32Derivation(i, pubkey)!.$2,
64 + sequence: psbt.getInputSequence(i),
65 + scriptType: "SPENDWITNESS"));
66 + }
67 +
68 + final outputs = <TrezorTxOutput>[];
69 + final outputCount = psbt.getGlobalOutputCount();
70 + for (var i = 0; i < outputCount; i++) {
71 + final script = Script.fromRaw(byteData: psbt.getOutputScript(i));
72 + outputs.add(TrezorTxOutput(
73 + amount: psbt.getOutputAmount(i),
74 + address: script.toAddress(),
75 + scriptType: _getScriptType(script.getAddressType()!)
76 + // ToDo: addressPath: psbt.getOutputBip32Derivation(i, pubkey).$2, // To highlight change outputs
77 + ));
78 + }
79 +
80 + final signedTx = await connect.signTransaction(coin: 'btc', inputs: inputs, outputs: outputs);
81 +
82 + return Uint8List.fromList(BytesUtils.fromHexString(signedTx!.serializedTx));
83 + }
84 +
85 + @override
86 + Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) async {
87 + final sig = await connect.signMessage(derivationPath ?? "m/84'/0'/0'/0/0",
88 + message: hex.encode(message), hex: true);
89 + return base64Decode(sig!.signature);
90 + }
91 +}
92 +
93 +class LitecoinTrezorService extends HardwareWalletService with BitcoinHardwareWalletService, LitecoinHardwareWalletService {
94 + LitecoinTrezorService(this.connect);
95 +
96 + final TrezorConnect connect;
97 +
98 + @override
99 + Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
100 + final indexRange = List.generate(limit, (i) => i + index);
101 + final requestParams = <TrezorGetPublicKeyParams>[];
102 + final xpubVersion = Bip44Conf.litecoinMainNet.altKeyNetVer;
103 +
104 + for (final i in indexRange) {
105 + final derivationPath = "m/84'/2'/$i'";
106 + requestParams.add(TrezorGetPublicKeyParams(path: derivationPath, coin: "LTC"));
107 + }
108 +
109 + final accounts = await connect.getPublicKeyBundle(requestParams);
110 +
111 + return accounts?.map((account) {
112 + final hd = Bip32Slip10Secp256k1.fromExtendedKey(account.xpub, xpubVersion)
113 + .childKey(Bip32KeyIndex(0));
114 +
115 + final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet);
116 + return HardwareAccountData(
117 + address: address,
118 + xpub: account.xpub,
119 + accountIndex: account.path[2] - 0x80000000, // unharden the path to get the index
120 + derivationPath: account.serializedPath,
121 + );
122 + }).toList() ??
123 + [];
124 + }
125 +
126 + @override
127 + Future<String> signLitecoinTransaction({
128 + required List<BitcoinBaseOutput> outputs,
129 + required List<PSBTReadyUtxoWithAddress> inputs,
130 + required Map<String, PublicKeyWithDerivationPath> publicKeys,
131 + }) async {
132 + final readyInputs = inputs
133 + .map((input) => TrezorTxInput(
134 + prevHash: input.utxo.txHash,
135 + prevIndex: input.utxo.vout,
136 + amount: input.utxo.value.toInt(),
137 + addressPath: Bip32PathParser.parse(input.ownerDerivationPath).toList(),
138 + scriptType: "SPENDWITNESS",
139 + ))
140 + .toList();
141 +
142 + final readyOutputs = outputs.map((output) {
143 + final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()];
144 +
145 + return TrezorTxOutput(
146 + amount: output.toOutput.amount.toInt(),
147 + address: maybeChangePath != null
148 + ? null
149 + : output.toOutput.scriptPubKey.toAddress(network: LitecoinNetwork.mainnet),
150 + scriptType: _getScriptType(output.toOutput.scriptPubKey.getAddressType()!),
151 + addressPath: maybeChangePath != null
152 + ? Bip32PathParser.parse(maybeChangePath.derivationPath).toList()
153 + : null,
154 + );
155 + }).toList();
156 +
157 + final signedTx =
158 + await connect.signTransaction(coin: 'LTC', inputs: readyInputs, outputs: readyOutputs);
159 +
160 + return signedTx!.serializedTx;
161 + }
162 +}
163 +
164 +String _getScriptType(BitcoinAddressType addressType) {
165 + switch (addressType) {
166 + case P2pkhAddressType.p2pkh:
167 + return "PAYTOADDRESS";
168 + case P2shAddressType.p2wpkhInP2sh:
169 + return "PAYTOSCRIPTHASH";
170 + case SegwitAddresType.p2tr:
171 + return "PAYTOTAPROOT";
172 + case SegwitAddresType.p2wsh:
173 + return "PAYTOP2SHWITNESS";
174 + case SegwitAddresType.p2wpkh:
175 + return "PAYTOWITNESS";
176 + default:
177 + throw Exception("Unknown Address Type");
178 + }
179 +}
cw_bitcoin/lib/litecoin_wallet.dart
+44 -62
@@ -1,53 +1,53 @@
1 import 'dart:async';
2 import 'dart:convert';
3 -
4 -import 'package:convert/convert.dart' as convert;
3 import 'dart:math';
6 -import 'package:collection/collection.dart';
7 -import 'package:crypto/crypto.dart';
8 -import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
9 -import 'package:cw_core/cake_hive.dart';
10 -import 'package:cw_core/mweb_utxo.dart';
11 -import 'package:cw_core/unspent_coin_type.dart';
12 -import 'package:cw_core/utils/print_verbose.dart';
13 -import 'package:cw_core/node.dart';
14 -import 'package:cw_mweb/mwebd.pbgrpc.dart';
15 -import 'package:fixnum/fixnum.dart';
4 +
5 import 'package:bip39/bip39.dart' as bip39;
6 import 'package:bitcoin_base/bitcoin_base.dart';
7 +import 'package:bitcoin_base/src/crypto/keypair/sign_utils.dart';
8 import 'package:blockchain_utils/blockchain_utils.dart';
9 import 'package:blockchain_utils/signer/ecdsa_signing_key.dart';
10 +import 'package:collection/collection.dart';
11 +import 'package:convert/convert.dart' as convert;
12 +import 'package:crypto/crypto.dart';
13 import 'package:cw_bitcoin/bitcoin_address_record.dart';
14 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
15 +import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
16 import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
17 import 'package:cw_bitcoin/bitcoin_unspent.dart';
18 +import 'package:cw_bitcoin/electrum_balance.dart';
19 +import 'package:cw_bitcoin/electrum_derivations.dart';
20 import 'package:cw_bitcoin/electrum_transaction_info.dart';
21 +import 'package:cw_bitcoin/electrum_wallet.dart';
22 +import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
23 +import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
24 +import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
25 import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
26 +import 'package:cw_bitcoin/psbt/transaction_builder.dart';
27 import 'package:cw_bitcoin/utils.dart';
27 -import 'package:cw_bitcoin/electrum_derivations.dart';
28 -import 'package:cw_core/encryption_file_utils.dart';
28 +import 'package:cw_core/cake_hive.dart';
29 import 'package:cw_core/crypto_currency.dart';
30 +import 'package:cw_core/encryption_file_utils.dart';
31 +import 'package:cw_core/mweb_utxo.dart';
32 +import 'package:cw_core/node.dart';
33 +import 'package:cw_core/output_info.dart';
34 import 'package:cw_core/pending_transaction.dart';
35 import 'package:cw_core/sync_status.dart';
36 import 'package:cw_core/transaction_direction.dart';
33 -import 'package:cw_core/unspent_coins_info.dart';
34 -import 'package:cw_bitcoin/electrum_balance.dart';
35 -import 'package:cw_bitcoin/electrum_wallet.dart';
36 -import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
37 -import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
37 import 'package:cw_core/transaction_priority.dart';
38 +import 'package:cw_core/unspent_coin_type.dart';
39 +import 'package:cw_core/unspent_coins_info.dart';
40 +import 'package:cw_core/utils/print_verbose.dart';
41 import 'package:cw_core/wallet_info.dart';
42 import 'package:cw_core/wallet_keys_file.dart';
41 -import 'package:cw_core/output_info.dart';
43 +import 'package:cw_core/wallet_type.dart';
44 +import 'package:cw_mweb/cw_mweb.dart';
45 +import 'package:cw_mweb/mwebd.pbgrpc.dart';
46 +import 'package:fixnum/fixnum.dart';
47 import 'package:flutter/foundation.dart';
48 import 'package:grpc/grpc.dart';
49 import 'package:hive/hive.dart';
45 -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
46 -import 'package:ledger_litecoin/ledger_litecoin.dart';
50 import 'package:mobx/mobx.dart';
48 -import 'package:cw_core/wallet_type.dart';
49 -import 'package:cw_mweb/cw_mweb.dart';
50 -import 'package:bitcoin_base/src/crypto/keypair/sign_utils.dart';
51 import 'package:pointycastle/ecc/api.dart';
52 import 'package:pointycastle/ecc/curves/secp256k1.dart';
53 import 'package:shared_preferences/shared_preferences.dart';
@@ -144,6 +144,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
144 }
145 });
146 }
147 +
148 late final Bip32Slip10Secp256k1? mwebHd;
149 late final Box<MwebUtxo> mwebUtxosBox;
150 Timer? _syncTimer;
@@ -160,6 +161,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
161 bool get hasRescan => true;
162
163 List<int> get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
164 +
165 List<int> get spendSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
166
167 static Future<LitecoinWallet> create(
@@ -1064,9 +1066,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
1066 tx.isMweb = mwebEnabled;
1067
1068 if (!mwebEnabled) {
1067 - tx.changeAddressOverride =
1068 - (await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(coinTypeToSpendFrom: UnspentCoinType.nonMweb))
1069 - .address;
1069 + tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses)
1070 + .getChangeAddress(coinTypeToSpendFrom: UnspentCoinType.nonMweb))
1071 + .address;
1072 return tx;
1073 }
1074 await waitForMwebAddresses();
@@ -1119,7 +1121,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
1121 bool isRegular = !hasMwebInput && !hasMwebOutput;
1122 bool shouldNotUseMwebChange = isPegIn || isRegular || !hasMwebInput;
1123 tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses)
1122 - .getChangeAddress(coinTypeToSpendFrom: shouldNotUseMwebChange ? UnspentCoinType.nonMweb : UnspentCoinType.any))
1124 + .getChangeAddress(
1125 + coinTypeToSpendFrom:
1126 + shouldNotUseMwebChange ? UnspentCoinType.nonMweb : UnspentCoinType.any))
1127 .address;
1128 if (isRegular) {
1129 tx.isMweb = false;
@@ -1368,16 +1372,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
1372 return false;
1373 }
1374
1371 - LedgerConnection? _ledgerConnection;
1372 - LitecoinLedgerApp? _litecoinLedgerApp;
1373 -
1374 - @override
1375 - void setLedgerConnection(LedgerConnection connection) {
1376 - _ledgerConnection = connection;
1377 - _litecoinLedgerApp = LitecoinLedgerApp(_ledgerConnection!,
1378 - derivationPath: walletInfo.derivationInfo!.derivationPath!);
1379 - }
1380 -
1375 @override
1376 Future<BtcTransaction> buildHardwareWalletTransaction({
1377 required List<BitcoinBaseOutput> outputs,
@@ -1391,38 +1385,26 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
1385 BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
1386 BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
1387 }) async {
1394 - final readyInputs = <LedgerTransaction>[];
1388 + final masterFingerprint =
1389 + await (hardwareWalletService as BitcoinHardwareWalletService).getMasterFingerprint();
1390 +
1391 + final readyInputs = <PSBTReadyUtxoWithAddress>[];
1392 for (final utxo in utxos) {
1393 final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
1394 final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
1395
1399 - readyInputs.add(LedgerTransaction(
1396 + readyInputs.add(PSBTReadyUtxoWithAddress(
1397 + utxo: utxo.utxo,
1398 rawTx: rawTx,
1401 - outputIndex: utxo.utxo.vout,
1402 - ownerPublicKey: Uint8List.fromList(hex.decode(publicKeyAndDerivationPath.publicKey)),
1399 + ownerDetails: utxo.ownerDetails,
1400 ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
1404 - // sequence: enableRBF ? 0x1 : 0xffffffff,
1405 - sequence: 0xffffffff,
1401 + ownerMasterFingerprint:masterFingerprint,
1402 + ownerPublicKey: publicKeyAndDerivationPath.publicKey,
1403 ));
1404 }
1405
1409 - String? changePath;
1410 - for (final output in outputs) {
1411 - final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()];
1412 - if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath;
1413 - }
1414 -
1415 - final rawHex = await _litecoinLedgerApp!.createTransaction(
1416 - inputs: readyInputs,
1417 - outputs: outputs
1418 - .map((e) => TransactionOutput.fromBigInt((e as BitcoinOutput).value,
1419 - Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
1420 - .toList(),
1421 - changePath: changePath,
1422 - sigHashType: 0x01,
1423 - additionals: ["bech32"],
1424 - isSegWit: true,
1425 - useTrustedInputForSegwit: true);
1406 + final rawHex = await (hardwareWalletService as LitecoinHardwareWalletService)
1407 + .signLitecoinTransaction(outputs: outputs, inputs: readyInputs, publicKeys: publicKeys);
1408
1409 return BtcTransaction.fromRaw(rawHex);
1410 }
cw_bitcoin/pubspec.lock
+77 -4
@@ -604,8 +604,8 @@ packages:
604 dependency: "direct main"
605 description:
606 path: "packages/ledger-bitcoin"
607 - ref: "6e6ce17c2ba5cfe3d16a60d53f930db8e827b8f3"
608 - resolved-ref: "6e6ce17c2ba5cfe3d16a60d53f930db8e827b8f3"
607 + ref: dc42621f55702d0732681afc2f90bc0047f26633
608 + resolved-ref: dc42621f55702d0732681afc2f90bc0047f26633
609 url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
610 source: git
611 version: "0.0.3"
@@ -1091,6 +1091,15 @@ packages:
1091 relative: true
1092 source: path
1093 version: "0.0.1"
1094 + trezor_connect:
1095 + dependency: "direct main"
1096 + description:
1097 + path: "."
1098 + ref: "59b3ceec158d3393b20d0c596cab2abcc62ac8e4"
1099 + resolved-ref: "59b3ceec158d3393b20d0c596cab2abcc62ac8e4"
1100 + url: "https://github.com/cake-tech/trezor_connect"
1101 + source: git
1102 + version: "0.0.1"
1103 tuple:
1104 dependency: transitive
1105 description:
@@ -1140,6 +1149,70 @@ packages:
1149 url: "https://github.com/bukata-sa/bc-ur-dart"
1150 source: git
1151 version: "0.1.0"
1152 + url_launcher:
1153 + dependency: transitive
1154 + description:
1155 + name: url_launcher
1156 + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
1157 + url: "https://pub.dev"
1158 + source: hosted
1159 + version: "6.3.2"
1160 + url_launcher_android:
1161 + dependency: transitive
1162 + description:
1163 + name: url_launcher_android
1164 + sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e"
1165 + url: "https://pub.dev"
1166 + source: hosted
1167 + version: "6.3.20"
1168 + url_launcher_ios:
1169 + dependency: transitive
1170 + description:
1171 + name: url_launcher_ios
1172 + sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7
1173 + url: "https://pub.dev"
1174 + source: hosted
1175 + version: "6.3.4"
1176 + url_launcher_linux:
1177 + dependency: transitive
1178 + description:
1179 + name: url_launcher_linux
1180 + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
1181 + url: "https://pub.dev"
1182 + source: hosted
1183 + version: "3.2.1"
1184 + url_launcher_macos:
1185 + dependency: transitive
1186 + description:
1187 + name: url_launcher_macos
1188 + sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f
1189 + url: "https://pub.dev"
1190 + source: hosted
1191 + version: "3.2.3"
1192 + url_launcher_platform_interface:
1193 + dependency: transitive
1194 + description:
1195 + name: url_launcher_platform_interface
1196 + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
1197 + url: "https://pub.dev"
1198 + source: hosted
1199 + version: "2.3.2"
1200 + url_launcher_web:
1201 + dependency: transitive
1202 + description:
1203 + name: url_launcher_web
1204 + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
1205 + url: "https://pub.dev"
1206 + source: hosted
1207 + version: "2.4.1"
1208 + url_launcher_windows:
1209 + dependency: transitive
1210 + description:
1211 + name: url_launcher_windows
1212 + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
1213 + url: "https://pub.dev"
1214 + source: hosted
1215 + version: "3.1.4"
1216 uuid:
1217 dependency: transitive
1218 description:
@@ -1229,5 +1302,5 @@ packages:
1302 source: hosted
1303 version: "2.2.2"
1304 sdks:
1232 - dart: ">=3.7.0-0 <4.0.0"
1233 - flutter: ">=3.27.0"
1305 + dart: ">=3.8.0 <4.0.0"
1306 + flutter: ">=3.29.0"
cw_bitcoin/pubspec.yaml
+5 -1
@@ -49,11 +49,15 @@ dependencies:
49 git:
50 url: https://github.com/cake-tech/ledger-flutter-plus-plugins
51 path: packages/ledger-bitcoin
52 - ref: 6e6ce17c2ba5cfe3d16a60d53f930db8e827b8f3
52 + ref: dc42621f55702d0732681afc2f90bc0047f26633
53 ledger_litecoin:
54 git:
55 url: https://github.com/cake-tech/ledger-flutter-plus-plugins
56 path: packages/ledger-litecoin
57 + trezor_connect:
58 + git:
59 + url: https://github.com/cake-tech/trezor_connect
60 + ref: 59b3ceec158d3393b20d0c596cab2abcc62ac8e4
61 bitbox_flutter:
62 path: ../scripts/bitbox_flutter
63 socks_socket:
cw_core/lib/hardware/device_connection_type.dart
+10 -1
@@ -13,7 +13,7 @@ enum DeviceConnectionType {
13 case HardwareWalletType.bitbox:
14 isSupported = [
15 WalletType.bitcoin,
16 - // WalletType.litecoin,
16 + WalletType.litecoin,
17 WalletType.ethereum,
18 WalletType.polygon
19 ].contains(walletType);
@@ -27,6 +27,15 @@ enum DeviceConnectionType {
27 WalletType.polygon
28 ].contains(walletType);
29 break;
30 + case HardwareWalletType.trezor:
31 + isSupported = [
32 + // WalletType.monero,
33 + WalletType.bitcoin,
34 + WalletType.litecoin,
35 + WalletType.ethereum,
36 + WalletType.polygon,
37 + ].contains(walletType);
38 + break;
39 }
40
41 return isSupported
cw_core/lib/wallet_info.dart
+7 -2
@@ -35,6 +35,8 @@ enum HardwareWalletType {
35 seedsigner,
36 @HiveField(5)
37 keystone,
38 + @HiveField(6)
39 + trezor,
40 }
41
42 @HiveType(typeId: DerivationInfo.typeId)
@@ -234,8 +236,11 @@ class WalletInfo extends HiveObject {
236 return showIntroCakePayCard!;
237 }
238
237 - bool get isHardwareWallet =>
238 - [HardwareWalletType.ledger, HardwareWalletType.bitbox].contains(hardwareWalletType);
239 + bool get isHardwareWallet => [
240 + HardwareWalletType.bitbox,
241 + HardwareWalletType.ledger,
242 + HardwareWalletType.trezor
243 + ].contains(hardwareWalletType);
244
245 DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp);
246
cw_evm/lib/evm_chain_client.dart
-1
@@ -9,7 +9,6 @@ import 'package:cw_core/utils/proxy_wrapper.dart';
9 import 'package:cw_evm/evm_chain_transaction_model.dart';
10 import 'package:cw_evm/evm_chain_transaction_priority.dart';
11 import 'package:cw_evm/evm_erc20_balance.dart';
12 -import 'package:cw_evm/hardware/evm_bitbox_credentials.dart';
12 import 'package:cw_evm/pending_evm_chain_transaction.dart';
13 import 'package:cw_evm/.secrets.g.dart' as secrets;
14 import 'package:flutter/foundation.dart';
cw_evm/lib/evm_chain_wallet.dart
+6 -2
@@ -29,8 +29,9 @@ import 'package:cw_evm/evm_chain_transaction_history.dart';
29 import 'package:cw_evm/evm_chain_transaction_model.dart';
30 import 'package:cw_evm/evm_chain_transaction_priority.dart';
31 import 'package:cw_evm/evm_chain_wallet_addresses.dart';
32 -import 'package:cw_evm/hardware/evm_bitbox_credentials.dart';
33 -import 'package:cw_evm/hardware/evm_ledger_credentials.dart';
32 +import 'package:cw_evm/hardware/evm_chain_bitbox_credentials.dart';
33 +import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart';
34 +import 'package:cw_evm/hardware/evm_chain_trezor_credentials.dart';
35 import 'package:hex/hex.dart';
36 import 'package:hive/hive.dart';
37 import 'package:mobx/mobx.dart';
@@ -214,6 +215,9 @@ abstract class EVMChainWalletBase
215 } else if (walletInfo.hardwareWalletType == HardwareWalletType.bitbox) {
216 _evmChainPrivateKey = EvmBitboxCredentials(walletInfo.address);
217 walletAddresses.address = walletInfo.address;
218 + } else if (walletInfo.hardwareWalletType == HardwareWalletType.trezor) {
219 + _evmChainPrivateKey = EvmTrezorCredentials(walletInfo.address);
220 + walletAddresses.address = walletInfo.address;
221 } else {
222 _evmChainPrivateKey = await getPrivateKey(
223 mnemonic: _mnemonic,
cw_evm/lib/hardware/evm_chain_bitbox_credentials.dart renamed
cw_evm/lib/hardware/evm_chain_ledger_credentials.dart renamed
cw_evm/lib/hardware/evm_chain_trezor_credentials.dart new
+108
@@ -0,0 +1,108 @@
1 +import 'dart:async';
2 +import 'dart:typed_data';
3 +
4 +import 'package:cw_core/hardware/device_not_connected_exception.dart' as exception;
5 +import 'package:trezor_connect/trezor_connect.dart';
6 +import 'package:web3dart/crypto.dart';
7 +import 'package:web3dart/web3dart.dart';
8 +
9 +import '../utils/rlp_decode.dart' as rlp;
10 +
11 +class EvmTrezorCredentials extends CredentialsWithKnownAddress {
12 + final String _address;
13 +
14 + TrezorConnect? trezorConnect;
15 + String? derivationPath;
16 +
17 + EvmTrezorCredentials(this._address);
18 +
19 + @override
20 + EthereumAddress get address => EthereumAddress.fromHex(_address);
21 +
22 + void setTrezorConnect(TrezorConnect connection, [String? derivationPath_]) {
23 + trezorConnect = connection;
24 + derivationPath = derivationPath_ ?? "m/44'/60'/0'/0/0";
25 + }
26 +
27 + @override
28 + MsgSignature signToEcSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) =>
29 + throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
30 +
31 + @override
32 + Future<MsgSignature> signToSignature(Uint8List payload,
33 + {int? chainId, bool isEIP1559 = false}) async {
34 + if (trezorConnect == null) {
35 + throw exception.DeviceNotConnectedException();
36 + }
37 +
38 + if (isEIP1559) payload = payload.sublist(1);
39 +
40 + final data = rlp.decode(payload);
41 +
42 + TrezorEthereumTransaction tx;
43 + if (isEIP1559) {
44 + final chainId = data[0][0];
45 + final nonce = bytesToHex(data[1], include0x: true);
46 + final maxPriorityFeePerGas = bytesToHex(data[2], include0x: true);
47 + final maxFeePerGas = bytesToHex(data[3], include0x: true);
48 + final maxGas = bytesToHex(data[4], include0x: true);
49 + final to = bytesToHex(data[5], include0x: true);
50 + final value = bytesToHex(data[6], include0x: true);
51 + final txData = bytesToHex(data[7], include0x: true);
52 +
53 + tx = TrezorEthereumTransaction(
54 + to: to,
55 + value: value,
56 + maxFeePerGas: maxFeePerGas,
57 + maxPriorityFeePerGas: maxPriorityFeePerGas,
58 + gasLimit: maxGas,
59 + nonce: nonce,
60 + chainId: chainId,
61 + data: txData,
62 + );
63 + } else {
64 + final nonce = bytesToHex(data[0]);
65 + final gasPrice = bytesToHex(data[1]);
66 + final maxGas = bytesToHex(data[2]);
67 + final to = bytesToHex(data[3]);
68 + final value = bytesToHex(data[4]);
69 + final txData = bytesToHex(data[5]);
70 + tx = TrezorEthereumTransaction(
71 + to: to,
72 + value: value,
73 + gasPrice: gasPrice,
74 + gasLimit: maxGas,
75 + nonce: nonce,
76 + chainId: chainId ?? 1,
77 + data: txData,
78 + );
79 + }
80 +
81 + final sig = (await trezorConnect!.ethereumSignTransaction(derivationPath!, transaction: tx))!;
82 +
83 + final v = int.parse(strip0x(sig.v), radix: 16);
84 +
85 + if (isEIP1559) {
86 + return MsgSignature(
87 + BigInt.parse(strip0x(sig.r), radix: 16), BigInt.parse(strip0x(sig.s), radix: 16), v);
88 + }
89 +
90 + return MsgSignature(
91 + BigInt.parse(strip0x(sig.r), radix: 16), BigInt.parse(strip0x(sig.s), radix: 16), v);
92 + }
93 +
94 + @override
95 + Future<Uint8List> signPersonalMessage(Uint8List payload, {int? chainId}) async {
96 + if (isNotConnected) throw exception.DeviceNotConnectedException();
97 + return hexToBytes((await trezorConnect!
98 + .ethereumSignMessage(derivationPath!, message: bytesToHex(payload), hex: true))
99 + ?.signature ??
100 + '');
101 + }
102 +
103 + @override
104 + Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
105 + throw UnimplementedError("EvmLedgerCredentials.signPersonalMessageToUint8List");
106 +
107 + bool get isNotConnected => trezorConnect == null;
108 +}
cw_evm/lib/hardware/evm_chain_trezor_service.dart new
+33
@@ -0,0 +1,33 @@
1 +import 'dart:async';
2 +
3 +import 'package:cw_core/hardware/hardware_account_data.dart';
4 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
5 +import 'package:trezor_connect/trezor_connect.dart';
6 +
7 +class EVMChainTrezorService extends HardwareWalletService {
8 + EVMChainTrezorService(this.connect, {this.chainId = 1});
9 +
10 + final TrezorConnect connect;
11 + final int chainId;
12 +
13 + @override
14 + Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
15 + final indexRange = List.generate(limit, (i) => i + index);
16 +
17 + final requestParams = <TrezorGetAddressParams>[];
18 + for (final i in indexRange) {
19 + final derivationPath = "m/44'/60'/$i'/0/0";
20 + requestParams.add(TrezorGetAddressParams(path: derivationPath));
21 + }
22 + final accounts = await connect.ethereumGetAddressBundle(requestParams);
23 +
24 + return accounts
25 + ?.map((account) => HardwareAccountData(
26 + address: account.address,
27 + accountIndex: account.path[2] - 0x80000000, // unharden the path to get the index
28 + derivationPath: account.serializedPath,
29 + ))
30 + .toList() ??
31 + [];
32 + }
33 +}
cw_evm/lib/utils/rlp_decode.dart new
+101
@@ -0,0 +1,101 @@
1 +import 'dart:typed_data';
2 +
3 +import 'package:web3dart/crypto.dart';
4 +
5 +/// RLP Decode
6 +///
7 +/// Adapted from https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/rlp
8 +
9 +class _Decoded {
10 + Uint8List remainder;
11 + List data;
12 +
13 + _Decoded(this.data, this.remainder);
14 +}
15 +
16 +int _decodeLength(Uint8List v) {
17 + if (v[0] == 0) throw Exception('invalid RLP: extra zeros');
18 + return int.parse(bytesToHex(v), radix: 16);
19 +}
20 +
21 +_Decoded _decode(Uint8List input) {
22 + final firstByte = input[0];
23 +
24 + if (firstByte <= 0x7f) {
25 + // a single byte whose value is in the [0x00, 0x7f] range, that byte is its own RLP encoding.
26 + return _Decoded(input.sublist(0, 1), input.sublist(1));
27 + } else if (firstByte <= 0xb7) {
28 + // string is 0-55 bytes long. A single byte with value 0x80 plus the length of the string followed by the string
29 + // The range of the first byte is [0x80, 0xb7]
30 + final length = firstByte - 0x7f;
31 +
32 + // set 0x80 null to 0
33 + final data = firstByte == 0x80 ? Uint8List(0) : input.sublist(1, length);
34 +
35 + if (length == 2 && data[0] < 0x80) {
36 + throw Exception('invalid RLP encoding: invalid prefix, single byte < 0x80 are not prefixed');
37 + }
38 +
39 + return _Decoded(data, input.sublist(length));
40 + } else if (firstByte <= 0xbf) {
41 + // string is greater than 55 bytes long. A single byte with the value (0xb7 plus the length of the length),
42 + // followed by the length, followed by the string
43 + final lLength = firstByte - 0xb6;
44 + if (input.length - 1 < lLength) {
45 + throw Exception('invalid RLP: not enough bytes for string length');
46 + }
47 +
48 + final length = _decodeLength(input.sublist(1, lLength));
49 + if (length <= 55) {
50 + throw Exception('invalid RLP: expected string length to be greater than 55');
51 + }
52 +
53 + final data = input.sublist(lLength, length + lLength);
54 + return _Decoded(data, input.sublist(length + lLength));
55 + } else if (firstByte <= 0xf7) {
56 + // a list between 0-55 bytes long
57 + final length = firstByte - 0xbf;
58 + var innerRemainder = input.sublist(1, length);
59 +
60 + final decoded = [];
61 + while (innerRemainder.isNotEmpty) {
62 + final d = _decode(innerRemainder);
63 + decoded.add(d.data);
64 + innerRemainder = d.remainder;
65 + }
66 +
67 + return _Decoded(decoded, input.sublist(length));
68 + } else {
69 + // a list over 55 bytes long
70 + final lLength = firstByte - 0xf6;
71 +
72 + final length = _decodeLength(input.sublist(1, lLength));
73 + if (length < 56) {
74 + throw Exception('invalid RLP: encoded list too short');
75 + }
76 +
77 + final totalLength = lLength + length;
78 + if (totalLength > input.length) {
79 + throw Exception('invalid RLP: total length is larger than the data');
80 + }
81 +
82 + var innerRemainder = input.sublist(lLength, totalLength);
83 + final decoded = [];
84 + while (innerRemainder.isNotEmpty) {
85 + final d = _decode(innerRemainder);
86 + decoded.add(d.data);
87 + innerRemainder = d.remainder;
88 + }
89 +
90 + return _Decoded(decoded, input.sublist(totalLength));
91 + }
92 +}
93 +
94 +/// RLP Decoding based on https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/
95 +List decode(Uint8List input) {
96 + final decoded = _decode(input);
97 +
98 + if (decoded.remainder.isNotEmpty) throw Exception('invalid RLP: remainder must be zero');
99 +
100 + return decoded.data;
101 +}
cw_evm/pubspec.yaml
+4
@@ -33,6 +33,10 @@ dependencies:
33 ref: f4761cd5171d4c1e2e42fd3298261650539fb2db
34 bitbox_flutter:
35 path: ../scripts/bitbox_flutter
36 + trezor_connect:
37 + git:
38 + url: https://github.com/cake-tech/trezor_connect
39 + ref: 59b3ceec158d3393b20d0c596cab2abcc62ac8e4
40
41 dependency_overrides:
42 web3dart:
cw_evm/test/cw_evm_test.dart
+37 -8
@@ -1,16 +1,45 @@
1 -import 'package:cw_evm/evm_chain_formatter.dart';
2 -import 'package:flutter_test/flutter_test.dart';
1 +import "dart:typed_data";
2 +
3 +import "package:cw_evm/evm_chain_formatter.dart";
4 +import "package:cw_evm/utils/rlp_decode.dart";
5 +import "package:flutter_test/flutter_test.dart";
6 +import "package:web3dart/crypto.dart";
7 +import "package:web3dart/src/utils/rlp.dart" as rlp;
8
9 void main() {
5 - group('EVMChainFormatter', () {
6 - group('truncateDecimals', () {
7 - test('no decimals', () => expect(EVMChainFormatter.truncateDecimals("5", 6), "5"));
8 - test('less than max decimals',
10 + group("EVMChainFormatter", () {
11 + group("truncateDecimals", () {
12 + test("no decimals", () => expect(EVMChainFormatter.truncateDecimals("5", 6), "5"));
13 + test("less than max decimals",
14 () => expect(EVMChainFormatter.truncateDecimals("5.00001", 6), "5.00001"));
10 - test('max decimals',
15 + test("max decimals",
16 () => expect(EVMChainFormatter.truncateDecimals("5.000001", 6), "5.000001"));
12 - test('more than max decimals',
17 + test("more than max decimals",
18 () => expect(EVMChainFormatter.truncateDecimals("5.0000001", 6), "5.000000"));
19 });
20 });
21 + group("rlp decode", () {
22 + test("0x05", () => expect(decode(hexToBytes("0x05")), [5]));
23 + test(
24 + "0xc88363617483646f67",
25 + () => expect(
26 + decode(hexToBytes("0xc88363617483646f67")),
27 + [
28 + [99, 97, 116],
29 + [100, 111, 103]
30 + ],
31 + ),
32 + );
33 +
34 + test(
35 + "cat dog",
36 + () => expect(
37 + decode(Uint8List.fromList(rlp.encode(["cat", "dog"]))),
38 + [
39 + [99, 97, 116],
40 + [100, 111, 103]
41 + ],
42 + ),
43 + );
44 + });
45 }
lib/bitcoin/cw_bitcoin.dart
+7 -5
@@ -536,11 +536,6 @@ class CWBitcoin extends Bitcoin {
536 return (bitcoinWallet.feeRate(BitcoinTransactionPriority.fast) * 10).round();
537 }
538
539 - @override
540 - void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) {
541 - (wallet as ElectrumWallet).setLedgerConnection(connection);
542 - }
543 -
539 @override
540 void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) {
541 (wallet as ElectrumWallet).hardwareWalletService = service;
@@ -560,6 +555,13 @@ class CWBitcoin extends Bitcoin {
555 return LitecoinBitboxService(manager);
556 }
557
558 + @override
559 + HardwareWalletService getTrezorHardwareWalletService(
560 + trezor.TrezorConnect connect, bool isBitcoin) {
561 + if (isBitcoin) return BitcoinTrezorService(connect);
562 + return LitecoinTrezorService(connect);
563 + }
564 +
565 @override
566 List<ElectrumSubAddress> getSilentPaymentAddresses(Object wallet) {
567 final bitcoinWallet = wallet as ElectrumWallet;
lib/di.dart
+7
@@ -56,6 +56,7 @@ import 'package:cake_wallet/view_model/dev/secure_preferences.dart';
56 import 'package:cake_wallet/view_model/dev/shared_preferences.dart';
57 import 'package:cake_wallet/view_model/hardware_wallet/bitbox_view_model.dart';
58 import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
59 +import 'package:cake_wallet/view_model/hardware_wallet/trezor_view_model.dart';
60 import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart';
61 import 'package:cake_wallet/view_model/link_view_model.dart';
62 import 'package:cake_wallet/tron/tron.dart';
@@ -281,6 +282,7 @@ import 'package:get_it/get_it.dart';
282 import 'package:hive/hive.dart';
283 import 'package:mobx/mobx.dart';
284 import 'package:shared_preferences/shared_preferences.dart';
285 +import 'package:trezor_connect/trezor_connect.dart';
286 import 'buy/kryptonim/kryptonim.dart';
287 import 'buy/meld/meld_buy_provider.dart';
288 import 'dogecoin/dogecoin.dart';
@@ -400,6 +402,7 @@ Future<void> setup({
402 switch(type) {
403 case HardwareWalletType.bitbox: return getIt<BitboxViewModel>();
404 case HardwareWalletType.ledger: return getIt<LedgerViewModel>();
405 + case HardwareWalletType.trezor: return getIt<TrezorViewModel>();
406 case HardwareWalletType.cupcake:
407 case HardwareWalletType.coldcard:
408 case HardwareWalletType.seedsigner:
@@ -412,6 +415,10 @@ Future<void> setup({
415
416 getIt.registerLazySingleton(() => BitboxViewModel());
417
418 + getIt.registerLazySingleton(() => TrezorConnect("cakewallet://trezor_connect",
419 + appName: "Cake Wallet"));
420 + getIt.registerLazySingleton(() => TrezorViewModel(getIt<TrezorConnect>()));
421 +
422 final secretStore = await SecretStoreBase.load(getIt.get<SecureStorage>());
423
424 getIt.registerSingleton<SecretStore>(secretStore);
lib/ethereum/cw_ethereum.dart
+7
@@ -188,6 +188,9 @@ class CWEthereum extends Ethereum {
188 } else if (service is EVMChainBitboxService) {
189 ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
190 .setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath);
191 + } else if (service is EVMChainTrezorService) {
192 + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials)
193 + .setTrezorConnect(service.connect, wallet.walletInfo.derivationInfo?.derivationPath);
194 }
195 }
196 @override
@@ -198,6 +201,10 @@ class CWEthereum extends Ethereum {
201 HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
202 EVMChainBitboxService(manager);
203
204 + @override
205 + HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect) =>
206 + EVMChainTrezorService(connect);
207 +
208 @override
209 List<String> getDefaultTokenContractAddresses() {
210 return DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList();
lib/main.dart
+2
@@ -62,6 +62,7 @@ import 'package:cw_core/window_size.dart';
62 import 'package:logging/logging.dart';
63 import 'package:cake_wallet/core/trade_monitor.dart';
64 import 'package:cake_wallet/core/reset_service.dart';
65 +import 'package:trezor_connect/trezor_connect.dart';
66
67 final navigatorKey = GlobalKey<NavigatorState>();
68 final rootKey = GlobalKey<RootState>();
@@ -375,6 +376,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
376 linkViewModel: linkViewModel,
377 tradeMonitor: tradeMonitor,
378 nodeSwitchingService: nodeSwitchingService,
379 + trezorConnect: getIt<TrezorConnect>(),
380 child: ThemeProvider(
381 themeStore: appStore.themeStore,
382 materialAppBuilder: (context, theme, darkTheme, themeMode) => MaterialApp(
lib/polygon/cw_polygon.dart
+7
@@ -206,6 +206,9 @@ class CWPolygon extends Polygon {
206 } else if (service is EVMChainBitboxService) {
207 ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
208 .setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath);
209 + } else if (service is EVMChainTrezorService) {
210 + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials)
211 + .setTrezorConnect(service.connect, wallet.walletInfo.derivationInfo?.derivationPath);
212 }
213 }
214
@@ -217,6 +220,10 @@ class CWPolygon extends Polygon {
220 HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
221 EVMChainBitboxService(manager, chainId: 137);
222
223 + @override
224 + HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect) =>
225 + EVMChainTrezorService(connect, chainId: 137);
226 +
227 @override
228 List<String> getDefaultTokenContractAddresses() =>
229 DefaultPolygonErc20Tokens().initialPolygonErc20Tokens.map((e) => e.contractAddress).toList();
lib/router.dart
+6
@@ -345,6 +345,12 @@ Route<dynamic> createRoute(RouteSettings settings) {
345 (_) => getIt.get<NewWalletTypePage>(
346 param1: NewWalletTypeArguments(
347 onTypeSelected: (BuildContext context, WalletType type) {
348 + if (hardwareWalletType == HardwareWalletType.trezor) {
349 + Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount,
350 + arguments: [type, hardwareWalletType]);
351 + return;
352 + }
353 +
354 final arguments = ConnectDevicePageParams(
355 walletType: type,
356 hardwareWalletType: hardwareWalletType,
lib/src/screens/connect_device/select_device_manufacturer_page.dart
+109 -55
@@ -1,3 +1,5 @@
1 +import 'dart:io';
2 +
3 import 'package:cake_wallet/generated/i18n.dart';
4 import 'package:cake_wallet/routes.dart';
5 import 'package:cake_wallet/src/screens/base_page.dart';
@@ -29,28 +31,62 @@ class SelectDeviceManufacturerPage extends BasePage {
31 @override
32 AppBarStyle get appBarStyle => AppBarStyle.regular;
33
34 + ColorFilter get _colorFilter =>
35 + ColorFilter.mode(currentTheme.colorScheme.onSurface, BlendMode.srcIn);
36 +
37 List<_DeviceManufacturer> get availableManufacturers => [
38 _DeviceManufacturer(
34 - image: SvgPicture.asset('assets/images/hardware_wallet/ledger_man.svg', height: 25),
39 + image: SvgPicture.asset(
40 + 'assets/images/hardware_wallet/ledger_man.svg',
41 + height: 25,
42 + colorFilter: _colorFilter,
43 + ),
44 hardwareWalletType: HardwareWalletType.ledger,
45 ),
37 - // _DeviceManufacturer(
38 - // image: SvgPicture.asset('assets/images/hardware_wallet/bitbox_man.svg', height: 25),
39 - // hardwareWalletType: HardwareWalletType.bitbox,
40 - // tag: S.current.new_tag,
41 - // ),
46 + if (Platform.isAndroid) ...[
47 + _DeviceManufacturer(
48 + image: SvgPicture.asset(
49 + 'assets/images/hardware_wallet/trezor_man.svg',
50 + height: 25,
51 + colorFilter: _colorFilter,
52 + ),
53 + hardwareWalletType: HardwareWalletType.trezor,
54 + tag: S.current.new_tag,
55 + ),
56 + _DeviceManufacturer(
57 + image: SvgPicture.asset(
58 + 'assets/images/hardware_wallet/bitbox_man.svg',
59 + height: 25,
60 + colorFilter: _colorFilter,
61 + ),
62 + hardwareWalletType: HardwareWalletType.bitbox,
63 + tag: S.current.new_tag,
64 + ),
65 + ],
66 _DeviceManufacturer(
43 - image: SvgPicture.asset('assets/images/hardware_wallet/cupcake_man.svg', height: 25),
67 + image: SvgPicture.asset(
68 + 'assets/images/hardware_wallet/cupcake_man.svg',
69 + height: 25,
70 + colorFilter: _colorFilter,
71 + ),
72 hardwareWalletType: HardwareWalletType.cupcake,
73 tag: S.current.new_tag,
74 ),
75 _DeviceManufacturer(
48 - image: SvgPicture.asset('assets/images/hardware_wallet/coldcard_man.svg', height: 25),
76 + image: SvgPicture.asset(
77 + 'assets/images/hardware_wallet/coldcard_man.svg',
78 + height: 25,
79 + colorFilter: _colorFilter,
80 + ),
81 hardwareWalletType: HardwareWalletType.coldcard,
82 tag: S.current.new_tag,
83 ),
84 // _DeviceManufacturer(
53 - // image: SvgPicture.asset('assets/images/hardware_wallet/seedsigner_man.svg', height: 25),
85 + // image: SvgPicture.asset(
86 + // 'assets/images/hardware_wallet/seedsigner_man.svg',
87 + // height: 25,
88 + // colorFilter: _colorFilter,
89 + // ),
90 // hardwareWalletType: HardwareWalletType.seedsigner,
91 // tag: S.current.new_tag,
92 // ),
@@ -61,26 +97,40 @@ class SelectDeviceManufacturerPage extends BasePage {
97 .toList();
98
99 List<_DeviceManufacturer> get comingManufacturers => [
100 + if (!Platform.isAndroid) ...[
101 + _DeviceManufacturer(
102 + image: SvgPicture.asset(
103 + 'assets/images/hardware_wallet/bitbox_man.svg',
104 + height: 25,
105 + colorFilter: _colorFilter,
106 + ),
107 + hardwareWalletType: HardwareWalletType.bitbox,
108 + tag: S.current.coming_soon_tag,
109 + ),
110 + ],
111 _DeviceManufacturer(
65 - image: SvgPicture.asset('assets/images/hardware_wallet/trezor_man.svg', height: 25),
66 - tag: S.current.coming_soon_tag,
67 - ),
68 - _DeviceManufacturer(
69 - image: SvgPicture.asset('assets/images/hardware_wallet/bitbox_man.svg', height: 25),
70 - hardwareWalletType: HardwareWalletType.bitbox,
71 - tag: S.current.coming_soon_tag,
72 - ),
73 - _DeviceManufacturer(
74 - image: SvgPicture.asset('assets/images/hardware_wallet/seedsigner_man.svg', height: 25),
112 + image: SvgPicture.asset(
113 + 'assets/images/hardware_wallet/seedsigner_man.svg',
114 + height: 25,
115 + colorFilter: _colorFilter,
116 + ),
117 hardwareWalletType: HardwareWalletType.seedsigner,
118 tag: S.current.coming_soon_tag,
119 ),
120 _DeviceManufacturer(
79 - image: SvgPicture.asset('assets/images/hardware_wallet/foundation_man.svg', height: 25),
121 + image: SvgPicture.asset(
122 + 'assets/images/hardware_wallet/foundation_man.svg',
123 + height: 25,
124 + colorFilter: _colorFilter,
125 + ),
126 tag: S.current.coming_soon_tag,
127 ),
128 _DeviceManufacturer(
83 - image: SvgPicture.asset('assets/images/hardware_wallet/keystone_man.svg', height: 25),
129 + image: SvgPicture.asset(
130 + 'assets/images/hardware_wallet/keystone_man.svg',
131 + height: 25,
132 + colorFilter: _colorFilter,
133 + ),
134 tag: S.current.coming_soon_tag,
135 ),
136 ];
@@ -90,48 +140,52 @@ class SelectDeviceManufacturerPage extends BasePage {
140 child: Center(
141 child: Container(
142 padding: EdgeInsets.only(left: 24, right: 24),
93 - child: Column(
94 - children: [
95 - ...availableManufacturers.map(
96 - (manufacturer) => Padding(
97 - padding: EdgeInsets.only(top: 10),
98 - child: ManufacturerOptionTile(
99 - image: manufacturer.image,
100 - tag: manufacturer.tag,
101 - onPressed: () {
102 - if (onSelect != null)
103 - return onSelect!.call(context, manufacturer.hardwareWalletType!);
104 -
105 - if (isAirgappedWallet(manufacturer.hardwareWalletType)) {
106 - _onScanQRCode(context, manufacturer.hardwareWalletType!);
107 - } else if (manufacturer.hardwareWalletType != null) {
108 - Navigator.pushNamed(context, Routes.connectHardwareWallet,
109 - arguments: [manufacturer.hardwareWalletType]);
110 - }
111 - },
112 - ),
113 - ),
114 - ),
115 - if (showUnavailable) ...[
116 - Padding(
117 - padding: EdgeInsets.only(top: 20, bottom: 10),
118 - child: DottedDivider(color: Theme.of(context).colorScheme.surfaceContainer),
119 - ),
120 - ...comingManufacturers.map(
143 + height: double.infinity,
144 + child: SingleChildScrollView(
145 + child: Column(
146 + children: [
147 + ...availableManufacturers.map(
148 (manufacturer) => Padding(
149 padding: EdgeInsets.only(top: 10),
150 child: ManufacturerOptionTile(
151 image: manufacturer.image,
152 tag: manufacturer.tag,
126 - onPressed: () =>
127 - Fluttertoast.showToast(msg: 'One more tap and it might work'),
128 - // Ester egg
129 - isUnavailable: true,
153 + onPressed: () {
154 + if (onSelect != null)
155 + return onSelect!.call(context, manufacturer.hardwareWalletType!);
156 +
157 + if (isAirgappedWallet(manufacturer.hardwareWalletType)) {
158 + _onScanQRCode(context, manufacturer.hardwareWalletType!);
159 + } else if (manufacturer.hardwareWalletType != null) {
160 + Navigator.pushNamed(context, Routes.connectHardwareWallet,
161 + arguments: [manufacturer.hardwareWalletType]);
162 + }
163 + },
164 ),
165 ),
166 ),
133 - ]
134 - ],
167 + if (showUnavailable) ...[
168 + Padding(
169 + padding: EdgeInsets.only(top: 20, bottom: 10),
170 + child: DottedDivider(color: Theme.of(context).colorScheme.surfaceContainer),
171 + ),
172 + ...comingManufacturers.map(
173 + (manufacturer) => Padding(
174 + padding: EdgeInsets.only(top: 10),
175 + child: ManufacturerOptionTile(
176 + image: manufacturer.image,
177 + tag: manufacturer.tag,
178 + onPressed: () =>
179 + Fluttertoast.showToast(msg: 'One more tap and it might work'),
180 + // Ester egg
181 +
182 + isUnavailable: true,
183 + ),
184 + ),
185 + ),
186 + ]
187 + ],
188 + ),
189 ),
190 ),
191 ),
lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
+4 -2
@@ -33,10 +33,12 @@ class CryptoBalanceWidget extends StatelessWidget {
33 switch (dashboardViewModel.wallet.hardwareWalletType) {
34 case null:
35 return null;
36 - case HardwareWalletType.ledger:
37 - return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
36 case HardwareWalletType.bitbox:
37 return "assets/images/hardware_wallet/device_bitbox.svg";
38 + case HardwareWalletType.ledger:
39 + return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
40 + case HardwareWalletType.trezor:
41 + return "assets/images/hardware_wallet/device_trezor_safe_5.svg";
42 case HardwareWalletType.cupcake:
43 return "assets/images/cupcake.svg";
44 case HardwareWalletType.coldcard:
lib/src/screens/root/root.dart
+17 -8
@@ -1,26 +1,28 @@
1 import 'dart:async';
2 import 'dart:io';
3 +
4 import 'package:cake_wallet/bitcoin/bitcoin.dart';
5 import 'package:cake_wallet/core/auth_service.dart';
6 import 'package:cake_wallet/core/node_switching_service.dart';
7 import 'package:cake_wallet/core/totp_request_details.dart';
8 import 'package:cake_wallet/core/trade_monitor.dart';
9 +import 'package:cake_wallet/entities/qr_scanner.dart';
10 +import 'package:cake_wallet/reactions/wallet_utils.dart';
11 +import 'package:cake_wallet/routes.dart';
12 +import 'package:cake_wallet/src/screens/auth/auth_page.dart';
13 +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
14 +import 'package:cake_wallet/store/app_store.dart';
15 +import 'package:cake_wallet/store/authentication_store.dart';
16 import 'package:cake_wallet/utils/device_info.dart';
17 import 'package:cake_wallet/view_model/link_view_model.dart';
18 import 'package:cw_core/utils/print_verbose.dart';
19 +import 'package:cw_core/utils/socket_health_logger.dart';
20 import 'package:cw_core/wallet_base.dart';
21 import 'package:cw_core/wallet_type.dart';
22 import 'package:flutter/material.dart';
14 -import 'package:cake_wallet/routes.dart';
15 -import 'package:cake_wallet/src/screens/auth/auth_page.dart';
16 -import 'package:cake_wallet/store/app_store.dart';
17 -import 'package:cake_wallet/store/authentication_store.dart';
18 -import 'package:cake_wallet/entities/qr_scanner.dart';
23 import 'package:mobx/mobx.dart';
24 +import 'package:trezor_connect/trezor_connect.dart';
25 import 'package:uni_links/uni_links.dart';
21 -import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
22 -import 'package:cake_wallet/reactions/wallet_utils.dart';
23 -import 'package:cw_core/utils/socket_health_logger.dart';
26
27 class Root extends StatefulWidget {
28 Root({
@@ -33,6 +35,7 @@ class Root extends StatefulWidget {
35 required this.linkViewModel,
36 required this.tradeMonitor,
37 required this.nodeSwitchingService,
38 + required this.trezorConnect,
39 }) : super(key: key);
40
41 final AuthenticationStore authenticationStore;
@@ -43,6 +46,7 @@ class Root extends StatefulWidget {
46 final LinkViewModel linkViewModel;
47 final TradeMonitor tradeMonitor;
48 final NodeSwitchingService nodeSwitchingService;
49 + final TrezorConnect trezorConnect;
50
51 @override
52 RootState createState() => RootState();
@@ -110,6 +114,11 @@ class RootState extends State<Root> with WidgetsBindingObserver {
114
115 widget.linkViewModel.currentLink = uri;
116
117 + if (uri.toString().startsWith(widget.trezorConnect.callbackBackUri)) {
118 + widget.trezorConnect.handleCallback(uri);
119 + return;
120 + }
121 +
122 bool requireAuth = await widget.authService.requireAuth();
123
124 if (!requireAuth && widget.authenticationStore.state == AuthenticationState.allowed) {
lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart
+1 -2
@@ -23,8 +23,7 @@ abstract class BaseBottomSheet extends StatelessWidget {
23 this.leftActionButtonKey,
24 this.rightActionButtonKey,
25 required this.maxHeight,
26 - }) : assert(footerType == FooterType.none,
27 - 'currentTheme is required unless footerType is none');
26 + });
27
28 final String titleText;
29 final String? titleIconPath;
lib/view_model/hardware_wallet/bitbox_view_model.dart
+4
@@ -11,6 +11,7 @@ import 'package:cw_core/hardware/hardware_wallet_service.dart';
11 import 'package:cw_core/root_dir.dart';
12 import 'package:cw_core/utils/print_verbose.dart';
13 import 'package:cw_core/wallet_base.dart';
14 +import 'package:cw_core/wallet_info.dart';
15 import 'package:cw_core/wallet_type.dart';
16 import 'package:bitbox_flutter/bitbox_flutter.dart' as sdk;
17 import 'package:mobx/mobx.dart';
@@ -28,6 +29,9 @@ abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
29 }
30 }
31
32 + @override
33 + HardwareWalletType get hardwareWalletType => HardwareWalletType.bitbox;
34 +
35 @override
36 @observable
37 bool isBleEnabled = false;
lib/view_model/hardware_wallet/hardware_wallet_view_model.dart
+2
@@ -1,9 +1,11 @@
1 import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
2 import 'package:cw_core/hardware/hardware_wallet_service.dart';
3 import 'package:cw_core/wallet_base.dart';
4 +import 'package:cw_core/wallet_info.dart';
5 import 'package:cw_core/wallet_type.dart';
6
7 abstract class HardwareWalletViewModel {
8 + HardwareWalletType get hardwareWalletType;
9 bool get isConnected;
10 bool get isBleEnabled;
11 bool get hasBluetooth;
lib/view_model/hardware_wallet/ledger_view_model.dart
+3
@@ -54,6 +54,9 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
54 }
55 }
56
57 + @override
58 + HardwareWalletType get hardwareWalletType => HardwareWalletType.ledger;
59 +
60 @override
61 @observable
62 bool isBleEnabled = false;
lib/view_model/hardware_wallet/trezor_view_model.dart new
+82
@@ -0,0 +1,82 @@
1 +import 'dart:async';
2 +
3 +import 'package:cake_wallet/bitcoin/bitcoin.dart';
4 +import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
5 +import 'package:cake_wallet/ethereum/ethereum.dart';
6 +import 'package:cake_wallet/polygon/polygon.dart';
7 +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
8 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
9 +import 'package:cw_core/wallet_base.dart';
10 +import 'package:cw_core/wallet_info.dart';
11 +import 'package:cw_core/wallet_type.dart';
12 +import 'package:trezor_connect/trezor_connect.dart' as sdk;
13 +import 'package:mobx/mobx.dart';
14 +
15 +part 'trezor_view_model.g.dart';
16 +
17 +class TrezorViewModel = TrezorViewModelBase with _$TrezorViewModel;
18 +
19 +abstract class TrezorViewModelBase extends HardwareWalletViewModel with Store {
20 + final sdk.TrezorConnect trezorConnect;
21 +
22 + TrezorViewModelBase(this.trezorConnect);
23 +
24 + @override
25 + HardwareWalletType get hardwareWalletType => HardwareWalletType.trezor;
26 +
27 + @override
28 + @observable
29 + bool isBleEnabled = false;
30 +
31 + @override
32 + bool get hasBluetooth => false;
33 +
34 + @override
35 + Future<void> updateBleState() async {}
36 +
37 + @override
38 + Stream<HardwareWalletDevice> scanForBleDevices() => throw UnimplementedError();
39 +
40 + @override
41 + Future<List<HardwareWalletDevice>> getAllUsbDevices() async => [];
42 +
43 + @override
44 + Future<void> stopScanning() async {}
45 +
46 + @override
47 + Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async => true;
48 +
49 + @override
50 + bool get isConnected => true;
51 +
52 + @override
53 + HardwareWalletService getHardwareWalletService(WalletType type) {
54 + switch (type) {
55 + case WalletType.bitcoin:
56 + return bitcoin!.getTrezorHardwareWalletService(trezorConnect, true);
57 + case WalletType.litecoin:
58 + return bitcoin!.getTrezorHardwareWalletService(trezorConnect, false);
59 + case WalletType.ethereum:
60 + return ethereum!.getTrezorHardwareWalletService(trezorConnect);
61 + case WalletType.polygon:
62 + return polygon!.getTrezorHardwareWalletService(trezorConnect);
63 + default:
64 + throw UnimplementedError();
65 + }
66 + }
67 +
68 + @override
69 + void initWallet(WalletBase wallet) {
70 + switch (wallet.type) {
71 + case WalletType.bitcoin:
72 + case WalletType.litecoin:
73 + return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
74 + case WalletType.ethereum:
75 + return ethereum!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
76 + case WalletType.polygon:
77 + return polygon!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
78 + default:
79 + throw Exception('Unexpected wallet type: ${wallet.type}');
80 + }
81 + }
82 +}
lib/view_model/send/send_view_model.dart
+1 -2
@@ -37,7 +37,6 @@ import 'package:cake_wallet/utils/payment_request.dart';
37 import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
38 import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
39 import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
40 -import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
40 import 'package:cake_wallet/view_model/send/fees_view_model.dart';
41 import 'package:cake_wallet/view_model/send/output.dart';
42 import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
@@ -942,7 +941,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
941 );
942
943 if (token != null) {
945 - selectedCryptoCurrency = token as CryptoCurrency;
944 + selectedCryptoCurrency = token;
945 }
946 }
947 }
lib/view_model/wallet_hardware_restore_view_model.dart
+1 -4
@@ -102,10 +102,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
102 throw Exception('Unexpected type: ${type.toString()}');
103 }
104
105 - if (hardwareWalletVM is LedgerViewModel)
106 - credentials.hardwareWalletType = HardwareWalletType.ledger;
107 - else
108 - credentials.hardwareWalletType = HardwareWalletType.bitbox;
105 + credentials.hardwareWalletType = hardwareWalletVM.hardwareWalletType;
106
107 return credentials;
108 }
pubspec_base.yaml
+5 -1
@@ -4,7 +4,7 @@ dependencies:
4 flutter_localizations:
5 sdk: flutter
6 intl: any
7 - url_launcher: 6.3.1
7 + url_launcher: 6.3.2
8 qr_flutter:
9 git:
10 url: https://github.com/cake-tech/qr.flutter.git
@@ -116,6 +116,10 @@ dependencies:
116 git:
117 url: https://github.com/vespr-wallet/ledger-flutter-plus
118 ref: 60817d4b20144f9da9029f5034790272795b9d38
119 + trezor_connect:
120 + git:
121 + url: https://github.com/cake-tech/trezor_connect
122 + ref: 59b3ceec158d3393b20d0c596cab2abcc62ac8e4
123 bitbox_flutter:
124 path: ./scripts/bitbox_flutter
125 hashlib: 1.19.2
tool/configure.dart
+15 -5
@@ -112,6 +112,7 @@ import 'package:cw_core/get_height_by_date.dart';
112 import 'package:hive/hive.dart';
113 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
114 import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
115 +import 'package:trezor_connect/trezor_connect.dart' as trezor;
116 import 'package:blockchain_utils/blockchain_utils.dart';
117 import 'package:bip39/bip39.dart' as bip39;
118 """;
@@ -138,6 +139,7 @@ import 'package:cw_bitcoin/litecoin_wallet.dart';
139 import 'package:cw_bitcoin/hardware/bitcoin_ledger_service.dart';
140 import 'package:cw_bitcoin/hardware/litecoin_ledger_service.dart';
141 import 'package:cw_bitcoin/hardware/bitbox_service.dart';
142 +import 'package:cw_bitcoin/hardware/trezor_service.dart';
143 import 'package:mobx/mobx.dart';
144 """;
145 const bitcoinCwPart = "part 'cw_bitcoin.dart';";
@@ -245,10 +247,10 @@ abstract class Bitcoin {
247 void deleteSilentPaymentAddress(Object wallet, String address);
248 Future<void> updateFeeRates(Object wallet);
249 int getMaxCustomFeeRate(Object wallet);
248 - void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection);
250 void setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
251 HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection, bool isBitcoin);
252 HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager, bool isBitcoin);
253 + HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect, bool isBitcoin);
254 List<Output> updateOutputs(PendingTransaction pendingTransaction, List<Output> outputs);
255 bool txIsReceivedSilentPayment(TransactionInfo txInfo);
256 bool txIsMweb(TransactionInfo txInfo);
@@ -697,6 +699,7 @@ import 'package:cw_core/utils/print_verbose.dart';
699 import 'package:hive/hive.dart';
700 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
701 import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
702 +import 'package:trezor_connect/trezor_connect.dart' as trezor;
703 import 'package:web3dart/web3dart.dart';
704
705 """;
@@ -707,11 +710,13 @@ import 'package:cw_evm/evm_chain_transaction_credentials.dart';
710 import 'package:cw_evm/evm_chain_transaction_info.dart';
711 import 'package:cw_evm/evm_chain_transaction_priority.dart';
712 import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
710 -import 'package:cw_evm/hardware/evm_ledger_credentials.dart';
711 -import 'package:cw_evm/hardware/evm_bitbox_credentials.dart';
713 +import 'package:cw_evm/hardware/evm_chain_bitbox_credentials.dart';
714 +import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart';
715 +import 'package:cw_evm/hardware/evm_chain_trezor_credentials.dart';
716 import 'package:cw_evm/evm_chain_wallet.dart';
717 import 'package:cw_evm/hardware/evm_chain_bitbox_service.dart';
718 import 'package:cw_evm/hardware/evm_chain_ledger_service.dart';
719 +import 'package:cw_evm/hardware/evm_chain_trezor_service.dart';
720
721 import 'package:cw_ethereum/ethereum_client.dart';
722 import 'package:cw_ethereum/ethereum_wallet.dart';
@@ -780,6 +785,7 @@ abstract class Ethereum {
785 void setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
786 HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
787 HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager);
788 + HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect);
789 List<String> getDefaultTokenContractAddresses();
790 bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
791 }
@@ -822,6 +828,7 @@ import 'package:cw_core/utils/print_verbose.dart';
828 import 'package:hive/hive.dart';
829 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
830 import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
831 +import 'package:trezor_connect/trezor_connect.dart' as trezor;
832 import 'package:web3dart/web3dart.dart';
833
834 """;
@@ -832,11 +839,13 @@ import 'package:cw_evm/evm_chain_transaction_credentials.dart';
839 import 'package:cw_evm/evm_chain_transaction_info.dart';
840 import 'package:cw_evm/evm_chain_transaction_priority.dart';
841 import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
835 -import 'package:cw_evm/hardware/evm_ledger_credentials.dart';
836 -import 'package:cw_evm/hardware/evm_bitbox_credentials.dart';
842 +import 'package:cw_evm/hardware/evm_chain_bitbox_credentials.dart';
843 +import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart';
844 +import 'package:cw_evm/hardware/evm_chain_trezor_credentials.dart';
845 import 'package:cw_evm/evm_chain_wallet.dart';
846 import 'package:cw_evm/hardware/evm_chain_bitbox_service.dart';
847 import 'package:cw_evm/hardware/evm_chain_ledger_service.dart';
848 +import 'package:cw_evm/hardware/evm_chain_trezor_service.dart';
849
850 import 'package:cw_polygon/polygon_client.dart';
851 import 'package:cw_polygon/polygon_wallet.dart';
@@ -895,6 +904,7 @@ abstract class Polygon {
904 void setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
905 HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
906 HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager);
907 + HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect);
908 List<String> getDefaultTokenContractAddresses();
909 bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
910 }