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 updateOutputs(PendingTransaction pendingTransaction, List 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 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 getDefaultTokenContractAddresses(); bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress); }