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 000000000..14c0329be
--- /dev/null
+++ b/assets/images/hardware_wallet/device_trezor_safe_5.svg
@@ -0,0 +1,3 @@
+
diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 38b765941..cf948459c 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 buildHardwareWalletTransaction({
required List outputs,
required BigInt fee,
diff --git a/cw_bitcoin/lib/hardware/bitbox_service.dart b/cw_bitcoin/lib/hardware/bitbox_service.dart
index 99bbf763b..3f47c5c62 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 = [];
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 signTransaction({required String transaction}) async {
final psbt = PsbtV2()..deserialize(base64Decode(transaction));
@@ -56,27 +60,31 @@ class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWal
}
@override
- Future signMessage({required Uint8List message, String? derivationPath}) async {
- throw UnimplementedError(); // ToDo (Konsti)
- }
+ Future signMessage({required Uint8List message, String? derivationPath}) =>
+ manager.signBTCMessage(bitboxCoinType, derivationPath ?? "m/84'/0'/0'/0/0", message);
@override
Future 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> getAvailableAccounts({int index = 0, int limit = 5}) async {
final accounts = [];
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 signMessage({required Uint8List message, String? derivationPath}) =>
+ manager.signBTCMessage(bitboxCoinType, derivationPath ?? "m/84'/0'/0'/0/0", message);
+
+ @override
+ Future signLitecoinTransaction({
+ required List outputs,
+ required List inputs,
+ required Map 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 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 cc4e2092d..885868d3c 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 getMasterFingerprint();
+ Future getMasterFingerprint() async => Uint8List.fromList([0, 0, 0, 0]);
+}
+
+mixin LitecoinHardwareWalletService on BitcoinHardwareWalletService {
+ Future signLitecoinTransaction({
+ required List outputs,
+ required List inputs,
+ required Map publicKeys,
+ });
}
diff --git a/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart b/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart
index 8334eb879..9ede5714e 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> getAvailableAccounts({int index = 0, int limit = 5}) async {
- final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
-
await litecoinLedgerApp.getVersion();
final accounts = [];
@@ -42,4 +46,43 @@ class LitecoinLedgerService extends HardwareWalletService {
return accounts;
}
+
+ @override
+ Future signLitecoinTransaction({
+ required List outputs,
+ required List inputs,
+ required Map publicKeys,
+ }) {
+
+ final readyInputs = [];
+ 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 000000000..8c5247a68
--- /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> getAvailableAccounts({int index = 0, int limit = 5}) async {
+ final indexRange = List.generate(limit, (i) => i + index);
+ final requestParams = [];
+
+ 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 signTransaction({required String transaction}) async {
+ final psbt = PsbtV2()..deserialize(base64Decode(transaction));
+
+ final inputs = [];
+ 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 = [];
+ 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 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> getAvailableAccounts({int index = 0, int limit = 5}) async {
+ final indexRange = List.generate(limit, (i) => i + index);
+ final requestParams = [];
+ 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 signLitecoinTransaction({
+ required List outputs,
+ required List inputs,
+ required Map 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 7b32547b8..eef24040c 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_transaction_info.dart';
-import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
-import 'package:cw_bitcoin/utils.dart';
+import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_derivations.dart';
-import 'package:cw_core/encryption_file_utils.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_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 mwebUtxosBox;
Timer? _syncTimer;
@@ -160,6 +161,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
bool get hasRescan => true;
List get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
+
List get spendSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
static Future 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 buildHardwareWalletTransaction({
required List outputs,
@@ -1391,38 +1385,26 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
}) async {
- final readyInputs = [];
+ final masterFingerprint =
+ await (hardwareWalletService as BitcoinHardwareWalletService).getMasterFingerprint();
+
+ final readyInputs = [];
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 6f0580bac..8cc60e9d0 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 5bb0b17de..d8d360ebf 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 92c809637..fa04bd10c 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 15a9438c0..4f849ec02 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 215e8bc28..29a88dcce 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 db35030c9..35420a693 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_chain_bitbox_credentials.dart
similarity index 100%
rename from cw_evm/lib/hardware/evm_bitbox_credentials.dart
rename to cw_evm/lib/hardware/evm_chain_bitbox_credentials.dart
diff --git a/cw_evm/lib/hardware/evm_ledger_credentials.dart b/cw_evm/lib/hardware/evm_chain_ledger_credentials.dart
similarity index 100%
rename from cw_evm/lib/hardware/evm_ledger_credentials.dart
rename to cw_evm/lib/hardware/evm_chain_ledger_credentials.dart
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 000000000..725cff249
--- /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 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 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 000000000..051660728
--- /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> getAvailableAccounts({int index = 0, int limit = 5}) async {
+ final indexRange = List.generate(limit, (i) => i + index);
+
+ final requestParams = [];
+ 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/utils/rlp_decode.dart b/cw_evm/lib/utils/rlp_decode.dart
new file mode 100644
index 000000000..c6f0b904d
--- /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 60e214da9..0785d78f3 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 c0ffd3e16..558a7349c 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 953c88c27..452b802c0 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 getSilentPaymentAddresses(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
diff --git a/lib/di.dart b/lib/di.dart
index 3587d1712..01312dd6f 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 setup({
switch(type) {
case HardwareWalletType.bitbox: return getIt();
case HardwareWalletType.ledger: return getIt();
+ case HardwareWalletType.trezor: return getIt();
case HardwareWalletType.cupcake:
case HardwareWalletType.coldcard:
case HardwareWalletType.seedsigner:
@@ -412,6 +415,10 @@ Future setup({
getIt.registerLazySingleton(() => BitboxViewModel());
+ getIt.registerLazySingleton(() => TrezorConnect("cakewallet://trezor_connect",
+ appName: "Cake Wallet"));
+ getIt.registerLazySingleton(() => TrezorViewModel(getIt()));
+
final secretStore = await SecretStoreBase.load(getIt.get());
getIt.registerSingleton(secretStore);
diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart
index 04dadaaed..840f3500e 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 getDefaultTokenContractAddresses() {
return DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList();
diff --git a/lib/main.dart b/lib/main.dart
index 2433f269f..ffc255b4f 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();
final rootKey = GlobalKey();
@@ -375,6 +376,7 @@ class AppState extends State with SingleTickerProviderStateMixin {
linkViewModel: linkViewModel,
tradeMonitor: tradeMonitor,
nodeSwitchingService: nodeSwitchingService,
+ trezorConnect: getIt(),
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 0758af151..e04e81f42 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 getDefaultTokenContractAddresses() =>
DefaultPolygonErc20Tokens().initialPolygonErc20Tokens.map((e) => e.contractAddress).toList();
diff --git a/lib/router.dart b/lib/router.dart
index 08ff50b72..02fe8275d 100644
--- a/lib/router.dart
+++ b/lib/router.dart
@@ -345,6 +345,12 @@ Route createRoute(RouteSettings settings) {
(_) => getIt.get(
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 920e8e77b..a629b6f8f 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 7ff65a76f..a14aa6bc9 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 f3da7945a..7f71dcb46 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 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 4b17a71d7..62fcdd22b 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 20370b58b..95d45418a 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 825090e02..8c369150f 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 7fa01340e..2ac764468 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 000000000..3aaf1a190
--- /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 updateBleState() async {}
+
+ @override
+ Stream scanForBleDevices() => throw UnimplementedError();
+
+ @override
+ Future> getAllUsbDevices() async => [];
+
+ @override
+ Future stopScanning() async {}
+
+ @override
+ Future 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 5e4bdae95..2a33f0414 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 8e2e2f7bd..9c56ce08a 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 eef7039e6..c5b8480bc 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 347eb79a2..f003ba19d 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 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