mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
LCW-1128-Add-support-for-Trezor (#2565)
* feat: add support for Trezor hardware wallet integration Restore Wallets * feat: add Trezor hardware wallet support, implement RLP decoding utility, and update transaction signing logic * feat: add bitcoin trezor signing * chore: update Trezor Connect dependency to specific commit hash in pubspec_base.yaml * feat: add trezor support for litecoin * feat: add colorFilter for device manufacturer icons to align with theme styles * refactor: optimize Trezor callback handling * feat: add BitBox and Trezor support for Bitcoin and Litecoin, update transaction signing logic, and improve modularity in hardware wallet services * refactor: simplify constructor assertions in `BaseBottomSheetWidget` and cleanup Trezor chainId handling logic * feat: conditionally render device manufacturers based on platform and hide SeedSigner for now
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32">
|
||||
<path fill="#000" fill-rule="evenodd" d="M7 6a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4v12.191a9 9 0 0 1-1.204 4.498l-3.353 5.81A3 3 0 0 1 17.845 30h-3.69a3 3 0 0 1-2.598-1.5l-3.353-5.811A9 9 0 0 1 7 18.19zm4-2a2 2 0 0 0-2 2v12.191a7 7 0 0 0 .937 3.498l3.352 5.81a1 1 0 0 0 .866.501h3.69a1 1 0 0 0 .866-.5l3.352-5.81a7 7 0 0 0 .937-3.5V6a2 2 0 0 0-2-2z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 447 B |
@@ -47,7 +47,6 @@ import 'package:cw_core/unspent_coin_type.dart';
|
||||
import 'package:cw_core/output_info.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:rxdart/subjects.dart';
|
||||
import 'package:sp_scanner/sp_scanner.dart';
|
||||
@@ -163,7 +162,7 @@ abstract class ElectrumWalletBase
|
||||
[HardwareWalletType? hardwareWalletType]) {
|
||||
switch (network) {
|
||||
case LitecoinNetwork.mainnet:
|
||||
if (hardwareWalletType == HardwareWalletType.ledger)
|
||||
if ([HardwareWalletType.ledger, HardwareWalletType.trezor].contains(hardwareWalletType))
|
||||
return Bip44Conf.litecoinMainNet.altKeyNetVer;
|
||||
return null;
|
||||
default:
|
||||
@@ -1423,8 +1422,6 @@ abstract class ElectrumWalletBase
|
||||
|
||||
HardwareWalletService? hardwareWalletService;
|
||||
|
||||
void setLedgerConnection(ledger.LedgerConnection connection) => throw UnimplementedError();
|
||||
|
||||
Future<BtcTransaction> buildHardwareWalletTransaction({
|
||||
required List<BitcoinBaseOutput> outputs,
|
||||
required BigInt fee,
|
||||
|
||||
@@ -6,7 +6,9 @@ import 'dart:typed_data';
|
||||
import 'package:bitbox_flutter/bitbox_manager.dart';
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
|
||||
import 'package:cw_bitcoin/psbt/transaction_builder.dart';
|
||||
import 'package:cw_bitcoin/psbt/v0_deserialize.dart';
|
||||
import 'package:cw_bitcoin/psbt/v0_finalizer.dart';
|
||||
import 'package:cw_bitcoin/utils.dart';
|
||||
@@ -17,7 +19,8 @@ import 'package:ledger_bitcoin/psbt.dart';
|
||||
class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWalletService {
|
||||
BitcoinBitboxService(this.manager);
|
||||
|
||||
final int bitboxCoinType = 0; // https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L40
|
||||
// https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L40
|
||||
static const int bitboxCoinType = 0;
|
||||
final BitboxManager manager;
|
||||
|
||||
@override
|
||||
@@ -25,9 +28,11 @@ class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWal
|
||||
final accounts = <HardwareAccountData>[];
|
||||
final indexRange = List.generate(limit, (i) => i + index);
|
||||
|
||||
// https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L252
|
||||
final keyType = 1; // xPUB
|
||||
for (final i in indexRange) {
|
||||
final derivationPath = "m/84'/0'/$i'";
|
||||
final xPub = await manager.getBTCXPub(bitboxCoinType, derivationPath, 1);
|
||||
final xPub = await manager.getBTCXPub(bitboxCoinType, derivationPath, keyType);
|
||||
final hd = Bip32Slip10Secp256k1.fromExtendedKey(xPub).childKey(Bip32KeyIndex(0));
|
||||
final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
|
||||
|
||||
@@ -42,7 +47,6 @@ class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWal
|
||||
return accounts;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future<Uint8List> signTransaction({required String transaction}) async {
|
||||
final psbt = PsbtV2()..deserialize(base64Decode(transaction));
|
||||
@@ -56,27 +60,31 @@ class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWal
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) async {
|
||||
throw UnimplementedError(); // ToDo (Konsti)
|
||||
}
|
||||
Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) =>
|
||||
manager.signBTCMessage(bitboxCoinType, derivationPath ?? "m/84'/0'/0'/0/0", message);
|
||||
|
||||
@override
|
||||
Future<Uint8List> getMasterFingerprint() => manager.getMasterFingerprint();
|
||||
}
|
||||
|
||||
class LitecoinBitboxService extends HardwareWalletService {
|
||||
class LitecoinBitboxService extends HardwareWalletService with BitcoinHardwareWalletService, LitecoinHardwareWalletService {
|
||||
LitecoinBitboxService(this.manager);
|
||||
|
||||
final BitboxManager manager;
|
||||
|
||||
// https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L40
|
||||
static const int bitboxCoinType = 2;
|
||||
|
||||
@override
|
||||
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
|
||||
final accounts = <HardwareAccountData>[];
|
||||
final indexRange = List.generate(limit, (i) => i + index);
|
||||
|
||||
// https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L252
|
||||
final keyType = 1; // xPUB
|
||||
for (final i in indexRange) {
|
||||
final derivationPath = "m/84'/2'/$i'";
|
||||
final xPub = await manager.getBTCXPub(0, derivationPath, 1);
|
||||
final xPub = await manager.getBTCXPub(bitboxCoinType, derivationPath, keyType);
|
||||
final hd = Bip32Slip10Secp256k1.fromExtendedKey(xPub).childKey(Bip32KeyIndex(0));
|
||||
final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet);
|
||||
|
||||
@@ -90,4 +98,27 @@ class LitecoinBitboxService extends HardwareWalletService {
|
||||
|
||||
return accounts;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) =>
|
||||
manager.signBTCMessage(bitboxCoinType, derivationPath ?? "m/84'/0'/0'/0/0", message);
|
||||
|
||||
@override
|
||||
Future<String> signLitecoinTransaction({
|
||||
required List<BitcoinBaseOutput> outputs,
|
||||
required List<PSBTReadyUtxoWithAddress> inputs,
|
||||
required Map<String, PublicKeyWithDerivationPath> publicKeys,
|
||||
}) async {
|
||||
final psbt = PSBTTransactionBuild(inputs: inputs, outputs: outputs, cwOutputs: []).psbt;
|
||||
|
||||
final signedPsbt = await manager.signBTCPsbt(bitboxCoinType, base64Encode(psbt.asPsbtV0()));
|
||||
final transactionRes = PsbtV2()
|
||||
..deserializeV0(base64Decode(signedPsbt))
|
||||
..finalizeV0();
|
||||
|
||||
return hex.encode(transactionRes.extract());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> getMasterFingerprint() => manager.getMasterFingerprint();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:cw_bitcoin/psbt/transaction_builder.dart';
|
||||
|
||||
mixin BitcoinHardwareWalletService {
|
||||
Future<Uint8List> getMasterFingerprint();
|
||||
Future<Uint8List> getMasterFingerprint() async => Uint8List.fromList([0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
mixin LitecoinHardwareWalletService on BitcoinHardwareWalletService {
|
||||
Future<String> signLitecoinTransaction({
|
||||
required List<BitcoinBaseOutput> outputs,
|
||||
required List<PSBTReadyUtxoWithAddress> inputs,
|
||||
required Map<String, PublicKeyWithDerivationPath> publicKeys,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
|
||||
import 'package:cw_bitcoin/psbt/transaction_builder.dart';
|
||||
import 'package:cw_bitcoin/utils.dart';
|
||||
import 'package:cw_core/hardware/hardware_account_data.dart';
|
||||
import 'package:cw_core/hardware/hardware_wallet_service.dart';
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
||||
import 'package:ledger_litecoin/ledger_litecoin.dart';
|
||||
|
||||
class LitecoinLedgerService extends HardwareWalletService {
|
||||
LitecoinLedgerService(this.ledgerConnection);
|
||||
class LitecoinLedgerService extends HardwareWalletService with BitcoinHardwareWalletService, LitecoinHardwareWalletService {
|
||||
LitecoinLedgerService(this.ledgerConnection)
|
||||
: litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
|
||||
|
||||
final LedgerConnection ledgerConnection;
|
||||
final LitecoinLedgerApp litecoinLedgerApp;
|
||||
|
||||
@override
|
||||
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
|
||||
final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
|
||||
|
||||
await litecoinLedgerApp.getVersion();
|
||||
|
||||
final accounts = <HardwareAccountData>[];
|
||||
@@ -42,4 +46,43 @@ class LitecoinLedgerService extends HardwareWalletService {
|
||||
|
||||
return accounts;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> signLitecoinTransaction({
|
||||
required List<BitcoinBaseOutput> outputs,
|
||||
required List<PSBTReadyUtxoWithAddress> inputs,
|
||||
required Map<String, PublicKeyWithDerivationPath> publicKeys,
|
||||
}) {
|
||||
|
||||
final readyInputs = <LedgerTransaction>[];
|
||||
for (final utxo in inputs) {
|
||||
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
|
||||
|
||||
readyInputs.add(LedgerTransaction(
|
||||
rawTx: utxo.rawTx,
|
||||
outputIndex: utxo.utxo.vout,
|
||||
ownerPublicKey: Uint8List.fromList(hex.decode(publicKeyAndDerivationPath.publicKey)),
|
||||
ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
|
||||
));
|
||||
}
|
||||
|
||||
// Check if we have the key to one of the output addresses to hide change on the device
|
||||
String? changePath;
|
||||
for (final output in outputs) {
|
||||
final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()];
|
||||
if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath;
|
||||
}
|
||||
|
||||
return litecoinLedgerApp.createTransaction(
|
||||
inputs: readyInputs,
|
||||
outputs: outputs
|
||||
.map((e) => TransactionOutput.fromBigInt((e as BitcoinOutput).value,
|
||||
Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
|
||||
.toList(),
|
||||
changePath: changePath,
|
||||
sigHashType: 0x01,
|
||||
additionals: ["bech32"],
|
||||
isSegWit: true,
|
||||
useTrustedInputForSegwit: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
|
||||
import 'package:cw_bitcoin/psbt/transaction_builder.dart';
|
||||
import 'package:cw_bitcoin/utils.dart';
|
||||
import 'package:cw_core/hardware/hardware_account_data.dart';
|
||||
import 'package:cw_core/hardware/hardware_wallet_service.dart';
|
||||
import 'package:ledger_bitcoin/psbt.dart';
|
||||
import 'package:trezor_connect/trezor_connect.dart';
|
||||
|
||||
class BitcoinTrezorService extends HardwareWalletService with BitcoinHardwareWalletService {
|
||||
BitcoinTrezorService(this.connect);
|
||||
|
||||
final TrezorConnect connect;
|
||||
|
||||
@override
|
||||
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
|
||||
final indexRange = List.generate(limit, (i) => i + index);
|
||||
final requestParams = <TrezorGetPublicKeyParams>[];
|
||||
|
||||
for (final i in indexRange) {
|
||||
requestParams.add(TrezorGetPublicKeyParams(path: "m/84'/0'/$i'"));
|
||||
}
|
||||
|
||||
final accounts = await connect.getPublicKeyBundle(requestParams);
|
||||
|
||||
return accounts?.map((account) {
|
||||
final hd = Bip32Slip10Secp256k1.fromExtendedKey(account.xpub).childKey(Bip32KeyIndex(0));
|
||||
final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
|
||||
return HardwareAccountData(
|
||||
address: address,
|
||||
xpub: account.xpub,
|
||||
accountIndex: account.path[2] - 0x80000000, // unharden the path to get the index
|
||||
derivationPath: account.serializedPath,
|
||||
);
|
||||
}).toList() ??
|
||||
[];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> signTransaction({required String transaction}) async {
|
||||
final psbt = PsbtV2()..deserialize(base64Decode(transaction));
|
||||
|
||||
final inputs = <TrezorTxInput>[];
|
||||
final inputCount = psbt.getGlobalInputCount();
|
||||
for (var i = 0; i < inputCount; i++) {
|
||||
final inputTxRaw = psbt.getInputNonWitnessUtxo(i);
|
||||
final inputTx = BtcTransaction.fromRaw(hex.encode(inputTxRaw!));
|
||||
final inputOutputIndex = psbt.getInputOutputIndex(i);
|
||||
|
||||
final publicKeys = psbt.inputMaps[i].keys.where((e) => e.startsWith("06"));
|
||||
final pubkey = Uint8List.fromList(hex.decode(publicKeys.first.substring(2)));
|
||||
|
||||
inputs.add(TrezorTxInput(
|
||||
prevHash: hex.encode(psbt.getInputPreviousTxid(i).reversed.toList()),
|
||||
prevIndex: inputOutputIndex,
|
||||
amount: inputTx.outputs[inputOutputIndex].amount.toInt(),
|
||||
addressPath: psbt.getInputBip32Derivation(i, pubkey)!.$2,
|
||||
sequence: psbt.getInputSequence(i),
|
||||
scriptType: "SPENDWITNESS"));
|
||||
}
|
||||
|
||||
final outputs = <TrezorTxOutput>[];
|
||||
final outputCount = psbt.getGlobalOutputCount();
|
||||
for (var i = 0; i < outputCount; i++) {
|
||||
final script = Script.fromRaw(byteData: psbt.getOutputScript(i));
|
||||
outputs.add(TrezorTxOutput(
|
||||
amount: psbt.getOutputAmount(i),
|
||||
address: script.toAddress(),
|
||||
scriptType: _getScriptType(script.getAddressType()!)
|
||||
// ToDo: addressPath: psbt.getOutputBip32Derivation(i, pubkey).$2, // To highlight change outputs
|
||||
));
|
||||
}
|
||||
|
||||
final signedTx = await connect.signTransaction(coin: 'btc', inputs: inputs, outputs: outputs);
|
||||
|
||||
return Uint8List.fromList(BytesUtils.fromHexString(signedTx!.serializedTx));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) async {
|
||||
final sig = await connect.signMessage(derivationPath ?? "m/84'/0'/0'/0/0",
|
||||
message: hex.encode(message), hex: true);
|
||||
return base64Decode(sig!.signature);
|
||||
}
|
||||
}
|
||||
|
||||
class LitecoinTrezorService extends HardwareWalletService with BitcoinHardwareWalletService, LitecoinHardwareWalletService {
|
||||
LitecoinTrezorService(this.connect);
|
||||
|
||||
final TrezorConnect connect;
|
||||
|
||||
@override
|
||||
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
|
||||
final indexRange = List.generate(limit, (i) => i + index);
|
||||
final requestParams = <TrezorGetPublicKeyParams>[];
|
||||
final xpubVersion = Bip44Conf.litecoinMainNet.altKeyNetVer;
|
||||
|
||||
for (final i in indexRange) {
|
||||
final derivationPath = "m/84'/2'/$i'";
|
||||
requestParams.add(TrezorGetPublicKeyParams(path: derivationPath, coin: "LTC"));
|
||||
}
|
||||
|
||||
final accounts = await connect.getPublicKeyBundle(requestParams);
|
||||
|
||||
return accounts?.map((account) {
|
||||
final hd = Bip32Slip10Secp256k1.fromExtendedKey(account.xpub, xpubVersion)
|
||||
.childKey(Bip32KeyIndex(0));
|
||||
|
||||
final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet);
|
||||
return HardwareAccountData(
|
||||
address: address,
|
||||
xpub: account.xpub,
|
||||
accountIndex: account.path[2] - 0x80000000, // unharden the path to get the index
|
||||
derivationPath: account.serializedPath,
|
||||
);
|
||||
}).toList() ??
|
||||
[];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> signLitecoinTransaction({
|
||||
required List<BitcoinBaseOutput> outputs,
|
||||
required List<PSBTReadyUtxoWithAddress> inputs,
|
||||
required Map<String, PublicKeyWithDerivationPath> publicKeys,
|
||||
}) async {
|
||||
final readyInputs = inputs
|
||||
.map((input) => TrezorTxInput(
|
||||
prevHash: input.utxo.txHash,
|
||||
prevIndex: input.utxo.vout,
|
||||
amount: input.utxo.value.toInt(),
|
||||
addressPath: Bip32PathParser.parse(input.ownerDerivationPath).toList(),
|
||||
scriptType: "SPENDWITNESS",
|
||||
))
|
||||
.toList();
|
||||
|
||||
final readyOutputs = outputs.map((output) {
|
||||
final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()];
|
||||
|
||||
return TrezorTxOutput(
|
||||
amount: output.toOutput.amount.toInt(),
|
||||
address: maybeChangePath != null
|
||||
? null
|
||||
: output.toOutput.scriptPubKey.toAddress(network: LitecoinNetwork.mainnet),
|
||||
scriptType: _getScriptType(output.toOutput.scriptPubKey.getAddressType()!),
|
||||
addressPath: maybeChangePath != null
|
||||
? Bip32PathParser.parse(maybeChangePath.derivationPath).toList()
|
||||
: null,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
final signedTx =
|
||||
await connect.signTransaction(coin: 'LTC', inputs: readyInputs, outputs: readyOutputs);
|
||||
|
||||
return signedTx!.serializedTx;
|
||||
}
|
||||
}
|
||||
|
||||
String _getScriptType(BitcoinAddressType addressType) {
|
||||
switch (addressType) {
|
||||
case P2pkhAddressType.p2pkh:
|
||||
return "PAYTOADDRESS";
|
||||
case P2shAddressType.p2wpkhInP2sh:
|
||||
return "PAYTOSCRIPTHASH";
|
||||
case SegwitAddresType.p2tr:
|
||||
return "PAYTOTAPROOT";
|
||||
case SegwitAddresType.p2wsh:
|
||||
return "PAYTOP2SHWITNESS";
|
||||
case SegwitAddresType.p2wpkh:
|
||||
return "PAYTOWITNESS";
|
||||
default:
|
||||
throw Exception("Unknown Address Type");
|
||||
}
|
||||
}
|
||||
@@ -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<MwebUtxo> mwebUtxosBox;
|
||||
Timer? _syncTimer;
|
||||
@@ -160,6 +161,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||
bool get hasRescan => true;
|
||||
|
||||
List<int> get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
|
||||
|
||||
List<int> get spendSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
|
||||
|
||||
static Future<LitecoinWallet> create(
|
||||
@@ -1064,9 +1066,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||
tx.isMweb = mwebEnabled;
|
||||
|
||||
if (!mwebEnabled) {
|
||||
tx.changeAddressOverride =
|
||||
(await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(coinTypeToSpendFrom: UnspentCoinType.nonMweb))
|
||||
.address;
|
||||
tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses)
|
||||
.getChangeAddress(coinTypeToSpendFrom: UnspentCoinType.nonMweb))
|
||||
.address;
|
||||
return tx;
|
||||
}
|
||||
await waitForMwebAddresses();
|
||||
@@ -1119,7 +1121,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||
bool isRegular = !hasMwebInput && !hasMwebOutput;
|
||||
bool shouldNotUseMwebChange = isPegIn || isRegular || !hasMwebInput;
|
||||
tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses)
|
||||
.getChangeAddress(coinTypeToSpendFrom: shouldNotUseMwebChange ? UnspentCoinType.nonMweb : UnspentCoinType.any))
|
||||
.getChangeAddress(
|
||||
coinTypeToSpendFrom:
|
||||
shouldNotUseMwebChange ? UnspentCoinType.nonMweb : UnspentCoinType.any))
|
||||
.address;
|
||||
if (isRegular) {
|
||||
tx.isMweb = false;
|
||||
@@ -1368,16 +1372,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||
return false;
|
||||
}
|
||||
|
||||
LedgerConnection? _ledgerConnection;
|
||||
LitecoinLedgerApp? _litecoinLedgerApp;
|
||||
|
||||
@override
|
||||
void setLedgerConnection(LedgerConnection connection) {
|
||||
_ledgerConnection = connection;
|
||||
_litecoinLedgerApp = LitecoinLedgerApp(_ledgerConnection!,
|
||||
derivationPath: walletInfo.derivationInfo!.derivationPath!);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BtcTransaction> buildHardwareWalletTransaction({
|
||||
required List<BitcoinBaseOutput> outputs,
|
||||
@@ -1391,38 +1385,26 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
|
||||
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
|
||||
}) async {
|
||||
final readyInputs = <LedgerTransaction>[];
|
||||
final masterFingerprint =
|
||||
await (hardwareWalletService as BitcoinHardwareWalletService).getMasterFingerprint();
|
||||
|
||||
final readyInputs = <PSBTReadyUtxoWithAddress>[];
|
||||
for (final utxo in utxos) {
|
||||
final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
|
||||
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
|
||||
|
||||
readyInputs.add(LedgerTransaction(
|
||||
readyInputs.add(PSBTReadyUtxoWithAddress(
|
||||
utxo: utxo.utxo,
|
||||
rawTx: rawTx,
|
||||
outputIndex: utxo.utxo.vout,
|
||||
ownerPublicKey: Uint8List.fromList(hex.decode(publicKeyAndDerivationPath.publicKey)),
|
||||
ownerDetails: utxo.ownerDetails,
|
||||
ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
|
||||
// sequence: enableRBF ? 0x1 : 0xffffffff,
|
||||
sequence: 0xffffffff,
|
||||
ownerMasterFingerprint:masterFingerprint,
|
||||
ownerPublicKey: publicKeyAndDerivationPath.publicKey,
|
||||
));
|
||||
}
|
||||
|
||||
String? changePath;
|
||||
for (final output in outputs) {
|
||||
final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()];
|
||||
if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath;
|
||||
}
|
||||
|
||||
final rawHex = await _litecoinLedgerApp!.createTransaction(
|
||||
inputs: readyInputs,
|
||||
outputs: outputs
|
||||
.map((e) => TransactionOutput.fromBigInt((e as BitcoinOutput).value,
|
||||
Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
|
||||
.toList(),
|
||||
changePath: changePath,
|
||||
sigHashType: 0x01,
|
||||
additionals: ["bech32"],
|
||||
isSegWit: true,
|
||||
useTrustedInputForSegwit: true);
|
||||
final rawHex = await (hardwareWalletService as LitecoinHardwareWalletService)
|
||||
.signLitecoinTransaction(outputs: outputs, inputs: readyInputs, publicKeys: publicKeys);
|
||||
|
||||
return BtcTransaction.fromRaw(rawHex);
|
||||
}
|
||||
|
||||
+77
-4
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:cw_core/hardware/device_not_connected_exception.dart' as exception;
|
||||
import 'package:trezor_connect/trezor_connect.dart';
|
||||
import 'package:web3dart/crypto.dart';
|
||||
import 'package:web3dart/web3dart.dart';
|
||||
|
||||
import '../utils/rlp_decode.dart' as rlp;
|
||||
|
||||
class EvmTrezorCredentials extends CredentialsWithKnownAddress {
|
||||
final String _address;
|
||||
|
||||
TrezorConnect? trezorConnect;
|
||||
String? derivationPath;
|
||||
|
||||
EvmTrezorCredentials(this._address);
|
||||
|
||||
@override
|
||||
EthereumAddress get address => EthereumAddress.fromHex(_address);
|
||||
|
||||
void setTrezorConnect(TrezorConnect connection, [String? derivationPath_]) {
|
||||
trezorConnect = connection;
|
||||
derivationPath = derivationPath_ ?? "m/44'/60'/0'/0/0";
|
||||
}
|
||||
|
||||
@override
|
||||
MsgSignature signToEcSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) =>
|
||||
throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
|
||||
|
||||
@override
|
||||
Future<MsgSignature> signToSignature(Uint8List payload,
|
||||
{int? chainId, bool isEIP1559 = false}) async {
|
||||
if (trezorConnect == null) {
|
||||
throw exception.DeviceNotConnectedException();
|
||||
}
|
||||
|
||||
if (isEIP1559) payload = payload.sublist(1);
|
||||
|
||||
final data = rlp.decode(payload);
|
||||
|
||||
TrezorEthereumTransaction tx;
|
||||
if (isEIP1559) {
|
||||
final chainId = data[0][0];
|
||||
final nonce = bytesToHex(data[1], include0x: true);
|
||||
final maxPriorityFeePerGas = bytesToHex(data[2], include0x: true);
|
||||
final maxFeePerGas = bytesToHex(data[3], include0x: true);
|
||||
final maxGas = bytesToHex(data[4], include0x: true);
|
||||
final to = bytesToHex(data[5], include0x: true);
|
||||
final value = bytesToHex(data[6], include0x: true);
|
||||
final txData = bytesToHex(data[7], include0x: true);
|
||||
|
||||
tx = TrezorEthereumTransaction(
|
||||
to: to,
|
||||
value: value,
|
||||
maxFeePerGas: maxFeePerGas,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas,
|
||||
gasLimit: maxGas,
|
||||
nonce: nonce,
|
||||
chainId: chainId,
|
||||
data: txData,
|
||||
);
|
||||
} else {
|
||||
final nonce = bytesToHex(data[0]);
|
||||
final gasPrice = bytesToHex(data[1]);
|
||||
final maxGas = bytesToHex(data[2]);
|
||||
final to = bytesToHex(data[3]);
|
||||
final value = bytesToHex(data[4]);
|
||||
final txData = bytesToHex(data[5]);
|
||||
tx = TrezorEthereumTransaction(
|
||||
to: to,
|
||||
value: value,
|
||||
gasPrice: gasPrice,
|
||||
gasLimit: maxGas,
|
||||
nonce: nonce,
|
||||
chainId: chainId ?? 1,
|
||||
data: txData,
|
||||
);
|
||||
}
|
||||
|
||||
final sig = (await trezorConnect!.ethereumSignTransaction(derivationPath!, transaction: tx))!;
|
||||
|
||||
final v = int.parse(strip0x(sig.v), radix: 16);
|
||||
|
||||
if (isEIP1559) {
|
||||
return MsgSignature(
|
||||
BigInt.parse(strip0x(sig.r), radix: 16), BigInt.parse(strip0x(sig.s), radix: 16), v);
|
||||
}
|
||||
|
||||
return MsgSignature(
|
||||
BigInt.parse(strip0x(sig.r), radix: 16), BigInt.parse(strip0x(sig.s), radix: 16), v);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> signPersonalMessage(Uint8List payload, {int? chainId}) async {
|
||||
if (isNotConnected) throw exception.DeviceNotConnectedException();
|
||||
return hexToBytes((await trezorConnect!
|
||||
.ethereumSignMessage(derivationPath!, message: bytesToHex(payload), hex: true))
|
||||
?.signature ??
|
||||
'');
|
||||
}
|
||||
|
||||
@override
|
||||
Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
|
||||
throw UnimplementedError("EvmLedgerCredentials.signPersonalMessageToUint8List");
|
||||
|
||||
bool get isNotConnected => trezorConnect == null;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cw_core/hardware/hardware_account_data.dart';
|
||||
import 'package:cw_core/hardware/hardware_wallet_service.dart';
|
||||
import 'package:trezor_connect/trezor_connect.dart';
|
||||
|
||||
class EVMChainTrezorService extends HardwareWalletService {
|
||||
EVMChainTrezorService(this.connect, {this.chainId = 1});
|
||||
|
||||
final TrezorConnect connect;
|
||||
final int chainId;
|
||||
|
||||
@override
|
||||
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
|
||||
final indexRange = List.generate(limit, (i) => i + index);
|
||||
|
||||
final requestParams = <TrezorGetAddressParams>[];
|
||||
for (final i in indexRange) {
|
||||
final derivationPath = "m/44'/60'/$i'/0/0";
|
||||
requestParams.add(TrezorGetAddressParams(path: derivationPath));
|
||||
}
|
||||
final accounts = await connect.ethereumGetAddressBundle(requestParams);
|
||||
|
||||
return accounts
|
||||
?.map((account) => HardwareAccountData(
|
||||
address: account.address,
|
||||
accountIndex: account.path[2] - 0x80000000, // unharden the path to get the index
|
||||
derivationPath: account.serializedPath,
|
||||
))
|
||||
.toList() ??
|
||||
[];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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]
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -536,11 +536,6 @@ class CWBitcoin extends Bitcoin {
|
||||
return (bitcoinWallet.feeRate(BitcoinTransactionPriority.fast) * 10).round();
|
||||
}
|
||||
|
||||
@override
|
||||
void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) {
|
||||
(wallet as ElectrumWallet).setLedgerConnection(connection);
|
||||
}
|
||||
|
||||
@override
|
||||
void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) {
|
||||
(wallet as ElectrumWallet).hardwareWalletService = service;
|
||||
@@ -560,6 +555,13 @@ class CWBitcoin extends Bitcoin {
|
||||
return LitecoinBitboxService(manager);
|
||||
}
|
||||
|
||||
@override
|
||||
HardwareWalletService getTrezorHardwareWalletService(
|
||||
trezor.TrezorConnect connect, bool isBitcoin) {
|
||||
if (isBitcoin) return BitcoinTrezorService(connect);
|
||||
return LitecoinTrezorService(connect);
|
||||
}
|
||||
|
||||
@override
|
||||
List<ElectrumSubAddress> getSilentPaymentAddresses(Object wallet) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
|
||||
@@ -56,6 +56,7 @@ import 'package:cake_wallet/view_model/dev/secure_preferences.dart';
|
||||
import 'package:cake_wallet/view_model/dev/shared_preferences.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/bitbox_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/trezor_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/link_view_model.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
@@ -281,6 +282,7 @@ import 'package:get_it/get_it.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:trezor_connect/trezor_connect.dart';
|
||||
import 'buy/kryptonim/kryptonim.dart';
|
||||
import 'buy/meld/meld_buy_provider.dart';
|
||||
import 'dogecoin/dogecoin.dart';
|
||||
@@ -400,6 +402,7 @@ Future<void> setup({
|
||||
switch(type) {
|
||||
case HardwareWalletType.bitbox: return getIt<BitboxViewModel>();
|
||||
case HardwareWalletType.ledger: return getIt<LedgerViewModel>();
|
||||
case HardwareWalletType.trezor: return getIt<TrezorViewModel>();
|
||||
case HardwareWalletType.cupcake:
|
||||
case HardwareWalletType.coldcard:
|
||||
case HardwareWalletType.seedsigner:
|
||||
@@ -412,6 +415,10 @@ Future<void> setup({
|
||||
|
||||
getIt.registerLazySingleton(() => BitboxViewModel());
|
||||
|
||||
getIt.registerLazySingleton(() => TrezorConnect("cakewallet://trezor_connect",
|
||||
appName: "Cake Wallet"));
|
||||
getIt.registerLazySingleton(() => TrezorViewModel(getIt<TrezorConnect>()));
|
||||
|
||||
final secretStore = await SecretStoreBase.load(getIt.get<SecureStorage>());
|
||||
|
||||
getIt.registerSingleton<SecretStore>(secretStore);
|
||||
|
||||
@@ -188,6 +188,9 @@ class CWEthereum extends Ethereum {
|
||||
} else if (service is EVMChainBitboxService) {
|
||||
((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
|
||||
.setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath);
|
||||
} else if (service is EVMChainTrezorService) {
|
||||
((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials)
|
||||
.setTrezorConnect(service.connect, wallet.walletInfo.derivationInfo?.derivationPath);
|
||||
}
|
||||
}
|
||||
@override
|
||||
@@ -198,6 +201,10 @@ class CWEthereum extends Ethereum {
|
||||
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
|
||||
EVMChainBitboxService(manager);
|
||||
|
||||
@override
|
||||
HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect) =>
|
||||
EVMChainTrezorService(connect);
|
||||
|
||||
@override
|
||||
List<String> getDefaultTokenContractAddresses() {
|
||||
return DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList();
|
||||
|
||||
@@ -62,6 +62,7 @@ import 'package:cw_core/window_size.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:cake_wallet/core/trade_monitor.dart';
|
||||
import 'package:cake_wallet/core/reset_service.dart';
|
||||
import 'package:trezor_connect/trezor_connect.dart';
|
||||
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
final rootKey = GlobalKey<RootState>();
|
||||
@@ -375,6 +376,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
|
||||
linkViewModel: linkViewModel,
|
||||
tradeMonitor: tradeMonitor,
|
||||
nodeSwitchingService: nodeSwitchingService,
|
||||
trezorConnect: getIt<TrezorConnect>(),
|
||||
child: ThemeProvider(
|
||||
themeStore: appStore.themeStore,
|
||||
materialAppBuilder: (context, theme, darkTheme, themeMode) => MaterialApp(
|
||||
|
||||
@@ -206,6 +206,9 @@ class CWPolygon extends Polygon {
|
||||
} else if (service is EVMChainBitboxService) {
|
||||
((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
|
||||
.setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath);
|
||||
} else if (service is EVMChainTrezorService) {
|
||||
((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials)
|
||||
.setTrezorConnect(service.connect, wallet.walletInfo.derivationInfo?.derivationPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,6 +220,10 @@ class CWPolygon extends Polygon {
|
||||
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
|
||||
EVMChainBitboxService(manager, chainId: 137);
|
||||
|
||||
@override
|
||||
HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect) =>
|
||||
EVMChainTrezorService(connect, chainId: 137);
|
||||
|
||||
@override
|
||||
List<String> getDefaultTokenContractAddresses() =>
|
||||
DefaultPolygonErc20Tokens().initialPolygonErc20Tokens.map((e) => e.contractAddress).toList();
|
||||
|
||||
@@ -345,6 +345,12 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
(_) => getIt.get<NewWalletTypePage>(
|
||||
param1: NewWalletTypeArguments(
|
||||
onTypeSelected: (BuildContext context, WalletType type) {
|
||||
if (hardwareWalletType == HardwareWalletType.trezor) {
|
||||
Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount,
|
||||
arguments: [type, hardwareWalletType]);
|
||||
return;
|
||||
}
|
||||
|
||||
final arguments = ConnectDevicePageParams(
|
||||
walletType: type,
|
||||
hardwareWalletType: hardwareWalletType,
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/core/node_switching_service.dart';
|
||||
import 'package:cake_wallet/core/totp_request_details.dart';
|
||||
import 'package:cake_wallet/core/trade_monitor.dart';
|
||||
import 'package:cake_wallet/entities/qr_scanner.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_utils.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/authentication_store.dart';
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:cake_wallet/view_model/link_view_model.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/utils/socket_health_logger.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/authentication_store.dart';
|
||||
import 'package:cake_wallet/entities/qr_scanner.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:trezor_connect/trezor_connect.dart';
|
||||
import 'package:uni_links/uni_links.dart';
|
||||
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_utils.dart';
|
||||
import 'package:cw_core/utils/socket_health_logger.dart';
|
||||
|
||||
class Root extends StatefulWidget {
|
||||
Root({
|
||||
@@ -33,6 +35,7 @@ class Root extends StatefulWidget {
|
||||
required this.linkViewModel,
|
||||
required this.tradeMonitor,
|
||||
required this.nodeSwitchingService,
|
||||
required this.trezorConnect,
|
||||
}) : super(key: key);
|
||||
|
||||
final AuthenticationStore authenticationStore;
|
||||
@@ -43,6 +46,7 @@ class Root extends StatefulWidget {
|
||||
final LinkViewModel linkViewModel;
|
||||
final TradeMonitor tradeMonitor;
|
||||
final NodeSwitchingService nodeSwitchingService;
|
||||
final TrezorConnect trezorConnect;
|
||||
|
||||
@override
|
||||
RootState createState() => RootState();
|
||||
@@ -110,6 +114,11 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
||||
|
||||
widget.linkViewModel.currentLink = uri;
|
||||
|
||||
if (uri.toString().startsWith(widget.trezorConnect.callbackBackUri)) {
|
||||
widget.trezorConnect.handleCallback(uri);
|
||||
return;
|
||||
}
|
||||
|
||||
bool requireAuth = await widget.authService.requireAuth();
|
||||
|
||||
if (!requireAuth && widget.authenticationStore.state == AuthenticationState.allowed) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -54,6 +54,9 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
HardwareWalletType get hardwareWalletType => HardwareWalletType.ledger;
|
||||
|
||||
@override
|
||||
@observable
|
||||
bool isBleEnabled = false;
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/polygon/polygon.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:cw_core/hardware/hardware_wallet_service.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:trezor_connect/trezor_connect.dart' as sdk;
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'trezor_view_model.g.dart';
|
||||
|
||||
class TrezorViewModel = TrezorViewModelBase with _$TrezorViewModel;
|
||||
|
||||
abstract class TrezorViewModelBase extends HardwareWalletViewModel with Store {
|
||||
final sdk.TrezorConnect trezorConnect;
|
||||
|
||||
TrezorViewModelBase(this.trezorConnect);
|
||||
|
||||
@override
|
||||
HardwareWalletType get hardwareWalletType => HardwareWalletType.trezor;
|
||||
|
||||
@override
|
||||
@observable
|
||||
bool isBleEnabled = false;
|
||||
|
||||
@override
|
||||
bool get hasBluetooth => false;
|
||||
|
||||
@override
|
||||
Future<void> updateBleState() async {}
|
||||
|
||||
@override
|
||||
Stream<HardwareWalletDevice> scanForBleDevices() => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<List<HardwareWalletDevice>> getAllUsbDevices() async => [];
|
||||
|
||||
@override
|
||||
Future<void> stopScanning() async {}
|
||||
|
||||
@override
|
||||
Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async => true;
|
||||
|
||||
@override
|
||||
bool get isConnected => true;
|
||||
|
||||
@override
|
||||
HardwareWalletService getHardwareWalletService(WalletType type) {
|
||||
switch (type) {
|
||||
case WalletType.bitcoin:
|
||||
return bitcoin!.getTrezorHardwareWalletService(trezorConnect, true);
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.getTrezorHardwareWalletService(trezorConnect, false);
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.getTrezorHardwareWalletService(trezorConnect);
|
||||
case WalletType.polygon:
|
||||
return polygon!.getTrezorHardwareWalletService(trezorConnect);
|
||||
default:
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initWallet(WalletBase wallet) {
|
||||
switch (wallet.type) {
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
case WalletType.polygon:
|
||||
return polygon!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
default:
|
||||
throw Exception('Unexpected wallet type: ${wallet.type}');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+5
-1
@@ -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
|
||||
|
||||
+15
-5
@@ -112,6 +112,7 @@ import 'package:cw_core/get_height_by_date.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
|
||||
import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
|
||||
import 'package:trezor_connect/trezor_connect.dart' as trezor;
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
""";
|
||||
@@ -138,6 +139,7 @@ import 'package:cw_bitcoin/litecoin_wallet.dart';
|
||||
import 'package:cw_bitcoin/hardware/bitcoin_ledger_service.dart';
|
||||
import 'package:cw_bitcoin/hardware/litecoin_ledger_service.dart';
|
||||
import 'package:cw_bitcoin/hardware/bitbox_service.dart';
|
||||
import 'package:cw_bitcoin/hardware/trezor_service.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
""";
|
||||
const bitcoinCwPart = "part 'cw_bitcoin.dart';";
|
||||
@@ -245,10 +247,10 @@ abstract class Bitcoin {
|
||||
void deleteSilentPaymentAddress(Object wallet, String address);
|
||||
Future<void> updateFeeRates(Object wallet);
|
||||
int getMaxCustomFeeRate(Object wallet);
|
||||
void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection);
|
||||
void setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
|
||||
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection, bool isBitcoin);
|
||||
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager, bool isBitcoin);
|
||||
HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect, bool isBitcoin);
|
||||
List<Output> updateOutputs(PendingTransaction pendingTransaction, List<Output> outputs);
|
||||
bool txIsReceivedSilentPayment(TransactionInfo txInfo);
|
||||
bool txIsMweb(TransactionInfo txInfo);
|
||||
@@ -697,6 +699,7 @@ import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
|
||||
import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
|
||||
import 'package:trezor_connect/trezor_connect.dart' as trezor;
|
||||
import 'package:web3dart/web3dart.dart';
|
||||
|
||||
""";
|
||||
@@ -707,11 +710,13 @@ import 'package:cw_evm/evm_chain_transaction_credentials.dart';
|
||||
import 'package:cw_evm/evm_chain_transaction_info.dart';
|
||||
import 'package:cw_evm/evm_chain_transaction_priority.dart';
|
||||
import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
|
||||
import 'package:cw_evm/hardware/evm_ledger_credentials.dart';
|
||||
import 'package:cw_evm/hardware/evm_bitbox_credentials.dart';
|
||||
import 'package:cw_evm/hardware/evm_chain_bitbox_credentials.dart';
|
||||
import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart';
|
||||
import 'package:cw_evm/hardware/evm_chain_trezor_credentials.dart';
|
||||
import 'package:cw_evm/evm_chain_wallet.dart';
|
||||
import 'package:cw_evm/hardware/evm_chain_bitbox_service.dart';
|
||||
import 'package:cw_evm/hardware/evm_chain_ledger_service.dart';
|
||||
import 'package:cw_evm/hardware/evm_chain_trezor_service.dart';
|
||||
|
||||
import 'package:cw_ethereum/ethereum_client.dart';
|
||||
import 'package:cw_ethereum/ethereum_wallet.dart';
|
||||
@@ -780,6 +785,7 @@ abstract class Ethereum {
|
||||
void setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
|
||||
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
|
||||
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager);
|
||||
HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect);
|
||||
List<String> getDefaultTokenContractAddresses();
|
||||
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
|
||||
}
|
||||
@@ -822,6 +828,7 @@ import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
|
||||
import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
|
||||
import 'package:trezor_connect/trezor_connect.dart' as trezor;
|
||||
import 'package:web3dart/web3dart.dart';
|
||||
|
||||
""";
|
||||
@@ -832,11 +839,13 @@ import 'package:cw_evm/evm_chain_transaction_credentials.dart';
|
||||
import 'package:cw_evm/evm_chain_transaction_info.dart';
|
||||
import 'package:cw_evm/evm_chain_transaction_priority.dart';
|
||||
import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
|
||||
import 'package:cw_evm/hardware/evm_ledger_credentials.dart';
|
||||
import 'package:cw_evm/hardware/evm_bitbox_credentials.dart';
|
||||
import 'package:cw_evm/hardware/evm_chain_bitbox_credentials.dart';
|
||||
import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart';
|
||||
import 'package:cw_evm/hardware/evm_chain_trezor_credentials.dart';
|
||||
import 'package:cw_evm/evm_chain_wallet.dart';
|
||||
import 'package:cw_evm/hardware/evm_chain_bitbox_service.dart';
|
||||
import 'package:cw_evm/hardware/evm_chain_ledger_service.dart';
|
||||
import 'package:cw_evm/hardware/evm_chain_trezor_service.dart';
|
||||
|
||||
import 'package:cw_polygon/polygon_client.dart';
|
||||
import 'package:cw_polygon/polygon_wallet.dart';
|
||||
@@ -895,6 +904,7 @@ abstract class Polygon {
|
||||
void setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
|
||||
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
|
||||
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager);
|
||||
HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect);
|
||||
List<String> getDefaultTokenContractAddresses();
|
||||
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user