LCW-1128-Add-support-for-Trezor (#2565)
What changed, and why it matters
This commit adds support for Trezor hardware wallets to the Cake Wallet app, alongside some refactoring of existing Ledger and BitBox hardware wallet code. It introduces new code paths for discovering accounts, signing Bitcoin/Litecoin transactions, and signing Ethereum/Polygon transactions via Trezor. The changes also include a custom RLP decoder for Ethereum transactions and UI updates to show Trezor as an available hardware wallet option on Android. There is no explicit security bug visible in the diff, but the commit is a large feature addition touching sensitive wallet-signing logic, so it warrants careful review.
Treat this as a high-touch security review item. Verify that the Trezor Connect integration uses authenticated/encrypted transport, validates device responses, and does not allow transaction tampering between the parsed RLP fields and what is shown to the user. Audit the custom RLP decoder against canonical test vectors and edge cases (empty lists, nested lists, leading zeros). Confirm that the refactored Ledger/BitBox paths did not inadvertently remove security checks (e.g., trusted inputs for SegWit). Review the `trezor_connect` dependency for supply-chain risk and ensure the pinned commit is from a trusted/audited branch.
Security signals we found
New hardware-wallet signing path added (Trezor) for Bitcoin, Litecoin, Ethereum, Polygon
Custom RLP decoder introduced for EVM transaction parsing before Trezor signing
Refactoring of existing Ledger/BitBox signing paths to share abstractions
Dependency added on external Git package `trezor_connect` pinned to commit `59b3ceec158d3393b20d0c596cab2abcc62ac8e4`
No explicit vulnerability or security fix described in commit message
Evidence from the diff
The commit implements Trezor integration across Bitcoin, Litecoin, Ethereum, and Polygon wallets. Key additions include: BitcoinTrezorService and LitecoinTrezorService for account discovery and PSBT/transaction signing using trezor_connect; EvmTrezorCredentials and EVMChainTrezorService for Ethereum/Polygon signing; a new rlp_decode.dart utility to parse RLP-encoded EVM transactions before sending them to Trezor; DI registration of TrezorConnect and TrezorViewModel; and UI changes to conditionally show Trezor/BitBox on Android. Existing Ledger/BitBox code was refactored: Ledger-specific connection setup was removed from ElectrumWallet/LitecoinWallet in favor of a generic hardwareWalletService, and EVM credential files were renamed. The commit pins trezor_connect to a specific Git commit hash.
Changed components
cw_bitcoin/lib/hardware/trezor_service.dartcw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dartcw_bitcoin/lib/hardware/litecoin_ledger_service.dartcw_bitcoin/lib/hardware/bitbox_service.dartcw_bitcoin/lib/litecoin_wallet.dartcw_bitcoin/lib/electrum_wallet.dartcw_evm/lib/hardware/evm_chain_trezor_credentials.dartcw_evm/lib/hardware/evm_chain_trezor_service.dartcw_evm/lib/utils/rlp_decode.dartlib/view_model/hardware_wallet/trezor_view_model.dartlib/di.dartlib/router.dartlib/src/screens/connect_device/select_device_manufacturer_page.dartpubspec_base.yamlcw_bitcoin/pubspec.yamlcw_evm/pubspec.yamlInspect captured patch +1182 / −370
diff --git a/assets/images/hardware_wallet/device_trezor_safe_5.svg b/assets/images/hardware_wallet/device_trezor_safe_5.svg
new file mode 100644
index 0000000..14c0329
--- /dev/null
+++ b/assets/images/hardware_wallet/device_trezor_safe_5.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32">
+ <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"/>
+</svg>
diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 38b7659..cf94845 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -47,7 +47,6 @@ import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/output_info.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
-import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
import 'package:mobx/mobx.dart';
import 'package:rxdart/subjects.dart';
import 'package:sp_scanner/sp_scanner.dart';
@@ -163,7 +162,7 @@ abstract class ElectrumWalletBase
[HardwareWalletType? hardwareWalletType]) {
switch (network) {
case LitecoinNetwork.mainnet:
- if (hardwareWalletType == HardwareWalletType.ledger)
+ if ([HardwareWalletType.ledger, HardwareWalletType.trezor].contains(hardwareWalletType))
return Bip44Conf.litecoinMainNet.altKeyNetVer;
return null;
default:
@@ -1423,8 +1422,6 @@ abstract class ElectrumWalletBase
HardwareWalletService? hardwareWalletService;
- void setLedgerConnection(ledger.LedgerConnection connection) => throw UnimplementedError();
-
Future<BtcTransaction> buildHardwareWalletTransaction({
required List<BitcoinBaseOutput> outputs,
required BigInt fee,
diff --git a/cw_bitcoin/lib/hardware/bitbox_service.dart b/cw_bitcoin/lib/hardware/bitbox_service.dart
index 99bbf76..3f47c5c 100644
--- a/cw_bitcoin/lib/hardware/bitbox_service.dart
+++ b/cw_bitcoin/lib/hardware/bitbox_service.dart
@@ -6,7 +6,9 @@ import 'dart:typed_data';
import 'package:bitbox_flutter/bitbox_manager.dart';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
+import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
+import 'package:cw_bitcoin/psbt/transaction_builder.dart';
import 'package:cw_bitcoin/psbt/v0_deserialize.dart';
import 'package:cw_bitcoin/psbt/v0_finalizer.dart';
import 'package:cw_bitcoin/utils.dart';
@@ -17,7 +19,8 @@ import 'package:ledger_bitcoin/psbt.dart';
class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWalletService {
BitcoinBitboxService(this.manager);
- final int bitboxCoinType = 0; // https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L40
+ // https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L40
+ static const int bitboxCoinType = 0;
final BitboxManager manager;
@override
@@ -25,9 +28,11 @@ class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWal
final accounts = <HardwareAccountData>[];
final indexRange = List.generate(limit, (i) => i + index);
+ // https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L252
+ final keyType = 1; // xPUB
for (final i in indexRange) {
final derivationPath = "m/84'/0'/$i'";
- final xPub = await manager.getBTCXPub(bitboxCoinType, derivationPath, 1);
+ final xPub = await manager.getBTCXPub(bitboxCoinType, derivationPath, keyType);
final hd = Bip32Slip10Secp256k1.fromExtendedKey(xPub).childKey(Bip32KeyIndex(0));
final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
@@ -42,7 +47,6 @@ class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWal
return accounts;
}
-
@override
Future<Uint8List> signTransaction({required String transaction}) async {
final psbt = PsbtV2()..deserialize(base64Decode(transaction));
@@ -56,27 +60,31 @@ class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWal
}
@override
- Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) async {
- throw UnimplementedError(); // ToDo (Konsti)
- }
+ Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) =>
+ manager.signBTCMessage(bitboxCoinType, derivationPath ?? "m/84'/0'/0'/0/0", message);
@override
Future<Uint8List> getMasterFingerprint() => manager.getMasterFingerprint();
}
-class LitecoinBitboxService extends HardwareWalletService {
+class LitecoinBitboxService extends HardwareWalletService with BitcoinHardwareWalletService, LitecoinHardwareWalletService {
LitecoinBitboxService(this.manager);
final BitboxManager manager;
+ // https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L40
+ static const int bitboxCoinType = 2;
+
@override
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
final accounts = <HardwareAccountData>[];
final indexRange = List.generate(limit, (i) => i + index);
+ // https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L252
+ final keyType = 1; // xPUB
for (final i in indexRange) {
final derivationPath = "m/84'/2'/$i'";
- final xPub = await manager.getBTCXPub(0, derivationPath, 1);
+ final xPub = await manager.getBTCXPub(bitboxCoinType, derivationPath, keyType);
final hd = Bip32Slip10Secp256k1.fromExtendedKey(xPub).childKey(Bip32KeyIndex(0));
final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet);
@@ -90,4 +98,27 @@ class LitecoinBitboxService extends HardwareWalletService {
return accounts;
}
+
+ @override
+ Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) =>
+ manager.signBTCMessage(bitboxCoinType, derivationPath ?? "m/84'/0'/0'/0/0", message);
+
+ @override
+ Future<String> signLitecoinTransaction({
+ required List<BitcoinBaseOutput> outputs,
+ required List<PSBTReadyUtxoWithAddress> inputs,
+ required Map<String, PublicKeyWithDerivationPath> publicKeys,
+ }) async {
+ final psbt = PSBTTransactionBuild(inputs: inputs, outputs: outputs, cwOutputs: []).psbt;
+
+ final signedPsbt = await manager.signBTCPsbt(bitboxCoinType, base64Encode(psbt.asPsbtV0()));
+ final transactionRes = PsbtV2()
+ ..deserializeV0(base64Decode(signedPsbt))
+ ..finalizeV0();
+
+ return hex.encode(transactionRes.extract());
+ }
+
+ @override
+ Future<Uint8List> getMasterFingerprint() => manager.getMasterFingerprint();
}
diff --git a/cw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dart b/cw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dart
index cc4e209..885868d 100644
--- a/cw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dart
+++ b/cw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dart
@@ -1,5 +1,17 @@
import 'dart:typed_data';
+import 'package:cw_bitcoin/electrum_wallet.dart';
+import 'package:bitcoin_base/bitcoin_base.dart';
+import 'package:cw_bitcoin/psbt/transaction_builder.dart';
+
mixin BitcoinHardwareWalletService {
- Future<Uint8List> getMasterFingerprint();
+ Future<Uint8List> getMasterFingerprint() async => Uint8List.fromList([0, 0, 0, 0]);
+}
+
+mixin LitecoinHardwareWalletService on BitcoinHardwareWalletService {
+ Future<String> signLitecoinTransaction({
+ required List<BitcoinBaseOutput> outputs,
+ required List<PSBTReadyUtxoWithAddress> inputs,
+ required Map<String, PublicKeyWithDerivationPath> publicKeys,
+ });
}
diff --git a/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart b/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart
index 8334eb8..9ede571 100644
--- a/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart
+++ b/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart
@@ -1,22 +1,26 @@
import 'dart:async';
+import 'dart:typed_data';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
+import 'package:cw_bitcoin/electrum_wallet.dart';
+import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
+import 'package:cw_bitcoin/psbt/transaction_builder.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/hardware/hardware_account_data.dart';
import 'package:cw_core/hardware/hardware_wallet_service.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:ledger_litecoin/ledger_litecoin.dart';
-class LitecoinLedgerService extends HardwareWalletService {
- LitecoinLedgerService(this.ledgerConnection);
+class LitecoinLedgerService extends HardwareWalletService with BitcoinHardwareWalletService, LitecoinHardwareWalletService {
+ LitecoinLedgerService(this.ledgerConnection)
+ : litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
final LedgerConnection ledgerConnection;
+ final LitecoinLedgerApp litecoinLedgerApp;
@override
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
- final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
-
await litecoinLedgerApp.getVersion();
final accounts = <HardwareAccountData>[];
@@ -42,4 +46,43 @@ class LitecoinLedgerService extends HardwareWalletService {
return accounts;
}
+
+ @override
+ Future<String> signLitecoinTransaction({
+ required List<BitcoinBaseOutput> outputs,
+ required List<PSBTReadyUtxoWithAddress> inputs,
+ required Map<String, PublicKeyWithDerivationPath> publicKeys,
+ }) {
+
+ final readyInputs = <LedgerTransaction>[];
+ for (final utxo in inputs) {
+ final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
+
+ readyInputs.add(LedgerTransaction(
+ rawTx: utxo.rawTx,
+ outputIndex: utxo.utxo.vout,
+ ownerPublicKey: Uint8List.fromList(hex.decode(publicKeyAndDerivationPath.publicKey)),
+ ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
+ ));
+ }
+
+ // Check if we have the key to one of the output addresses to hide change on the device
+ String? changePath;
+ for (final output in outputs) {
+ final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()];
+ if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath;
+ }
+
+ return litecoinLedgerApp.createTransaction(
+ inputs: readyInputs,
+ outputs: outputs
+ .map((e) => TransactionOutput.fromBigInt((e as BitcoinOutput).value,
+ Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
+ .toList(),
+ changePath: changePath,
+ sigHashType: 0x01,
+ additionals: ["bech32"],
+ isSegWit: true,
+ useTrustedInputForSegwit: true);
+ }
}
diff --git a/cw_bitcoin/lib/hardware/trezor_service.dart b/cw_bitcoin/lib/hardware/trezor_service.dart
new file mode 100644
index 0000000..8c5247a
--- /dev/null
+++ b/cw_bitcoin/lib/hardware/trezor_service.dart
@@ -0,0 +1,179 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:bitcoin_base/bitcoin_base.dart';
+import 'package:blockchain_utils/blockchain_utils.dart';
+import 'package:cw_bitcoin/electrum_wallet.dart';
+import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
+import 'package:cw_bitcoin/psbt/transaction_builder.dart';
+import 'package:cw_bitcoin/utils.dart';
+import 'package:cw_core/hardware/hardware_account_data.dart';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
+import 'package:ledger_bitcoin/psbt.dart';
+import 'package:trezor_connect/trezor_connect.dart';
+
+class BitcoinTrezorService extends HardwareWalletService with BitcoinHardwareWalletService {
+ BitcoinTrezorService(this.connect);
+
+ final TrezorConnect connect;
+
+ @override
+ Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
+ final indexRange = List.generate(limit, (i) => i + index);
+ final requestParams = <TrezorGetPublicKeyParams>[];
+
+ for (final i in indexRange) {
+ requestParams.add(TrezorGetPublicKeyParams(path: "m/84'/0'/$i'"));
+ }
+
+ final accounts = await connect.getPublicKeyBundle(requestParams);
+
+ return accounts?.map((account) {
+ final hd = Bip32Slip10Secp256k1.fromExtendedKey(account.xpub).childKey(Bip32KeyIndex(0));
+ final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
+ return HardwareAccountData(
+ address: address,
+ xpub: account.xpub,
+ accountIndex: account.path[2] - 0x80000000, // unharden the path to get the index
+ derivationPath: account.serializedPath,
+ );
+ }).toList() ??
+ [];
+ }
+
+ @override
+ Future<Uint8List> signTransaction({required String transaction}) async {
+ final psbt = PsbtV2()..deserialize(base64Decode(transaction));
+
+ final inputs = <TrezorTxInput>[];
+ final inputCount = psbt.getGlobalInputCount();
+ for (var i = 0; i < inputCount; i++) {
+ final inputTxRaw = psbt.getInputNonWitnessUtxo(i);
+ final inputTx = BtcTransaction.fromRaw(hex.encode(inputTxRaw!));
+ final inputOutputIndex = psbt.getInputOutputIndex(i);
+
+ final publicKeys = psbt.inputMaps[i].keys.where((e) => e.startsWith("06"));
+ final pubkey = Uint8List.fromList(hex.decode(publicKeys.first.substring(2)));
+
+ inputs.add(TrezorTxInput(
+ prevHash: hex.encode(psbt.getInputPreviousTxid(i).reversed.toList()),
+ prevIndex: inputOutputIndex,
+ amount: inputTx.outputs[inputOutputIndex].amount.toInt(),
+ addressPath: psbt.getInputBip32Derivation(i, pubkey)!.$2,
+ sequence: psbt.getInputSequence(i),
+ scriptType: "SPENDWITNESS"));
+ }
+
+ final outputs = <TrezorTxOutput>[];
+ final outputCount = psbt.getGlobalOutputCount();
+ for (var i = 0; i < outputCount; i++) {
+ final script = Script.fromRaw(byteData: psbt.getOutputScript(i));
+ outputs.add(TrezorTxOutput(
+ amount: psbt.getOutputAmount(i),
+ address: script.toAddress(),
+ scriptType: _getScriptType(script.getAddressType()!)
+ // ToDo: addressPath: psbt.getOutputBip32Derivation(i, pubkey).$2, // To highlight change outputs
+ ));
+ }
+
+ final signedTx = await connect.signTransaction(coin: 'btc', inputs: inputs, outputs: outputs);
+
+ return Uint8List.fromList(BytesUtils.fromHexString(signedTx!.serializedTx));
+ }
+
+ @override
+ Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) async {
+ final sig = await connect.signMessage(derivationPath ?? "m/84'/0'/0'/0/0",
+ message: hex.encode(message), hex: true);
+ return base64Decode(sig!.signature);
+ }
+}
+
+class LitecoinTrezorService extends HardwareWalletService with BitcoinHardwareWalletService, LitecoinHardwareWalletService {
+ LitecoinTrezorService(this.connect);
+
+ final TrezorConnect connect;
+
+ @override
+ Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
+ final indexRange = List.generate(limit, (i) => i + index);
+ final requestParams = <TrezorGetPublicKeyParams>[];
+ final xpubVersion = Bip44Conf.litecoinMainNet.altKeyNetVer;
+
+ for (final i in indexRange) {
+ final derivationPath = "m/84'/2'/$i'";
+ requestParams.add(TrezorGetPublicKeyParams(path: derivationPath, coin: "LTC"));
+ }
+
+ final accounts = await connect.getPublicKeyBundle(requestParams);
+
+ return accounts?.map((account) {
+ final hd = Bip32Slip10Secp256k1.fromExtendedKey(account.xpub, xpubVersion)
+ .childKey(Bip32KeyIndex(0));
+
+ final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet);
+ return HardwareAccountData(
+ address: address,
+ xpub: account.xpub,
+ accountIndex: account.path[2] - 0x80000000, // unharden the path to get the index
+ derivationPath: account.serializedPath,
+ );
+ }).toList() ??
+ [];
+ }
+
+ @override
+ Future<String> signLitecoinTransaction({
+ required List<BitcoinBaseOutput> outputs,
+ required List<PSBTReadyUtxoWithAddress> inputs,
+ required Map<String, PublicKeyWithDerivationPath> publicKeys,
+ }) async {
+ final readyInputs = inputs
+ .map((input) => TrezorTxInput(
+ prevHash: input.utxo.txHash,
+ prevIndex: input.utxo.vout,
+ amount: input.utxo.value.toInt(),
+ addressPath: Bip32PathParser.parse(input.ownerDerivationPath).toList(),
+ scriptType: "SPENDWITNESS",
+ ))
+ .toList();
+
+ final readyOutputs = outputs.map((output) {
+ final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()];
+
+ return TrezorTxOutput(
+ amount: output.toOutput.amount.toInt(),
+ address: maybeChangePath != null
+ ? null
+ : output.toOutput.scriptPubKey.toAddress(network: LitecoinNetwork.mainnet),
+ scriptType: _getScriptType(output.toOutput.scriptPubKey.getAddressType()!),
+ addressPath: maybeChangePath != null
+ ? Bip32PathParser.parse(maybeChangePath.derivationPath).toList()
+ : null,
+ );
+ }).toList();
+
+ final signedTx =
+ await connect.signTransaction(coin: 'LTC', inputs: readyInputs, outputs: readyOutputs);
+
+ return signedTx!.serializedTx;
+ }
+}
+
+String _getScriptType(BitcoinAddressType addressType) {
+ switch (addressType) {
+ case P2pkhAddressType.p2pkh:
+ return "PAYTOADDRESS";
+ case P2shAddressType.p2wpkhInP2sh:
+ return "PAYTOSCRIPTHASH";
+ case SegwitAddresType.p2tr:
+ return "PAYTOTAPROOT";
+ case SegwitAddresType.p2wsh:
+ return "PAYTOP2SHWITNESS";
+ case SegwitAddresType.p2wpkh:
+ return "PAYTOWITNESS";
+ default:
+ throw Exception("Unknown Address Type");
+ }
+}
diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index 7b32547..eef2404 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -1,53 +1,53 @@
import 'dart:async';
import 'dart:convert';
-
-import 'package:convert/convert.dart' as convert;
import 'dart:math';
-import 'package:collection/collection.dart';
-import 'package:crypto/crypto.dart';
-import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
-import 'package:cw_core/cake_hive.dart';
-import 'package:cw_core/mweb_utxo.dart';
-import 'package:cw_core/unspent_coin_type.dart';
-import 'package:cw_core/utils/print_verbose.dart';
-import 'package:cw_core/node.dart';
-import 'package:cw_mweb/mwebd.pbgrpc.dart';
-import 'package:fixnum/fixnum.dart';
+
import 'package:bip39/bip39.dart' as bip39;
import 'package:bitcoin_base/bitcoin_base.dart';
+import 'package:bitcoin_base/src/crypto/keypair/sign_utils.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:blockchain_utils/signer/ecdsa_signing_key.dart';
+import 'package:collection/collection.dart';
+import 'package:convert/convert.dart' as convert;
+import 'package:crypto/crypto.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
+import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
+import 'package:cw_bitcoin/electrum_balance.dart';
+import 'package:cw_bitcoin/electrum_derivations.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart';
+import 'package:cw_bitcoin/electrum_wallet.dart';
+import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
+import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
+import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
+import 'package:cw_bitcoin/psbt/transaction_builder.dart';
import 'package:cw_bitcoin/utils.dart';
-import 'package:cw_bitcoin/electrum_derivations.dart';
-import 'package:cw_core/encryption_file_utils.dart';
+import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/crypto_currency.dart';
+import 'package:cw_core/encryption_file_utils.dart';
+import 'package:cw_core/mweb_utxo.dart';
+import 'package:cw_core/node.dart';
+import 'package:cw_core/output_info.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_direction.dart';
-import 'package:cw_core/unspent_coins_info.dart';
-import 'package:cw_bitcoin/electrum_balance.dart';
-import 'package:cw_bitcoin/electrum_wallet.dart';
-import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
-import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
import 'package:cw_core/transaction_priority.dart';
+import 'package:cw_core/unspent_coin_type.dart';
+import 'package:cw_core/unspent_coins_info.dart';
+import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_keys_file.dart';
-import 'package:cw_core/output_info.dart';
+import 'package:cw_core/wallet_type.dart';
+import 'package:cw_mweb/cw_mweb.dart';
+import 'package:cw_mweb/mwebd.pbgrpc.dart';
+import 'package:fixnum/fixnum.dart';
import 'package:flutter/foundation.dart';
import 'package:grpc/grpc.dart';
import 'package:hive/hive.dart';
-import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
-import 'package:ledger_litecoin/ledger_litecoin.dart';
import 'package:mobx/mobx.dart';
-import 'package:cw_core/wallet_type.dart';
-import 'package:cw_mweb/cw_mweb.dart';
-import 'package:bitcoin_base/src/crypto/keypair/sign_utils.dart';
import 'package:pointycastle/ecc/api.dart';
import 'package:pointycastle/ecc/curves/secp256k1.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -144,6 +144,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}
});
}
+
late final Bip32Slip10Secp256k1? mwebHd;
late final Box<MwebUtxo> mwebUtxosBox;
Timer? _syncTimer;
@@ -160,6 +161,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
bool get hasRescan => true;
List<int> get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
+
List<int> get spendSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
static Future<LitecoinWallet> create(
@@ -1064,9 +1066,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
tx.isMweb = mwebEnabled;
if (!mwebEnabled) {
- tx.changeAddressOverride =
- (await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(coinTypeToSpendFrom: UnspentCoinType.nonMweb))
- .address;
+ tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses)
+ .getChangeAddress(coinTypeToSpendFrom: UnspentCoinType.nonMweb))
+ .address;
return tx;
}
await waitForMwebAddresses();
@@ -1119,7 +1121,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
bool isRegular = !hasMwebInput && !hasMwebOutput;
bool shouldNotUseMwebChange = isPegIn || isRegular || !hasMwebInput;
tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses)
- .getChangeAddress(coinTypeToSpendFrom: shouldNotUseMwebChange ? UnspentCoinType.nonMweb : UnspentCoinType.any))
+ .getChangeAddress(
+ coinTypeToSpendFrom:
+ shouldNotUseMwebChange ? UnspentCoinType.nonMweb : UnspentCoinType.any))
.address;
if (isRegular) {
tx.isMweb = false;
@@ -1368,16 +1372,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
return false;
}
- LedgerConnection? _ledgerConnection;
- LitecoinLedgerApp? _litecoinLedgerApp;
-
- @override
- void setLedgerConnection(LedgerConnection connection) {
- _ledgerConnection = connection;
- _litecoinLedgerApp = LitecoinLedgerApp(_ledgerConnection!,
- derivationPath: walletInfo.derivationInfo!.derivationPath!);
- }
-
@override
Future<BtcTransaction> buildHardwareWalletTransaction({
required List<BitcoinBaseOutput> outputs,
@@ -1391,38 +1385,26 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
}) async {
- final readyInputs = <LedgerTransaction>[];
+ final masterFingerprint =
+ await (hardwareWalletService as BitcoinHardwareWalletService).getMasterFingerprint();
+
+ final readyInputs = <PSBTReadyUtxoWithAddress>[];
for (final utxo in utxos) {
final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
- readyInputs.add(LedgerTransaction(
+ readyInputs.add(PSBTReadyUtxoWithAddress(
+ utxo: utxo.utxo,
rawTx: rawTx,
- outputIndex: utxo.utxo.vout,
- ownerPublicKey: Uint8List.fromList(hex.decode(publicKeyAndDerivationPath.publicKey)),
+ ownerDetails: utxo.ownerDetails,
ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
- // sequence: enableRBF ? 0x1 : 0xffffffff,
- sequence: 0xffffffff,
+ ownerMasterFingerprint:masterFingerprint,
+ ownerPublicKey: publicKeyAndDerivationPath.publicKey,
));
}
- String? changePath;
- for (final output in outputs) {
- final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()];
- if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath;
- }
-
- final rawHex = await _litecoinLedgerApp!.createTransaction(
- inputs: readyInputs,
- outputs: outputs
- .map((e) => TransactionOutput.fromBigInt((e as BitcoinOutput).value,
- Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
- .toList(),
- changePath: changePath,
- sigHashType: 0x01,
- additionals: ["bech32"],
- isSegWit: true,
- useTrustedInputForSegwit: true);
+ final rawHex = await (hardwareWalletService as LitecoinHardwareWalletService)
+ .signLitecoinTransaction(outputs: outputs, inputs: readyInputs, publicKeys: publicKeys);
return BtcTransaction.fromRaw(rawHex);
}
diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock
index 6f0580b..8cc60e9 100644
--- a/cw_bitcoin/pubspec.lock
+++ b/cw_bitcoin/pubspec.lock
@@ -604,8 +604,8 @@ packages:
dependency: "direct main"
description:
path: "packages/ledger-bitcoin"
- ref: "6e6ce17c2ba5cfe3d16a60d53f930db8e827b8f3"
- resolved-ref: "6e6ce17c2ba5cfe3d16a60d53f930db8e827b8f3"
+ ref: dc42621f55702d0732681afc2f90bc0047f26633
+ resolved-ref: dc42621f55702d0732681afc2f90bc0047f26633
url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
source: git
version: "0.0.3"
@@ -1091,6 +1091,15 @@ packages:
relative: true
source: path
version: "0.0.1"
+ trezor_connect:
+ dependency: "direct main"
+ description:
+ path: "."
+ ref: "59b3ceec158d3393b20d0c596cab2abcc62ac8e4"
+ resolved-ref: "59b3ceec158d3393b20d0c596cab2abcc62ac8e4"
+ url: "https://github.com/cake-tech/trezor_connect"
+ source: git
+ version: "0.0.1"
tuple:
dependency: transitive
description:
@@ -1140,6 +1149,70 @@ packages:
url: "https://github.com/bukata-sa/bc-ur-dart"
source: git
version: "0.1.0"
+ url_launcher:
+ dependency: transitive
+ description:
+ name: url_launcher
+ sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.3.2"
+ url_launcher_android:
+ dependency: transitive
+ description:
+ name: url_launcher_android
+ sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.3.20"
+ url_launcher_ios:
+ dependency: transitive
+ description:
+ name: url_launcher_ios
+ sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.3.4"
+ url_launcher_linux:
+ dependency: transitive
+ description:
+ name: url_launcher_linux
+ sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.1"
+ url_launcher_macos:
+ dependency: transitive
+ description:
+ name: url_launcher_macos
+ sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.3"
+ url_launcher_platform_interface:
+ dependency: transitive
+ description:
+ name: url_launcher_platform_interface
+ sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.2"
+ url_launcher_web:
+ dependency: transitive
+ description:
+ name: url_launcher_web
+ sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.1"
+ url_launcher_windows:
+ dependency: transitive
+ description:
+ name: url_launcher_windows
+ sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.4"
uuid:
dependency: transitive
description:
@@ -1229,5 +1302,5 @@ packages:
source: hosted
version: "2.2.2"
sdks:
- dart: ">=3.7.0-0 <4.0.0"
- flutter: ">=3.27.0"
+ dart: ">=3.8.0 <4.0.0"
+ flutter: ">=3.29.0"
diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml
index 5bb0b17..d8d360e 100644
--- a/cw_bitcoin/pubspec.yaml
+++ b/cw_bitcoin/pubspec.yaml
@@ -49,11 +49,15 @@ dependencies:
git:
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
path: packages/ledger-bitcoin
- ref: 6e6ce17c2ba5cfe3d16a60d53f930db8e827b8f3
+ ref: dc42621f55702d0732681afc2f90bc0047f26633
ledger_litecoin:
git:
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
path: packages/ledger-litecoin
+ trezor_connect:
+ git:
+ url: https://github.com/cake-tech/trezor_connect
+ ref: 59b3ceec158d3393b20d0c596cab2abcc62ac8e4
bitbox_flutter:
path: ../scripts/bitbox_flutter
socks_socket:
diff --git a/cw_core/lib/hardware/device_connection_type.dart b/cw_core/lib/hardware/device_connection_type.dart
index 92c8096..fa04bd1 100644
--- a/cw_core/lib/hardware/device_connection_type.dart
+++ b/cw_core/lib/hardware/device_connection_type.dart
@@ -13,7 +13,7 @@ enum DeviceConnectionType {
case HardwareWalletType.bitbox:
isSupported = [
WalletType.bitcoin,
- // WalletType.litecoin,
+ WalletType.litecoin,
WalletType.ethereum,
WalletType.polygon
].contains(walletType);
@@ -27,6 +27,15 @@ enum DeviceConnectionType {
WalletType.polygon
].contains(walletType);
break;
+ case HardwareWalletType.trezor:
+ isSupported = [
+ // WalletType.monero,
+ WalletType.bitcoin,
+ WalletType.litecoin,
+ WalletType.ethereum,
+ WalletType.polygon,
+ ].contains(walletType);
+ break;
}
return isSupported
diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart
index 15a9438..4f849ec 100644
--- a/cw_core/lib/wallet_info.dart
+++ b/cw_core/lib/wallet_info.dart
@@ -35,6 +35,8 @@ enum HardwareWalletType {
seedsigner,
@HiveField(5)
keystone,
+ @HiveField(6)
+ trezor,
}
@HiveType(typeId: DerivationInfo.typeId)
@@ -234,8 +236,11 @@ class WalletInfo extends HiveObject {
return showIntroCakePayCard!;
}
- bool get isHardwareWallet =>
- [HardwareWalletType.ledger, HardwareWalletType.bitbox].contains(hardwareWalletType);
+ bool get isHardwareWallet => [
+ HardwareWalletType.bitbox,
+ HardwareWalletType.ledger,
+ HardwareWalletType.trezor
+ ].contains(hardwareWalletType);
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp);
diff --git a/cw_evm/lib/evm_chain_client.dart b/cw_evm/lib/evm_chain_client.dart
index 215e8bc..29a88dc 100644
--- a/cw_evm/lib/evm_chain_client.dart
+++ b/cw_evm/lib/evm_chain_client.dart
@@ -9,7 +9,6 @@ import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/evm_erc20_balance.dart';
-import 'package:cw_evm/hardware/evm_bitbox_credentials.dart';
import 'package:cw_evm/pending_evm_chain_transaction.dart';
import 'package:cw_evm/.secrets.g.dart' as secrets;
import 'package:flutter/foundation.dart';
diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart
index db35030..35420a6 100644
--- a/cw_evm/lib/evm_chain_wallet.dart
+++ b/cw_evm/lib/evm_chain_wallet.dart
@@ -29,8 +29,9 @@ import 'package:cw_evm/evm_chain_transaction_history.dart';
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/evm_chain_wallet_addresses.dart';
-import 'package:cw_evm/hardware/evm_bitbox_credentials.dart';
-import 'package:cw_evm/hardware/evm_ledger_credentials.dart';
+import 'package:cw_evm/hardware/evm_chain_bitbox_credentials.dart';
+import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart';
+import 'package:cw_evm/hardware/evm_chain_trezor_credentials.dart';
import 'package:hex/hex.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
@@ -214,6 +215,9 @@ abstract class EVMChainWalletBase
} else if (walletInfo.hardwareWalletType == HardwareWalletType.bitbox) {
_evmChainPrivateKey = EvmBitboxCredentials(walletInfo.address);
walletAddresses.address = walletInfo.address;
+ } else if (walletInfo.hardwareWalletType == HardwareWalletType.trezor) {
+ _evmChainPrivateKey = EvmTrezorCredentials(walletInfo.address);
+ walletAddresses.address = walletInfo.address;
} else {
_evmChainPrivateKey = await getPrivateKey(
mnemonic: _mnemonic,
diff --git a/cw_evm/lib/hardware/evm_bitbox_credentials.dart b/cw_evm/lib/hardware/evm_bitbox_credentials.dart
deleted file mode 100644
index d8dca4e..0000000
--- a/cw_evm/lib/hardware/evm_bitbox_credentials.dart
+++ /dev/null
@@ -1,79 +0,0 @@
-import 'dart:async';
-import 'dart:typed_data';
-
-import 'package:bitbox_flutter/bitbox_manager.dart';
-import 'package:cw_core/hardware/device_not_connected_exception.dart' as exception;
-import 'package:web3dart/crypto.dart';
-import 'package:web3dart/web3dart.dart';
-
-class EvmBitboxCredentials extends CredentialsWithKnownAddress {
- final String _address;
-
- BitboxManager? bitboxManager;
- String? derivationPath;
-
- EvmBitboxCredentials(this._address);
-
- @override
- EthereumAddress get address => EthereumAddress.fromHex(_address);
-
- void setBitbox(BitboxManager connection, [String? derivationPath_]) {
- bitboxManager = connection;
- derivationPath = derivationPath_ ?? "m/44'/60'/0'/0/0";
- }
-
- @override
- MsgSignature signToEcSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) =>
- throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
-
- @override
- Future<MsgSignature> signToSignature(Uint8List payload,
- {int? chainId, bool isEIP1559 = false}) async {
- if (bitboxManager == null) {
- throw exception.DeviceNotConnectedException();
- }
-
- if (isEIP1559) payload = payload.sublist(1);
- final sig = await bitboxManager!
- .signETHRLPTransaction(chainId ?? 1, derivationPath!, bytesToHex(payload), isEIP1559);
-
- final r = bytesToHex(sig.sublist(0, 32));
- final s = bytesToHex(sig.sublist(32, 32 + 32));
- final v = sig.last.toInt();
-
- if (isEIP1559) {
- return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), v);
- }
-
- var truncChainId = chainId ?? 1;
- while (truncChainId.bitLength > 32) {
- truncChainId >>= 8;
- }
-
- final truncTarget = truncChainId * 2 + 35;
-
- int parity = v;
- if (truncTarget & 0xff == v) {
- parity = 0;
- } else if ((truncTarget + 1) & 0xff == v) {
- parity = 1;
- }
-
- // https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L26
- final chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity;
-
- return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
- }
-
- @override
- Future<Uint8List> signPersonalMessage(Uint8List payload, {int? chainId}) async {
- if (isNotConnected) throw exception.DeviceNotConnectedException();
- return await bitboxManager!.signETHMessage(chainId ?? 1, derivationPath!, payload);
- }
-
- @override
- Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
- throw UnimplementedError("EvmLedgerCredentials.signPersonalMessageToUint8List");
-
- bool get isNotConnected => bitboxManager == null;
-}
diff --git a/cw_evm/lib/hardware/evm_chain_bitbox_credentials.dart b/cw_evm/lib/hardware/evm_chain_bitbox_credentials.dart
new file mode 100644
index 0000000..d8dca4e
--- /dev/null
+++ b/cw_evm/lib/hardware/evm_chain_bitbox_credentials.dart
@@ -0,0 +1,79 @@
+import 'dart:async';
+import 'dart:typed_data';
+
+import 'package:bitbox_flutter/bitbox_manager.dart';
+import 'package:cw_core/hardware/device_not_connected_exception.dart' as exception;
+import 'package:web3dart/crypto.dart';
+import 'package:web3dart/web3dart.dart';
+
+class EvmBitboxCredentials extends CredentialsWithKnownAddress {
+ final String _address;
+
+ BitboxManager? bitboxManager;
+ String? derivationPath;
+
+ EvmBitboxCredentials(this._address);
+
+ @override
+ EthereumAddress get address => EthereumAddress.fromHex(_address);
+
+ void setBitbox(BitboxManager connection, [String? derivationPath_]) {
+ bitboxManager = connection;
+ derivationPath = derivationPath_ ?? "m/44'/60'/0'/0/0";
+ }
+
+ @override
+ MsgSignature signToEcSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) =>
+ throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
+
+ @override
+ Future<MsgSignature> signToSignature(Uint8List payload,
+ {int? chainId, bool isEIP1559 = false}) async {
+ if (bitboxManager == null) {
+ throw exception.DeviceNotConnectedException();
+ }
+
+ if (isEIP1559) payload = payload.sublist(1);
+ final sig = await bitboxManager!
+ .signETHRLPTransaction(chainId ?? 1, derivationPath!, bytesToHex(payload), isEIP1559);
+
+ final r = bytesToHex(sig.sublist(0, 32));
+ final s = bytesToHex(sig.sublist(32, 32 + 32));
+ final v = sig.last.toInt();
+
+ if (isEIP1559) {
+ return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), v);
+ }
+
+ var truncChainId = chainId ?? 1;
+ while (truncChainId.bitLength > 32) {
+ truncChainId >>= 8;
+ }
+
+ final truncTarget = truncChainId * 2 + 35;
+
+ int parity = v;
+ if (truncTarget & 0xff == v) {
+ parity = 0;
+ } else if ((truncTarget + 1) & 0xff == v) {
+ parity = 1;
+ }
+
+ // https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L26
+ final chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity;
+
+ return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
+ }
+
+ @override
+ Future<Uint8List> signPersonalMessage(Uint8List payload, {int? chainId}) async {
+ if (isNotConnected) throw exception.DeviceNotConnectedException();
+ return await bitboxManager!.signETHMessage(chainId ?? 1, derivationPath!, payload);
+ }
+
+ @override
+ Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
+ throw UnimplementedError("EvmLedgerCredentials.signPersonalMessageToUint8List");
+
+ bool get isNotConnected => bitboxManager == null;
+}
diff --git a/cw_evm/lib/hardware/evm_chain_ledger_credentials.dart b/cw_evm/lib/hardware/evm_chain_ledger_credentials.dart
new file mode 100644
index 0000000..13a93cf
--- /dev/null
+++ b/cw_evm/lib/hardware/evm_chain_ledger_credentials.dart
@@ -0,0 +1,109 @@
+import 'dart:async';
+import 'dart:typed_data';
+
+import 'package:cw_core/hardware/device_not_connected_exception.dart'
+ as exception;
+import 'package:cw_core/utils/print_verbose.dart';
+import 'package:ledger_ethereum/ledger_ethereum.dart';
+import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
+import 'package:web3dart/crypto.dart';
+import 'package:web3dart/web3dart.dart';
+
+class EvmLedgerCredentials extends CredentialsWithKnownAddress {
+ final String _address;
+
+ EthereumLedgerApp? ethereumLedgerApp;
+
+ EvmLedgerCredentials(this._address);
+
+ @override
+ EthereumAddress get address => EthereumAddress.fromHex(_address);
+
+ void setLedgerConnection(LedgerConnection connection,
+ [String? derivationPath]) {
+ ethereumLedgerApp = EthereumLedgerApp(connection,
+ derivationPath: derivationPath ?? "m/44'/60'/0'/0/0");
+ }
+
+ @override
+ MsgSignature signToEcSignature(Uint8List payload,
+ {int? chainId, bool isEIP1559 = false}) =>
+ throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
+
+ @override
+ Future<MsgSignature> signToSignature(Uint8List payload,
+ {int? chainId, bool isEIP1559 = false}) async {
+ if (ethereumLedgerApp == null) {
+ throw exception.DeviceNotConnectedException();
+ }
+
+ final sig = await ethereumLedgerApp!.signTransaction(payload);
+
+ final v = sig[0].toInt();
+ final r = bytesToHex(sig.sublist(1, 1 + 32));
+ final s = bytesToHex(sig.sublist(1 + 32, 1 + 32 + 32));
+
+ var truncChainId = chainId ?? 1;
+ while (truncChainId.bitLength > 32) {
+ truncChainId >>= 8;
+ }
+
+ final truncTarget = truncChainId * 2 + 35;
+
+ int parity = v;
+ if (truncTarget & 0xff == v) {
+ parity = 0;
+ } else if ((truncTarget + 1) & 0xff == v) {
+ parity = 1;
+ }
+
+ // https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L26
+ int chainIdV;
+ if (isEIP1559) {
+ chainIdV = v;
+ } else {
+ chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity;
+ }
+
+ return MsgSignature(
+ BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
+ }
+
+ @override
+ Future<Uint8List> signPersonalMessage(Uint8List payload,
+ {int? chainId}) async {
+ if (isNotConnected) throw exception.DeviceNotConnectedException();
+
+ final sig = await ethereumLedgerApp!.signMessage(payload);
+
+ if (sig.isEmpty) throw Exception("No Signature received from device!");
+
+ final r = sig.sublist(1, 1 + 32);
+ final s = sig.sublist(1 + 32, 1 + 32 + 32);
+ final v = [sig[0]];
+
+ // https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L63
+ return Uint8List.fromList(r + s + v);
+ }
+
+ @override
+ Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
+ throw UnimplementedError(
+ "EvmLedgerCredentials.signPersonalMessageToUint8List");
+
+ Future<void> provideERC20Info(
+ String erc20ContractAddress, int chainId) async {
+ if (isNotConnected) throw exception.DeviceNotConnectedException();
+
+ try {
+ await ethereumLedgerApp!.getAndProvideERC20TokenInformation(
+ erc20ContractAddress: erc20ContractAddress, chainId: chainId);
+ } catch (e) {
+ printV(e);
+ rethrow;
+ // if (e.errorCode != -28672) rethrow;
+ }
+ }
+
+ bool get isNotConnected => ethereumLedgerApp == null || ethereumLedgerApp!.connection.isDisconnected;
+}
diff --git a/cw_evm/lib/hardware/evm_chain_trezor_credentials.dart b/cw_evm/lib/hardware/evm_chain_trezor_credentials.dart
new file mode 100644
index 0000000..725cff2
--- /dev/null
+++ b/cw_evm/lib/hardware/evm_chain_trezor_credentials.dart
@@ -0,0 +1,108 @@
+import 'dart:async';
+import 'dart:typed_data';
+
+import 'package:cw_core/hardware/device_not_connected_exception.dart' as exception;
+import 'package:trezor_connect/trezor_connect.dart';
+import 'package:web3dart/crypto.dart';
+import 'package:web3dart/web3dart.dart';
+
+import '../utils/rlp_decode.dart' as rlp;
+
+class EvmTrezorCredentials extends CredentialsWithKnownAddress {
+ final String _address;
+
+ TrezorConnect? trezorConnect;
+ String? derivationPath;
+
+ EvmTrezorCredentials(this._address);
+
+ @override
+ EthereumAddress get address => EthereumAddress.fromHex(_address);
+
+ void setTrezorConnect(TrezorConnect connection, [String? derivationPath_]) {
+ trezorConnect = connection;
+ derivationPath = derivationPath_ ?? "m/44'/60'/0'/0/0";
+ }
+
+ @override
+ MsgSignature signToEcSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) =>
+ throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
+
+ @override
+ Future<MsgSignature> signToSignature(Uint8List payload,
+ {int? chainId, bool isEIP1559 = false}) async {
+ if (trezorConnect == null) {
+ throw exception.DeviceNotConnectedException();
+ }
+
+ if (isEIP1559) payload = payload.sublist(1);
+
+ final data = rlp.decode(payload);
+
+ TrezorEthereumTransaction tx;
+ if (isEIP1559) {
+ final chainId = data[0][0];
+ final nonce = bytesToHex(data[1], include0x: true);
+ final maxPriorityFeePerGas = bytesToHex(data[2], include0x: true);
+ final maxFeePerGas = bytesToHex(data[3], include0x: true);
+ final maxGas = bytesToHex(data[4], include0x: true);
+ final to = bytesToHex(data[5], include0x: true);
+ final value = bytesToHex(data[6], include0x: true);
+ final txData = bytesToHex(data[7], include0x: true);
+
+ tx = TrezorEthereumTransaction(
+ to: to,
+ value: value,
+ maxFeePerGas: maxFeePerGas,
+ maxPriorityFeePerGas: maxPriorityFeePerGas,
+ gasLimit: maxGas,
+ nonce: nonce,
+ chainId: chainId,
+ data: txData,
+ );
+ } else {
+ final nonce = bytesToHex(data[0]);
+ final gasPrice = bytesToHex(data[1]);
+ final maxGas = bytesToHex(data[2]);
+ final to = bytesToHex(data[3]);
+ final value = bytesToHex(data[4]);
+ final txData = bytesToHex(data[5]);
+ tx = TrezorEthereumTransaction(
+ to: to,
+ value: value,
+ gasPrice: gasPrice,
+ gasLimit: maxGas,
+ nonce: nonce,
+ chainId: chainId ?? 1,
+ data: txData,
+ );
+ }
+
+ final sig = (await trezorConnect!.ethereumSignTransaction(derivationPath!, transaction: tx))!;
+
+ final v = int.parse(strip0x(sig.v), radix: 16);
+
+ if (isEIP1559) {
+ return MsgSignature(
+ BigInt.parse(strip0x(sig.r), radix: 16), BigInt.parse(strip0x(sig.s), radix: 16), v);
+ }
+
+ return MsgSignature(
+ BigInt.parse(strip0x(sig.r), radix: 16), BigInt.parse(strip0x(sig.s), radix: 16), v);
+ }
+
+ @override
+ Future<Uint8List> signPersonalMessage(Uint8List payload, {int? chainId}) async {
+ if (isNotConnected) throw exception.DeviceNotConnectedException();
+ return hexToBytes((await trezorConnect!
+ .ethereumSignMessage(derivationPath!, message: bytesToHex(payload), hex: true))
+ ?.signature ??
+ '');
+ }
+
+ @override
+ Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
+ throw UnimplementedError("EvmLedgerCredentials.signPersonalMessageToUint8List");
+
+ bool get isNotConnected => trezorConnect == null;
+}
diff --git a/cw_evm/lib/hardware/evm_chain_trezor_service.dart b/cw_evm/lib/hardware/evm_chain_trezor_service.dart
new file mode 100644
index 0000000..0516607
--- /dev/null
+++ b/cw_evm/lib/hardware/evm_chain_trezor_service.dart
@@ -0,0 +1,33 @@
+import 'dart:async';
+
+import 'package:cw_core/hardware/hardware_account_data.dart';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
+import 'package:trezor_connect/trezor_connect.dart';
+
+class EVMChainTrezorService extends HardwareWalletService {
+ EVMChainTrezorService(this.connect, {this.chainId = 1});
+
+ final TrezorConnect connect;
+ final int chainId;
+
+ @override
+ Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
+ final indexRange = List.generate(limit, (i) => i + index);
+
+ final requestParams = <TrezorGetAddressParams>[];
+ for (final i in indexRange) {
+ final derivationPath = "m/44'/60'/$i'/0/0";
+ requestParams.add(TrezorGetAddressParams(path: derivationPath));
+ }
+ final accounts = await connect.ethereumGetAddressBundle(requestParams);
+
+ return accounts
+ ?.map((account) => HardwareAccountData(
+ address: account.address,
+ accountIndex: account.path[2] - 0x80000000, // unharden the path to get the index
+ derivationPath: account.serializedPath,
+ ))
+ .toList() ??
+ [];
+ }
+}
diff --git a/cw_evm/lib/hardware/evm_ledger_credentials.dart b/cw_evm/lib/hardware/evm_ledger_credentials.dart
deleted file mode 100644
index 13a93cf..0000000
--- a/cw_evm/lib/hardware/evm_ledger_credentials.dart
+++ /dev/null
@@ -1,109 +0,0 @@
-import 'dart:async';
-import 'dart:typed_data';
-
-import 'package:cw_core/hardware/device_not_connected_exception.dart'
- as exception;
-import 'package:cw_core/utils/print_verbose.dart';
-import 'package:ledger_ethereum/ledger_ethereum.dart';
-import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
-import 'package:web3dart/crypto.dart';
-import 'package:web3dart/web3dart.dart';
-
-class EvmLedgerCredentials extends CredentialsWithKnownAddress {
- final String _address;
-
- EthereumLedgerApp? ethereumLedgerApp;
-
- EvmLedgerCredentials(this._address);
-
- @override
- EthereumAddress get address => EthereumAddress.fromHex(_address);
-
- void setLedgerConnection(LedgerConnection connection,
- [String? derivationPath]) {
- ethereumLedgerApp = EthereumLedgerApp(connection,
- derivationPath: derivationPath ?? "m/44'/60'/0'/0/0");
- }
-
- @override
- MsgSignature signToEcSignature(Uint8List payload,
- {int? chainId, bool isEIP1559 = false}) =>
- throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
-
- @override
- Future<MsgSignature> signToSignature(Uint8List payload,
- {int? chainId, bool isEIP1559 = false}) async {
- if (ethereumLedgerApp == null) {
- throw exception.DeviceNotConnectedException();
- }
-
- final sig = await ethereumLedgerApp!.signTransaction(payload);
-
- final v = sig[0].toInt();
- final r = bytesToHex(sig.sublist(1, 1 + 32));
- final s = bytesToHex(sig.sublist(1 + 32, 1 + 32 + 32));
-
- var truncChainId = chainId ?? 1;
- while (truncChainId.bitLength > 32) {
- truncChainId >>= 8;
- }
-
- final truncTarget = truncChainId * 2 + 35;
-
- int parity = v;
- if (truncTarget & 0xff == v) {
- parity = 0;
- } else if ((truncTarget + 1) & 0xff == v) {
- parity = 1;
- }
-
- // https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L26
- int chainIdV;
- if (isEIP1559) {
- chainIdV = v;
- } else {
- chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity;
- }
-
- return MsgSignature(
- BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
- }
-
- @override
- Future<Uint8List> signPersonalMessage(Uint8List payload,
- {int? chainId}) async {
- if (isNotConnected) throw exception.DeviceNotConnectedException();
-
- final sig = await ethereumLedgerApp!.signMessage(payload);
-
- if (sig.isEmpty) throw Exception("No Signature received from device!");
-
- final r = sig.sublist(1, 1 + 32);
- final s = sig.sublist(1 + 32, 1 + 32 + 32);
- final v = [sig[0]];
-
- // https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L63
- return Uint8List.fromList(r + s + v);
- }
-
- @override
- Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
- throw UnimplementedError(
- "EvmLedgerCredentials.signPersonalMessageToUint8List");
-
- Future<void> provideERC20Info(
- String erc20ContractAddress, int chainId) async {
- if (isNotConnected) throw exception.DeviceNotConnectedException();
-
- try {
- await ethereumLedgerApp!.getAndProvideERC20TokenInformation(
- erc20ContractAddress: erc20ContractAddress, chainId: chainId);
- } catch (e) {
- printV(e);
- rethrow;
- // if (e.errorCode != -28672) rethrow;
- }
- }
-
- bool get isNotConnected => ethereumLedgerApp == null || ethereumLedgerApp!.connection.isDisconnected;
-}
diff --git a/cw_evm/lib/utils/rlp_decode.dart b/cw_evm/lib/utils/rlp_decode.dart
new file mode 100644
index 0000000..c6f0b90
--- /dev/null
+++ b/cw_evm/lib/utils/rlp_decode.dart
@@ -0,0 +1,101 @@
+import 'dart:typed_data';
+
+import 'package:web3dart/crypto.dart';
+
+/// RLP Decode
+///
+/// Adapted from https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/rlp
+
+class _Decoded {
+ Uint8List remainder;
+ List data;
+
+ _Decoded(this.data, this.remainder);
+}
+
+int _decodeLength(Uint8List v) {
+ if (v[0] == 0) throw Exception('invalid RLP: extra zeros');
+ return int.parse(bytesToHex(v), radix: 16);
+}
+
+_Decoded _decode(Uint8List input) {
+ final firstByte = input[0];
+
+ if (firstByte <= 0x7f) {
+ // a single byte whose value is in the [0x00, 0x7f] range, that byte is its own RLP encoding.
+ return _Decoded(input.sublist(0, 1), input.sublist(1));
+ } else if (firstByte <= 0xb7) {
+ // string is 0-55 bytes long. A single byte with value 0x80 plus the length of the string followed by the string
+ // The range of the first byte is [0x80, 0xb7]
+ final length = firstByte - 0x7f;
+
+ // set 0x80 null to 0
+ final data = firstByte == 0x80 ? Uint8List(0) : input.sublist(1, length);
+
+ if (length == 2 && data[0] < 0x80) {
+ throw Exception('invalid RLP encoding: invalid prefix, single byte < 0x80 are not prefixed');
+ }
+
+ return _Decoded(data, input.sublist(length));
+ } else if (firstByte <= 0xbf) {
+ // string is greater than 55 bytes long. A single byte with the value (0xb7 plus the length of the length),
+ // followed by the length, followed by the string
+ final lLength = firstByte - 0xb6;
+ if (input.length - 1 < lLength) {
+ throw Exception('invalid RLP: not enough bytes for string length');
+ }
+
+ final length = _decodeLength(input.sublist(1, lLength));
+ if (length <= 55) {
+ throw Exception('invalid RLP: expected string length to be greater than 55');
+ }
+
+ final data = input.sublist(lLength, length + lLength);
+ return _Decoded(data, input.sublist(length + lLength));
+ } else if (firstByte <= 0xf7) {
+ // a list between 0-55 bytes long
+ final length = firstByte - 0xbf;
+ var innerRemainder = input.sublist(1, length);
+
+ final decoded = [];
+ while (innerRemainder.isNotEmpty) {
+ final d = _decode(innerRemainder);
+ decoded.add(d.data);
+ innerRemainder = d.remainder;
+ }
+
+ return _Decoded(decoded, input.sublist(length));
+ } else {
+ // a list over 55 bytes long
+ final lLength = firstByte - 0xf6;
+
+ final length = _decodeLength(input.sublist(1, lLength));
+ if (length < 56) {
+ throw Exception('invalid RLP: encoded list too short');
+ }
+
+ final totalLength = lLength + length;
+ if (totalLength > input.length) {
+ throw Exception('invalid RLP: total length is larger than the data');
+ }
+
+ var innerRemainder = input.sublist(lLength, totalLength);
+ final decoded = [];
+ while (innerRemainder.isNotEmpty) {
+ final d = _decode(innerRemainder);
+ decoded.add(d.data);
+ innerRemainder = d.remainder;
+ }
+
+ return _Decoded(decoded, input.sublist(totalLength));
+ }
+}
+
+/// RLP Decoding based on https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/
+List decode(Uint8List input) {
+ final decoded = _decode(input);
+
+ if (decoded.remainder.isNotEmpty) throw Exception('invalid RLP: remainder must be zero');
+
+ return decoded.data;
+}
diff --git a/cw_evm/pubspec.yaml b/cw_evm/pubspec.yaml
index 60e214d..0785d78 100644
--- a/cw_evm/pubspec.yaml
+++ b/cw_evm/pubspec.yaml
@@ -33,6 +33,10 @@ dependencies:
ref: f4761cd5171d4c1e2e42fd3298261650539fb2db
bitbox_flutter:
path: ../scripts/bitbox_flutter
+ trezor_connect:
+ git:
+ url: https://github.com/cake-tech/trezor_connect
+ ref: 59b3ceec158d3393b20d0c596cab2abcc62ac8e4
dependency_overrides:
web3dart:
diff --git a/cw_evm/test/cw_evm_test.dart b/cw_evm/test/cw_evm_test.dart
index c0ffd3e..558a734 100644
--- a/cw_evm/test/cw_evm_test.dart
+++ b/cw_evm/test/cw_evm_test.dart
@@ -1,16 +1,45 @@
-import 'package:cw_evm/evm_chain_formatter.dart';
-import 'package:flutter_test/flutter_test.dart';
+import "dart:typed_data";
+
+import "package:cw_evm/evm_chain_formatter.dart";
+import "package:cw_evm/utils/rlp_decode.dart";
+import "package:flutter_test/flutter_test.dart";
+import "package:web3dart/crypto.dart";
+import "package:web3dart/src/utils/rlp.dart" as rlp;
void main() {
- group('EVMChainFormatter', () {
- group('truncateDecimals', () {
- test('no decimals', () => expect(EVMChainFormatter.truncateDecimals("5", 6), "5"));
- test('less than max decimals',
+ group("EVMChainFormatter", () {
+ group("truncateDecimals", () {
+ test("no decimals", () => expect(EVMChainFormatter.truncateDecimals("5", 6), "5"));
+ test("less than max decimals",
() => expect(EVMChainFormatter.truncateDecimals("5.00001", 6), "5.00001"));
- test('max decimals',
+ test("max decimals",
() => expect(EVMChainFormatter.truncateDecimals("5.000001", 6), "5.000001"));
- test('more than max decimals',
+ test("more than max decimals",
() => expect(EVMChainFormatter.truncateDecimals("5.0000001", 6), "5.000000"));
});
});
+ group("rlp decode", () {
+ test("0x05", () => expect(decode(hexToBytes("0x05")), [5]));
+ test(
+ "0xc88363617483646f67",
+ () => expect(
+ decode(hexToBytes("0xc88363617483646f67")),
+ [
+ [99, 97, 116],
+ [100, 111, 103]
+ ],
+ ),
+ );
+
+ test(
+ "cat dog",
+ () => expect(
+ decode(Uint8List.fromList(rlp.encode(["cat", "dog"]))),
+ [
+ [99, 97, 116],
+ [100, 111, 103]
+ ],
+ ),
+ );
+ });
}
diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart
index 953c88c..452b802 100644
--- a/lib/bitcoin/cw_bitcoin.dart
+++ b/lib/bitcoin/cw_bitcoin.dart
@@ -536,11 +536,6 @@ class CWBitcoin extends Bitcoin {
return (bitcoinWallet.feeRate(BitcoinTransactionPriority.fast) * 10).round();
}
- @override
- void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) {
- (wallet as ElectrumWallet).setLedgerConnection(connection);
- }
-
@override
void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) {
(wallet as ElectrumWallet).hardwareWalletService = service;
@@ -560,6 +555,13 @@ class CWBitcoin extends Bitcoin {
return LitecoinBitboxService(manager);
}
+ @override
+ HardwareWalletService getTrezorHardwareWalletService(
+ trezor.TrezorConnect connect, bool isBitcoin) {
+ if (isBitcoin) return BitcoinTrezorService(connect);
+ return LitecoinTrezorService(connect);
+ }
+
@override
List<ElectrumSubAddress> getSilentPaymentAddresses(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
diff --git a/lib/di.dart b/lib/di.dart
index 3587d17..01312dd 100644
--- a/lib/di.dart
+++ b/lib/di.dart
@@ -56,6 +56,7 @@ import 'package:cake_wallet/view_model/dev/secure_preferences.dart';
import 'package:cake_wallet/view_model/dev/shared_preferences.dart';
import 'package:cake_wallet/view_model/hardware_wallet/bitbox_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/trezor_view_model.dart';
import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/tron/tron.dart';
@@ -281,6 +282,7 @@ import 'package:get_it/get_it.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
+import 'package:trezor_connect/trezor_connect.dart';
import 'buy/kryptonim/kryptonim.dart';
import 'buy/meld/meld_buy_provider.dart';
import 'dogecoin/dogecoin.dart';
@@ -400,6 +402,7 @@ Future<void> setup({
switch(type) {
case HardwareWalletType.bitbox: return getIt<BitboxViewModel>();
case HardwareWalletType.ledger: return getIt<LedgerViewModel>();
+ case HardwareWalletType.trezor: return getIt<TrezorViewModel>();
case HardwareWalletType.cupcake:
case HardwareWalletType.coldcard:
case HardwareWalletType.seedsigner:
@@ -412,6 +415,10 @@ Future<void> setup({
getIt.registerLazySingleton(() => BitboxViewModel());
+ getIt.registerLazySingleton(() => TrezorConnect("cakewallet://trezor_connect",
+ appName: "Cake Wallet"));
+ getIt.registerLazySingleton(() => TrezorViewModel(getIt<TrezorConnect>()));
+
final secretStore = await SecretStoreBase.load(getIt.get<SecureStorage>());
getIt.registerSingleton<SecretStore>(secretStore);
diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart
index 04dadaa..840f350 100644
--- a/lib/ethereum/cw_ethereum.dart
+++ b/lib/ethereum/cw_ethereum.dart
@@ -188,6 +188,9 @@ class CWEthereum extends Ethereum {
} else if (service is EVMChainBitboxService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
.setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath);
+ } else if (service is EVMChainTrezorService) {
+ ((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials)
+ .setTrezorConnect(service.connect, wallet.walletInfo.derivationInfo?.derivationPath);
}
}
@override
@@ -198,6 +201,10 @@ class CWEthereum extends Ethereum {
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
EVMChainBitboxService(manager);
+ @override
+ HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect) =>
+ EVMChainTrezorService(connect);
+
@override
List<String> getDefaultTokenContractAddresses() {
return DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList();
diff --git a/lib/main.dart b/lib/main.dart
index 2433f26..ffc255b 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -62,6 +62,7 @@ import 'package:cw_core/window_size.dart';
import 'package:logging/logging.dart';
import 'package:cake_wallet/core/trade_monitor.dart';
import 'package:cake_wallet/core/reset_service.dart';
+import 'package:trezor_connect/trezor_connect.dart';
final navigatorKey = GlobalKey<NavigatorState>();
final rootKey = GlobalKey<RootState>();
@@ -375,6 +376,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
linkViewModel: linkViewModel,
tradeMonitor: tradeMonitor,
nodeSwitchingService: nodeSwitchingService,
+ trezorConnect: getIt<TrezorConnect>(),
child: ThemeProvider(
themeStore: appStore.themeStore,
materialAppBuilder: (context, theme, darkTheme, themeMode) => MaterialApp(
diff --git a/lib/polygon/cw_polygon.dart b/lib/polygon/cw_polygon.dart
index 0758af1..e04e81f 100644
--- a/lib/polygon/cw_polygon.dart
+++ b/lib/polygon/cw_polygon.dart
@@ -206,6 +206,9 @@ class CWPolygon extends Polygon {
} else if (service is EVMChainBitboxService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
.setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath);
+ } else if (service is EVMChainTrezorService) {
+ ((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials)
+ .setTrezorConnect(service.connect, wallet.walletInfo.derivationInfo?.derivationPath);
}
}
@@ -217,6 +220,10 @@ class CWPolygon extends Polygon {
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
EVMChainBitboxService(manager, chainId: 137);
+ @override
+ HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect) =>
+ EVMChainTrezorService(connect, chainId: 137);
+
@override
List<String> getDefaultTokenContractAddresses() =>
DefaultPolygonErc20Tokens().initialPolygonErc20Tokens.map((e) => e.contractAddress).toList();
diff --git a/lib/router.dart b/lib/router.dart
index 08ff50b..02fe827 100644
--- a/lib/router.dart
+++ b/lib/router.dart
@@ -345,6 +345,12 @@ Route<dynamic> createRoute(RouteSettings settings) {
(_) => getIt.get<NewWalletTypePage>(
param1: NewWalletTypeArguments(
onTypeSelected: (BuildContext context, WalletType type) {
+ if (hardwareWalletType == HardwareWalletType.trezor) {
+ Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount,
+ arguments: [type, hardwareWalletType]);
+ return;
+ }
+
final arguments = ConnectDevicePageParams(
walletType: type,
hardwareWalletType: hardwareWalletType,
diff --git a/lib/src/screens/connect_device/select_device_manufacturer_page.dart b/lib/src/screens/connect_device/select_device_manufacturer_page.dart
index 920e8e7..a629b6f 100644
--- a/lib/src/screens/connect_device/select_device_manufacturer_page.dart
+++ b/lib/src/screens/connect_device/select_device_manufacturer_page.dart
@@ -1,3 +1,5 @@
+import 'dart:io';
+
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
@@ -29,28 +31,62 @@ class SelectDeviceManufacturerPage extends BasePage {
@override
AppBarStyle get appBarStyle => AppBarStyle.regular;
+ ColorFilter get _colorFilter =>
+ ColorFilter.mode(currentTheme.colorScheme.onSurface, BlendMode.srcIn);
+
List<_DeviceManufacturer> get availableManufacturers => [
_DeviceManufacturer(
- image: SvgPicture.asset('assets/images/hardware_wallet/ledger_man.svg', height: 25),
+ image: SvgPicture.asset(
+ 'assets/images/hardware_wallet/ledger_man.svg',
+ height: 25,
+ colorFilter: _colorFilter,
+ ),
hardwareWalletType: HardwareWalletType.ledger,
),
- // _DeviceManufacturer(
- // image: SvgPicture.asset('assets/images/hardware_wallet/bitbox_man.svg', height: 25),
- // hardwareWalletType: HardwareWalletType.bitbox,
- // tag: S.current.new_tag,
- // ),
+ if (Platform.isAndroid) ...[
+ _DeviceManufacturer(
+ image: SvgPicture.asset(
+ 'assets/images/hardware_wallet/trezor_man.svg',
+ height: 25,
+ colorFilter: _colorFilter,
+ ),
+ hardwareWalletType: HardwareWalletType.trezor,
+ tag: S.current.new_tag,
+ ),
+ _DeviceManufacturer(
+ image: SvgPicture.asset(
+ 'assets/images/hardware_wallet/bitbox_man.svg',
+ height: 25,
+ colorFilter: _colorFilter,
+ ),
+ hardwareWalletType: HardwareWalletType.bitbox,
+ tag: S.current.new_tag,
+ ),
+ ],
_DeviceManufacturer(
- image: SvgPicture.asset('assets/images/hardware_wallet/cupcake_man.svg', height: 25),
+ image: SvgPicture.asset(
+ 'assets/images/hardware_wallet/cupcake_man.svg',
+ height: 25,
+ colorFilter: _colorFilter,
+ ),
hardwareWalletType: HardwareWalletType.cupcake,
tag: S.current.new_tag,
),
_DeviceManufacturer(
- image: SvgPicture.asset('assets/images/hardware_wallet/coldcard_man.svg', height: 25),
+ image: SvgPicture.asset(
+ 'assets/images/hardware_wallet/coldcard_man.svg',
+ height: 25,
+ colorFilter: _colorFilter,
+ ),
hardwareWalletType: HardwareWalletType.coldcard,
tag: S.current.new_tag,
),
// _DeviceManufacturer(
- // image: SvgPicture.asset('assets/images/hardware_wallet/seedsigner_man.svg', height: 25),
+ // image: SvgPicture.asset(
+ // 'assets/images/hardware_wallet/seedsigner_man.svg',
+ // height: 25,
+ // colorFilter: _colorFilter,
+ // ),
// hardwareWalletType: HardwareWalletType.seedsigner,
// tag: S.current.new_tag,
// ),
@@ -61,26 +97,40 @@ class SelectDeviceManufacturerPage extends BasePage {
.toList();
List<_DeviceManufacturer> get comingManufacturers => [
+ if (!Platform.isAndroid) ...[
+ _DeviceManufacturer(
+ image: SvgPicture.asset(
+ 'assets/images/hardware_wallet/bitbox_man.svg',
+ height: 25,
+ colorFilter: _colorFilter,
+ ),
+ hardwareWalletType: HardwareWalletType.bitbox,
+ tag: S.current.coming_soon_tag,
+ ),
+ ],
_DeviceManufacturer(
- image: SvgPicture.asset('assets/images/hardware_wallet/trezor_man.svg', height: 25),
- tag: S.current.coming_soon_tag,
- ),
- _DeviceManufacturer(
- image: SvgPicture.asset('assets/images/hardware_wallet/bitbox_man.svg', height: 25),
- hardwareWalletType: HardwareWalletType.bitbox,
- tag: S.current.coming_soon_tag,
- ),
- _DeviceManufacturer(
- image: SvgPicture.asset('assets/images/hardware_wallet/seedsigner_man.svg', height: 25),
+ image: SvgPicture.asset(
+ 'assets/images/hardware_wallet/seedsigner_man.svg',
+ height: 25,
+ colorFilter: _colorFilter,
+ ),
hardwareWalletType: HardwareWalletType.seedsigner,
tag: S.current.coming_soon_tag,
),
_DeviceManufacturer(
- image: SvgPicture.asset('assets/images/hardware_wallet/foundation_man.svg', height: 25),
+ image: SvgPicture.asset(
+ 'assets/images/hardware_wallet/foundation_man.svg',
+ height: 25,
+ colorFilter: _colorFilter,
+ ),
tag: S.current.coming_soon_tag,
),
_DeviceManufacturer(
- image: SvgPicture.asset('assets/images/hardware_wallet/keystone_man.svg', height: 25),
+ image: SvgPicture.asset(
+ 'assets/images/hardware_wallet/keystone_man.svg',
+ height: 25,
+ colorFilter: _colorFilter,
+ ),
tag: S.current.coming_soon_tag,
),
];
@@ -90,48 +140,52 @@ class SelectDeviceManufacturerPage extends BasePage {
child: Center(
child: Container(
padding: EdgeInsets.only(left: 24, right: 24),
- child: Column(
- children: [
- ...availableManufacturers.map(
- (manufacturer) => Padding(
- padding: EdgeInsets.only(top: 10),
- child: ManufacturerOptionTile(
- image: manufacturer.image,
- tag: manufacturer.tag,
- onPressed: () {
- if (onSelect != null)
- return onSelect!.call(context, manufacturer.hardwareWalletType!);
-
- if (isAirgappedWallet(manufacturer.hardwareWalletType)) {
- _onScanQRCode(context, manufacturer.hardwareWalletType!);
- } else if (manufacturer.hardwareWalletType != null) {
- Navigator.pushNamed(context, Routes.connectHardwareWallet,
- arguments: [manufacturer.hardwareWalletType]);
- }
- },
- ),
- ),
- ),
- if (showUnavailable) ...[
- Padding(
- padding: EdgeInsets.only(top: 20, bottom: 10),
- child: DottedDivider(color: Theme.of(context).colorScheme.surfaceContainer),
- ),
- ...comingManufacturers.map(
+ height: double.infinity,
+ child: SingleChildScrollView(
+ child: Column(
+ children: [
+ ...availableManufacturers.map(
(manufacturer) => Padding(
padding: EdgeInsets.only(top: 10),
child: ManufacturerOptionTile(
image: manufacturer.image,
tag: manufacturer.tag,
- onPressed: () =>
- Fluttertoast.showToast(msg: 'One more tap and it might work'),
- // Ester egg
- isUnavailable: true,
+ onPressed: () {
+ if (onSelect != null)
+ return onSelect!.call(context, manufacturer.hardwareWalletType!);
+
+ if (isAirgappedWallet(manufacturer.hardwareWalletType)) {
+ _onScanQRCode(context, manufacturer.hardwareWalletType!);
+ } else if (manufacturer.hardwareWalletType != null) {
+ Navigator.pushNamed(context, Routes.connectHardwareWallet,
+ arguments: [manufacturer.hardwareWalletType]);
+ }
+ },
),
),
),
- ]
- ],
+ if (showUnavailable) ...[
+ Padding(
+ padding: EdgeInsets.only(top: 20, bottom: 10),
+ child: DottedDivider(color: Theme.of(context).colorScheme.surfaceContainer),
+ ),
+ ...comingManufacturers.map(
+ (manufacturer) => Padding(
+ padding: EdgeInsets.only(top: 10),
+ child: ManufacturerOptionTile(
+ image: manufacturer.image,
+ tag: manufacturer.tag,
+ onPressed: () =>
+ Fluttertoast.showToast(msg: 'One more tap and it might work'),
+ // Ester egg
+
+ isUnavailable: true,
+ ),
+ ),
+ ),
+ ]
+ ],
+ ),
),
),
),
diff --git a/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart b/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
index 7ff65a7..a14aa6b 100644
--- a/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
+++ b/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
@@ -33,10 +33,12 @@ class CryptoBalanceWidget extends StatelessWidget {
switch (dashboardViewModel.wallet.hardwareWalletType) {
case null:
return null;
- case HardwareWalletType.ledger:
- return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
case HardwareWalletType.bitbox:
return "assets/images/hardware_wallet/device_bitbox.svg";
+ case HardwareWalletType.ledger:
+ return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
+ case HardwareWalletType.trezor:
+ return "assets/images/hardware_wallet/device_trezor_safe_5.svg";
case HardwareWalletType.cupcake:
return "assets/images/cupcake.svg";
case HardwareWalletType.coldcard:
diff --git a/lib/src/screens/root/root.dart b/lib/src/screens/root/root.dart
index f3da794..7f71dcb 100644
--- a/lib/src/screens/root/root.dart
+++ b/lib/src/screens/root/root.dart
@@ -1,26 +1,28 @@
import 'dart:async';
import 'dart:io';
+
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/node_switching_service.dart';
import 'package:cake_wallet/core/totp_request_details.dart';
import 'package:cake_wallet/core/trade_monitor.dart';
+import 'package:cake_wallet/entities/qr_scanner.dart';
+import 'package:cake_wallet/reactions/wallet_utils.dart';
+import 'package:cake_wallet/routes.dart';
+import 'package:cake_wallet/src/screens/auth/auth_page.dart';
+import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
+import 'package:cake_wallet/store/app_store.dart';
+import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cw_core/utils/print_verbose.dart';
+import 'package:cw_core/utils/socket_health_logger.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
-import 'package:cake_wallet/routes.dart';
-import 'package:cake_wallet/src/screens/auth/auth_page.dart';
-import 'package:cake_wallet/store/app_store.dart';
-import 'package:cake_wallet/store/authentication_store.dart';
-import 'package:cake_wallet/entities/qr_scanner.dart';
import 'package:mobx/mobx.dart';
+import 'package:trezor_connect/trezor_connect.dart';
import 'package:uni_links/uni_links.dart';
-import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
-import 'package:cake_wallet/reactions/wallet_utils.dart';
-import 'package:cw_core/utils/socket_health_logger.dart';
class Root extends StatefulWidget {
Root({
@@ -33,6 +35,7 @@ class Root extends StatefulWidget {
required this.linkViewModel,
required this.tradeMonitor,
required this.nodeSwitchingService,
+ required this.trezorConnect,
}) : super(key: key);
final AuthenticationStore authenticationStore;
@@ -43,6 +46,7 @@ class Root extends StatefulWidget {
final LinkViewModel linkViewModel;
final TradeMonitor tradeMonitor;
final NodeSwitchingService nodeSwitchingService;
+ final TrezorConnect trezorConnect;
@override
RootState createState() => RootState();
@@ -110,6 +114,11 @@ class RootState extends State<Root> with WidgetsBindingObserver {
widget.linkViewModel.currentLink = uri;
+ if (uri.toString().startsWith(widget.trezorConnect.callbackBackUri)) {
+ widget.trezorConnect.handleCallback(uri);
+ return;
+ }
+
bool requireAuth = await widget.authService.requireAuth();
if (!requireAuth && widget.authenticationStore.state == AuthenticationState.allowed) {
diff --git a/lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart b/lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart
index 4b17a71..62fcdd2 100644
--- a/lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart
+++ b/lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart
@@ -23,8 +23,7 @@ abstract class BaseBottomSheet extends StatelessWidget {
this.leftActionButtonKey,
this.rightActionButtonKey,
required this.maxHeight,
- }) : assert(footerType == FooterType.none,
- 'currentTheme is required unless footerType is none');
+ });
final String titleText;
final String? titleIconPath;
diff --git a/lib/view_model/hardware_wallet/bitbox_view_model.dart b/lib/view_model/hardware_wallet/bitbox_view_model.dart
index 20370b5..95d4541 100644
--- a/lib/view_model/hardware_wallet/bitbox_view_model.dart
+++ b/lib/view_model/hardware_wallet/bitbox_view_model.dart
@@ -11,6 +11,7 @@ import 'package:cw_core/hardware/hardware_wallet_service.dart';
import 'package:cw_core/root_dir.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
+import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:bitbox_flutter/bitbox_flutter.dart' as sdk;
import 'package:mobx/mobx.dart';
@@ -28,6 +29,9 @@ abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
}
}
+ @override
+ HardwareWalletType get hardwareWalletType => HardwareWalletType.bitbox;
+
@override
@observable
bool isBleEnabled = false;
diff --git a/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart b/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart
index 825090e..8c36915 100644
--- a/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart
+++ b/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart
@@ -1,9 +1,11 @@
import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
import 'package:cw_core/hardware/hardware_wallet_service.dart';
import 'package:cw_core/wallet_base.dart';
+import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
abstract class HardwareWalletViewModel {
+ HardwareWalletType get hardwareWalletType;
bool get isConnected;
bool get isBleEnabled;
bool get hasBluetooth;
diff --git a/lib/view_model/hardware_wallet/ledger_view_model.dart b/lib/view_model/hardware_wallet/ledger_view_model.dart
index 7fa0134..2ac7644 100644
--- a/lib/view_model/hardware_wallet/ledger_view_model.dart
+++ b/lib/view_model/hardware_wallet/ledger_view_model.dart
@@ -54,6 +54,9 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
}
}
+ @override
+ HardwareWalletType get hardwareWalletType => HardwareWalletType.ledger;
+
@override
@observable
bool isBleEnabled = false;
diff --git a/lib/view_model/hardware_wallet/trezor_view_model.dart b/lib/view_model/hardware_wallet/trezor_view_model.dart
new file mode 100644
index 0000000..3aaf1a1
--- /dev/null
+++ b/lib/view_model/hardware_wallet/trezor_view_model.dart
@@ -0,0 +1,82 @@
+import 'dart:async';
+
+import 'package:cake_wallet/bitcoin/bitcoin.dart';
+import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
+import 'package:cake_wallet/ethereum/ethereum.dart';
+import 'package:cake_wallet/polygon/polygon.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
+import 'package:cw_core/wallet_base.dart';
+import 'package:cw_core/wallet_info.dart';
+import 'package:cw_core/wallet_type.dart';
+import 'package:trezor_connect/trezor_connect.dart' as sdk;
+import 'package:mobx/mobx.dart';
+
+part 'trezor_view_model.g.dart';
+
+class TrezorViewModel = TrezorViewModelBase with _$TrezorViewModel;
+
+abstract class TrezorViewModelBase extends HardwareWalletViewModel with Store {
+ final sdk.TrezorConnect trezorConnect;
+
+ TrezorViewModelBase(this.trezorConnect);
+
+ @override
+ HardwareWalletType get hardwareWalletType => HardwareWalletType.trezor;
+
+ @override
+ @observable
+ bool isBleEnabled = false;
+
+ @override
+ bool get hasBluetooth => false;
+
+ @override
+ Future<void> updateBleState() async {}
+
+ @override
+ Stream<HardwareWalletDevice> scanForBleDevices() => throw UnimplementedError();
+
+ @override
+ Future<List<HardwareWalletDevice>> getAllUsbDevices() async => [];
+
+ @override
+ Future<void> stopScanning() async {}
+
+ @override
+ Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async => true;
+
+ @override
+ bool get isConnected => true;
+
+ @override
+ HardwareWalletService getHardwareWalletService(WalletType type) {
+ switch (type) {
+ case WalletType.bitcoin:
+ return bitcoin!.getTrezorHardwareWalletService(trezorConnect, true);
+ case WalletType.litecoin:
+ return bitcoin!.getTrezorHardwareWalletService(trezorConnect, false);
+ case WalletType.ethereum:
+ return ethereum!.getTrezorHardwareWalletService(trezorConnect);
+ case WalletType.polygon:
+ return polygon!.getTrezorHardwareWalletService(trezorConnect);
+ default:
+ throw UnimplementedError();
+ }
+ }
+
+ @override
+ void initWallet(WalletBase wallet) {
+ switch (wallet.type) {
+ case WalletType.bitcoin:
+ case WalletType.litecoin:
+ return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
+ case WalletType.ethereum:
+ return ethereum!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
+ case WalletType.polygon:
+ return polygon!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
+ default:
+ throw Exception('Unexpected wallet type: ${wallet.type}');
+ }
+ }
+}
diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart
index 5e4bdae..2a33f04 100644
--- a/lib/view_model/send/send_view_model.dart
+++ b/lib/view_model/send/send_view_model.dart
@@ -37,7 +37,6 @@ import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
-import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cake_wallet/view_model/send/fees_view_model.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
@@ -942,7 +941,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
);
if (token != null) {
- selectedCryptoCurrency = token as CryptoCurrency;
+ selectedCryptoCurrency = token;
}
}
}
diff --git a/lib/view_model/wallet_hardware_restore_view_model.dart b/lib/view_model/wallet_hardware_restore_view_model.dart
index 8e2e2f7..9c56ce0 100644
--- a/lib/view_model/wallet_hardware_restore_view_model.dart
+++ b/lib/view_model/wallet_hardware_restore_view_model.dart
@@ -102,10 +102,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
throw Exception('Unexpected type: ${type.toString()}');
}
- if (hardwareWalletVM is LedgerViewModel)
- credentials.hardwareWalletType = HardwareWalletType.ledger;
- else
- credentials.hardwareWalletType = HardwareWalletType.bitbox;
+ credentials.hardwareWalletType = hardwareWalletVM.hardwareWalletType;
return credentials;
}
diff --git a/pubspec_base.yaml b/pubspec_base.yaml
index eef7039..c5b8480 100644
--- a/pubspec_base.yaml
+++ b/pubspec_base.yaml
@@ -4,7 +4,7 @@ dependencies:
flutter_localizations:
sdk: flutter
intl: any
- url_launcher: 6.3.1
+ url_launcher: 6.3.2
qr_flutter:
git:
url: https://github.com/cake-tech/qr.flutter.git
@@ -116,6 +116,10 @@ dependencies:
git:
url: https://github.com/vespr-wallet/ledger-flutter-plus
ref: 60817d4b20144f9da9029f5034790272795b9d38
+ trezor_connect:
+ git:
+ url: https://github.com/cake-tech/trezor_connect
+ ref: 59b3ceec158d3393b20d0c596cab2abcc62ac8e4
bitbox_flutter:
path: ./scripts/bitbox_flutter
hashlib: 1.19.2
diff --git a/tool/configure.dart b/tool/configure.dart
index 347eb79..f003ba1 100644
--- a/tool/configure.dart
+++ b/tool/configure.dart
@@ -112,6 +112,7 @@ import 'package:cw_core/get_height_by_date.dart';
import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
+import 'package:trezor_connect/trezor_connect.dart' as trezor;
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:bip39/bip39.dart' as bip39;
""";
@@ -138,6 +139,7 @@ import 'package:cw_bitcoin/litecoin_wallet.dart';
import 'package:cw_bitcoin/hardware/bitcoin_ledger_service.dart';
import 'package:cw_bitcoin/hardware/litecoin_ledger_service.dart';
import 'package:cw_bitcoin/hardware/bitbox_service.dart';
+import 'package:cw_bitcoin/hardware/trezor_service.dart';
import 'package:mobx/mobx.dart';
""";
const bitcoinCwPart = "part 'cw_bitcoin.dart';";
@@ -245,10 +247,10 @@ abstract class Bitcoin {
void deleteSilentPaymentAddress(Object wallet, String address);
Future<void> updateFeeRates(Object wallet);
int getMaxCustomFeeRate(Object wallet);
- void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection);
void setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection, bool isBitcoin);
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager, bool isBitcoin);
+ HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect, bool isBitcoin);
List<Output> updateOutputs(PendingTransaction pendingTransaction, List<Output> outputs);
bool txIsReceivedSilentPayment(TransactionInfo txInfo);
bool txIsMweb(TransactionInfo txInfo);
@@ -697,6 +699,7 @@ import 'package:cw_core/utils/print_verbose.dart';
import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
+import 'package:trezor_connect/trezor_connect.dart' as trezor;
import 'package:web3dart/web3dart.dart';
""";
@@ -707,11 +710,13 @@ import 'package:cw_evm/evm_chain_transaction_credentials.dart';
import 'package:cw_evm/evm_chain_transaction_info.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
-import 'package:cw_evm/hardware/evm_ledger_credentials.dart';
-import 'package:cw_evm/hardware/evm_bitbox_credentials.dart';
+import 'package:cw_evm/hardware/evm_chain_bitbox_credentials.dart';
+import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart';
+import 'package:cw_evm/hardware/evm_chain_trezor_credentials.dart';
import 'package:cw_evm/evm_chain_wallet.dart';
import 'package:cw_evm/hardware/evm_chain_bitbox_service.dart';
import 'package:cw_evm/hardware/evm_chain_ledger_service.dart';
+import 'package:cw_evm/hardware/evm_chain_trezor_service.dart';
import 'package:cw_ethereum/ethereum_client.dart';
import 'package:cw_ethereum/ethereum_wallet.dart';
@@ -780,6 +785,7 @@ abstract class Ethereum {
void setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager);
+ HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect);
List<String> getDefaultTokenContractAddresses();
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
}
@@ -822,6 +828,7 @@ import 'package:cw_core/utils/print_verbose.dart';
import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
+import 'package:trezor_connect/trezor_connect.dart' as trezor;
import 'package:web3dart/web3dart.dart';
""";
@@ -832,11 +839,13 @@ import 'package:cw_evm/evm_chain_transaction_credentials.dart';
import 'package:cw_evm/evm_chain_transaction_info.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
-import 'package:cw_evm/hardware/evm_ledger_credentials.dart';
-import 'package:cw_evm/hardware/evm_bitbox_credentials.dart';
+import 'package:cw_evm/hardware/evm_chain_bitbox_credentials.dart';
+import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart';
+import 'package:cw_evm/hardware/evm_chain_trezor_credentials.dart';
import 'package:cw_evm/evm_chain_wallet.dart';
import 'package:cw_evm/hardware/evm_chain_bitbox_service.dart';
import 'package:cw_evm/hardware/evm_chain_ledger_service.dart';
+import 'package:cw_evm/hardware/evm_chain_trezor_service.dart';
import 'package:cw_polygon/polygon_client.dart';
import 'package:cw_polygon/polygon_wallet.dart';
@@ -895,6 +904,7 @@ abstract class Polygon {
void setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager);
+ HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect);
List<String> getDefaultTokenContractAddresses();
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
}
Why this scored 27/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.