mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-23 06:38:35 +02:00
* chore: add ffi and ffigen dependencies to pubspec.yaml * refactor: replace method channel with FFI implementation for MWeb module * fix: correct `cd` command in build_mwebd.sh script * chore: add go.sum file with dependency checksums * fix: ensure ANDROID_NDK_VERSION is set before building and dynamically update NDK_BIN path * fix: update NDK_BIN path to dynamically detect platform in build_mwebd.sh script * refactor: remove unused mwebd dependencies and clean up CwMwebPlugin code * Update scripts/android/build_mwebd.sh Co-authored-by: cyan <cyjan@mrcyjanek.net> * refactor: replace iOS MoneroWallet framework with Mwebd.xcframework and clean up plugin implementation [skip-ci] * refactor: extract hardware wallet service abstraction * refactor: extend BitcoinHardwareWalletService from HardwareWalletService and override account retrieval method * feat: add BitBox hardware wallet support and implement manufacturer selection page with custom UI components * refactor: update manufacturer selection UI and integrate custom gradient styles for ManufacturerOptionTile widget * fix: mwebd ffi ios * feat: add BitBox hardware wallet support, update derivation paths, and refactor hardware wallet credential implementations * feat: add bitbox support and refactor the old ledger implementation * feat: add BitBox hardware wallet support and refactor hardware wallet service implementations * feat: refactor hardware wallet support to introduce abstraction for BitBox and Ledger; update device scanning, initialization, and UI integration * fix: BitBox hardware wallet ethereum transaction signing * chore: update BitBox Flutter path references and include build steps in CI workflows * chore: update CI workflows to include `--dont-install` flag for BitBox Flutter builds * refactor: replace if-else logic with switch-case for HardwareWalletViewModel factory * feat: refactor hardware wallet services to introduce shared interface, improve ledger and bitbox implementations, and support additional transaction signing operations * feat: add SVG assets for hardware wallet manufacturers (Foundation, Keystone, SeedSigner) * feat: redesign hardware wallet restoration flow with improved routing, UI updates, and manufacturer-specific device selection handling * fix: validate required BIP353 fields in transaction builder to prevent potential null value errors * feat: improve BitBox transaction signing logs with detailed PSBT information * refactor: update MWebFfi class to use non-nullable `lib` and improve address debug logging * feat: add BitBox coin type constant and improve hardware wallet service extensibility * chore: remove unused iOS/Android build scripts for mwebd * feat: add BitBox SVG asset and update localization for hardware wallet connection instructions * feat: update manufacturer tags and add Toast Easter egg for device selection interactions * refactor: replace LedgerViewModel with HardwareWalletViewModel across providers and improve hardware wallet handling * refactor: replace mweb.so with libmweb.so, use sync methods in MWebFfi, and update iOS/Android frameworks/scripts accordingly * feat: add Ledger Nano X SVG asset to hardware wallet resources * chore: unify mwebd build process in workflows, remove unused gomobile init command * refactor: remove redundant print statement and unnecessary whitespace * chore: add `gomobile init` to Android PR test build workflow * chore: update Android PR test build workflow and add file existence check in `build_bitbox_flutter.sh` * chore: override `ledger_usb_plus` dependency in pubspec_base.yaml * feat: add verbose logging for BitBox connection and initialization process * Update lib/view_model/hardware_wallet/bitbox_view_model.dart [skip ci] * CW-1206-refactor-hardware-wallets-code (#2507) * feat: add support for new hardware wallet types (Cupcake, Coldcard, SeedSigner) and improve QR Code handling - Introduced new hardware wallet types (Cupcake, Coldcard, SeedSigner) with corresponding assets and localization updates. - Updated QR Code format selection for hardware wallets with new formats (BCUR, BBQR) specific to devices. - Improved hardware wallet restoration flow to include airgapped wallet handling via QR Code scanning. - Refactored routing, view models, and dependency injection to accommodate new hardware wallet types. - Enhanced UI for device manufacturer selection and QR Code format dialogs. * feat: update hardware wallet assets and restoration flow * feat: add changing airgapped wallet type * feat: add Cupcake Man SVG asset to hardware wallet resources * refactor: update Litecoin receive options to require wallet parameter and handle hardware wallets * fix: debugging keystone integration feat: support for zpub view only restore * fix: seed signer restore * fix: disable bitbox restoring (for now) [skip-ci] * refactor: rename `isCupcake` to `isBitcoinViewOnly` for clarity [skip ci] --------- Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * refactor: standardize hardware wallet connection handling across wallet types --------- Co-authored-by: cyan <cyjan@mrcyjanek.net> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
499 lines
17 KiB
Dart
499 lines
17 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:bip39/bip39.dart' as bip39;
|
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
|
import 'package:cw_bitcoin/address_from_output.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_wallet_addresses.dart';
|
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
|
import 'package:cw_bitcoin/electrum_derivations.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/payjoin/manager.dart';
|
|
import 'package:cw_bitcoin/payjoin/storage.dart';
|
|
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
|
import 'package:cw_bitcoin/psbt/signer.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_core/crypto_currency.dart';
|
|
import 'package:cw_core/encryption_file_utils.dart';
|
|
import 'package:cw_core/output_info.dart';
|
|
import 'package:cw_core/payjoin_session.dart';
|
|
import 'package:cw_core/pending_transaction.dart';
|
|
import 'package:cw_core/unspent_coins_info.dart';
|
|
import 'package:cw_core/utils/zpub.dart';
|
|
import 'package:cw_core/wallet_info.dart';
|
|
import 'package:cw_core/wallet_keys_file.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:hive/hive.dart';
|
|
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
|
import 'package:ledger_bitcoin/psbt.dart';
|
|
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
|
import 'package:mobx/mobx.dart';
|
|
import 'package:ur/cbor_lite.dart';
|
|
import 'package:ur/ur.dart';
|
|
import 'package:ur/ur_decoder.dart';
|
|
|
|
part 'bitcoin_wallet.g.dart';
|
|
|
|
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
|
|
|
|
abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|
BitcoinWalletBase({
|
|
required String password,
|
|
required WalletInfo walletInfo,
|
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
|
required Box<PayjoinSession> payjoinBox,
|
|
required EncryptionFileUtils encryptionFileUtils,
|
|
Uint8List? seedBytes,
|
|
String? mnemonic,
|
|
String? xpub,
|
|
String? addressPageType,
|
|
BasedUtxoNetwork? networkParam,
|
|
List<BitcoinAddressRecord>? initialAddresses,
|
|
ElectrumBalance? initialBalance,
|
|
Map<String, int>? initialRegularAddressIndex,
|
|
Map<String, int>? initialChangeAddressIndex,
|
|
String? passphrase,
|
|
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
|
int initialSilentAddressIndex = 0,
|
|
bool? alwaysScan,
|
|
}) : super(
|
|
mnemonic: mnemonic,
|
|
passphrase: passphrase,
|
|
xpub: xpub,
|
|
password: password,
|
|
walletInfo: walletInfo,
|
|
unspentCoinsInfo: unspentCoinsInfo,
|
|
network: networkParam == null
|
|
? BitcoinNetwork.mainnet
|
|
: networkParam == BitcoinNetwork.mainnet
|
|
? BitcoinNetwork.mainnet
|
|
: BitcoinNetwork.testnet,
|
|
initialAddresses: initialAddresses,
|
|
initialBalance: initialBalance,
|
|
seedBytes: seedBytes,
|
|
encryptionFileUtils: encryptionFileUtils,
|
|
currency: networkParam == BitcoinNetwork.testnet
|
|
? CryptoCurrency.tbtc
|
|
: CryptoCurrency.btc,
|
|
alwaysScan: alwaysScan,
|
|
) {
|
|
// in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here)
|
|
// the sideHd derivation path = m/84'/0'/0'/1 (account 1, index unspecified here)
|
|
// String derivationPath = walletInfo.derivationInfo!.derivationPath!;
|
|
// String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1";
|
|
// final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
|
|
|
|
payjoinManager = PayjoinManager(PayjoinStorage(payjoinBox), this);
|
|
walletAddresses = BitcoinWalletAddresses(walletInfo,
|
|
initialAddresses: initialAddresses,
|
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
|
initialSilentAddresses: initialSilentAddresses,
|
|
initialSilentAddressIndex: initialSilentAddressIndex,
|
|
mainHd: hd,
|
|
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
|
|
network: networkParam ?? network,
|
|
masterHd:
|
|
seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null,
|
|
isHardwareWallet: walletInfo.isHardwareWallet,
|
|
payjoinManager: payjoinManager);
|
|
|
|
autorun((_) {
|
|
this.walletAddresses.isEnabledAutoGenerateSubaddress =
|
|
this.isEnabledAutoGenerateSubaddress;
|
|
});
|
|
}
|
|
|
|
@override
|
|
bool get hasRescan => true;
|
|
|
|
static Future<BitcoinWallet> create({
|
|
required String mnemonic,
|
|
required String password,
|
|
required WalletInfo walletInfo,
|
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
|
required Box<PayjoinSession> payjoinBox,
|
|
required EncryptionFileUtils encryptionFileUtils,
|
|
String? passphrase,
|
|
String? addressPageType,
|
|
BasedUtxoNetwork? network,
|
|
List<BitcoinAddressRecord>? initialAddresses,
|
|
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
|
ElectrumBalance? initialBalance,
|
|
Map<String, int>? initialRegularAddressIndex,
|
|
Map<String, int>? initialChangeAddressIndex,
|
|
int initialSilentAddressIndex = 0,
|
|
}) async {
|
|
late Uint8List seedBytes;
|
|
|
|
switch (walletInfo.derivationInfo?.derivationType) {
|
|
case DerivationType.bip39:
|
|
seedBytes = await bip39.mnemonicToSeed(
|
|
mnemonic,
|
|
passphrase: passphrase ?? "",
|
|
);
|
|
break;
|
|
case DerivationType.electrum:
|
|
default:
|
|
seedBytes =
|
|
await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
|
break;
|
|
}
|
|
|
|
return BitcoinWallet(
|
|
mnemonic: mnemonic,
|
|
passphrase: passphrase ?? "",
|
|
password: password,
|
|
walletInfo: walletInfo,
|
|
unspentCoinsInfo: unspentCoinsInfo,
|
|
initialAddresses: initialAddresses,
|
|
initialSilentAddresses: initialSilentAddresses,
|
|
initialSilentAddressIndex: initialSilentAddressIndex,
|
|
initialBalance: initialBalance,
|
|
encryptionFileUtils: encryptionFileUtils,
|
|
seedBytes: seedBytes,
|
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
|
addressPageType: addressPageType,
|
|
networkParam: network,
|
|
payjoinBox: payjoinBox,
|
|
);
|
|
}
|
|
|
|
static Future<BitcoinWallet> open({
|
|
required String name,
|
|
required WalletInfo walletInfo,
|
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
|
required Box<PayjoinSession> payjoinBox,
|
|
required String password,
|
|
required EncryptionFileUtils encryptionFileUtils,
|
|
}) async {
|
|
final network = walletInfo.network != null
|
|
? BasedUtxoNetwork.fromName(walletInfo.network!)
|
|
: BitcoinNetwork.mainnet;
|
|
|
|
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
|
|
|
ElectrumWalletSnapshot? snp = null;
|
|
|
|
try {
|
|
snp = await ElectrumWalletSnapshot.load(
|
|
encryptionFileUtils,
|
|
name,
|
|
walletInfo.type,
|
|
password,
|
|
network,
|
|
);
|
|
} catch (e) {
|
|
if (!hasKeysFile) rethrow;
|
|
}
|
|
|
|
final WalletKeysData keysData;
|
|
// Migrate wallet from the old scheme to then new .keys file scheme
|
|
if (!hasKeysFile) {
|
|
keysData = WalletKeysData(
|
|
mnemonic: snp!.mnemonic,
|
|
xPub: snp.xpub,
|
|
passphrase: snp.passphrase,
|
|
);
|
|
} else {
|
|
keysData = await WalletKeysFile.readKeysFile(
|
|
name,
|
|
walletInfo.type,
|
|
password,
|
|
encryptionFileUtils,
|
|
);
|
|
}
|
|
|
|
walletInfo.derivationInfo ??= DerivationInfo();
|
|
|
|
// set the default if not present:
|
|
walletInfo.derivationInfo!.derivationPath ??=
|
|
snp?.derivationPath ?? electrum_path;
|
|
walletInfo.derivationInfo!.derivationType ??=
|
|
snp?.derivationType ?? DerivationType.electrum;
|
|
|
|
Uint8List? seedBytes = null;
|
|
final mnemonic = keysData.mnemonic;
|
|
final passphrase = keysData.passphrase;
|
|
|
|
if (mnemonic != null) {
|
|
switch (walletInfo.derivationInfo!.derivationType) {
|
|
case DerivationType.electrum:
|
|
seedBytes =
|
|
await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
|
break;
|
|
case DerivationType.bip39:
|
|
default:
|
|
seedBytes = await bip39.mnemonicToSeed(
|
|
mnemonic,
|
|
passphrase: passphrase ?? '',
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return BitcoinWallet(
|
|
mnemonic: mnemonic,
|
|
xpub: keysData.xPub != null ? convertZpubToXpub(keysData.xPub!) : null,
|
|
password: password,
|
|
passphrase: passphrase,
|
|
walletInfo: walletInfo,
|
|
unspentCoinsInfo: unspentCoinsInfo,
|
|
initialAddresses: snp?.addresses,
|
|
initialSilentAddresses: snp?.silentAddresses,
|
|
initialSilentAddressIndex: snp?.silentAddressIndex ?? 0,
|
|
initialBalance: snp?.balance,
|
|
encryptionFileUtils: encryptionFileUtils,
|
|
seedBytes: seedBytes,
|
|
initialRegularAddressIndex: snp?.regularAddressIndex,
|
|
initialChangeAddressIndex: snp?.changeAddressIndex,
|
|
addressPageType: snp?.addressPageType,
|
|
networkParam: network,
|
|
alwaysScan: snp?.alwaysScan,
|
|
payjoinBox: payjoinBox);
|
|
}
|
|
|
|
@override
|
|
Future<void> close({bool shouldCleanup = false}) async {
|
|
payjoinManager.cleanupSessions();
|
|
super.close(shouldCleanup: shouldCleanup);
|
|
}
|
|
|
|
late final PayjoinManager payjoinManager;
|
|
|
|
bool get isPayjoinAvailable => unspentCoinsInfo.values
|
|
.where((element) =>
|
|
element.walletId == id && element.isSending && !element.isFrozen)
|
|
.isNotEmpty;
|
|
|
|
Future<PsbtV2> buildPsbt({
|
|
required List<BitcoinBaseOutput> outputs,
|
|
required List<OutputInfo> cwOutputs,
|
|
required BigInt fee,
|
|
required BasedUtxoNetwork network,
|
|
required List<UtxoWithAddress> utxos,
|
|
required Map<String, PublicKeyWithDerivationPath> publicKeys,
|
|
required Uint8List masterFingerprint,
|
|
String? memo,
|
|
bool enableRBF = false,
|
|
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
|
|
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
|
|
}) async {
|
|
final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[];
|
|
for (final utxo in utxos) {
|
|
final rawTx =
|
|
await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
|
|
final publicKeyAndDerivationPath =
|
|
publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
|
|
|
|
psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
|
|
utxo: utxo.utxo,
|
|
rawTx: rawTx,
|
|
ownerDetails: utxo.ownerDetails,
|
|
ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
|
|
ownerMasterFingerprint: masterFingerprint,
|
|
ownerPublicKey: publicKeyAndDerivationPath.publicKey,
|
|
));
|
|
}
|
|
|
|
return PSBTTransactionBuild(
|
|
inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF, cwOutputs: cwOutputs)
|
|
.psbt;
|
|
}
|
|
|
|
@override
|
|
Future<BtcTransaction> buildHardwareWalletTransaction({
|
|
required List<BitcoinBaseOutput> outputs,
|
|
required BigInt fee,
|
|
required BasedUtxoNetwork network,
|
|
required List<UtxoWithAddress> utxos,
|
|
required List<OutputInfo> cwOutputs,
|
|
required Map<String, PublicKeyWithDerivationPath> publicKeys,
|
|
String? memo,
|
|
bool enableRBF = false,
|
|
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
|
|
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
|
|
}) async {
|
|
final masterFingerprint =
|
|
await (hardwareWalletService as BitcoinHardwareWalletService).getMasterFingerprint();
|
|
|
|
final psbt = await buildPsbt(
|
|
outputs: outputs,
|
|
fee: fee,
|
|
network: network,
|
|
utxos: utxos,
|
|
cwOutputs: cwOutputs,
|
|
publicKeys: publicKeys,
|
|
masterFingerprint: masterFingerprint,
|
|
memo: memo,
|
|
enableRBF: enableRBF,
|
|
inputOrdering: inputOrdering,
|
|
outputOrdering: outputOrdering,
|
|
);
|
|
|
|
final psbtStr = base64Encode(psbt.serialize());
|
|
final rawHex = await hardwareWalletService!.signTransaction(transaction: psbtStr);
|
|
return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex));
|
|
}
|
|
|
|
@override
|
|
Future<PendingTransaction> createTransaction(Object credentials) async {
|
|
credentials = credentials as BitcoinTransactionCredentials;
|
|
|
|
final tx = (await super.createTransaction(credentials))
|
|
as PendingBitcoinTransaction;
|
|
|
|
final payjoinUri = credentials.payjoinUri;
|
|
if (payjoinUri == null && !tx.shouldCommitUR()) return tx;
|
|
|
|
final transaction = await buildPsbt(
|
|
utxos: tx.utxos,
|
|
outputs: tx.outputs
|
|
.map((e) => BitcoinOutput(
|
|
address: addressFromScript(e.scriptPubKey),
|
|
value: e.amount,
|
|
isSilentPayment: e.isSilentPayment,
|
|
isChange: e.isChange,
|
|
))
|
|
.toList(),
|
|
cwOutputs: credentials.outputs,
|
|
fee: BigInt.from(tx.fee),
|
|
network: network,
|
|
memo: credentials.outputs.first.memo,
|
|
outputOrdering: BitcoinOrdering.none,
|
|
enableRBF: true,
|
|
publicKeys: tx.publicKeys!,
|
|
masterFingerprint: Uint8List.fromList([0, 0, 0, 0]));
|
|
|
|
if (tx.shouldCommitUR()) {
|
|
tx.unsignedPsbt = transaction.asPsbtV0();
|
|
return tx;
|
|
}
|
|
|
|
final originalPsbt =
|
|
await signPsbt(base64.encode(transaction.asPsbtV0()), getUtxoWithPrivateKeys());
|
|
|
|
tx.commitOverride = () async {
|
|
final sender =
|
|
await payjoinManager.initSender(payjoinUri!, originalPsbt, int.parse(tx.feeRate));
|
|
payjoinManager.spawnNewSender(
|
|
sender: sender, pjUrl: payjoinUri, amount: BigInt.from(tx.amount));
|
|
};
|
|
|
|
return tx;
|
|
}
|
|
|
|
List<UtxoWithPrivateKey> getUtxoWithPrivateKeys() => unspentCoins
|
|
.where((e) => (e.isSending && !e.isFrozen))
|
|
.map((unspent) => UtxoWithPrivateKey.fromUnspent(unspent, this))
|
|
.toList();
|
|
|
|
Future<void> commitPsbt(String finalizedPsbt) {
|
|
final psbt = PsbtV2()..deserializeV0(base64.decode(finalizedPsbt));
|
|
|
|
final btcTx =
|
|
BtcTransaction.fromRaw(BytesUtils.toHexString(psbt.extract()));
|
|
|
|
return PendingBitcoinTransaction(
|
|
btcTx,
|
|
type,
|
|
electrumClient: electrumClient,
|
|
amount: 0,
|
|
fee: 0,
|
|
feeRate: "",
|
|
network: network,
|
|
hasChange: true,
|
|
isViewOnly: false,
|
|
).commit();
|
|
}
|
|
|
|
Future<String> signPsbt(
|
|
String preProcessedPsbt, List<UtxoWithPrivateKey> utxos) async {
|
|
final psbt = PsbtV2()..deserializeV0(base64Decode(preProcessedPsbt));
|
|
|
|
await psbt.signWithUTXO(utxos, (txDigest, utxo, key, sighash) {
|
|
return utxo.utxo.isP2tr()
|
|
? key.signTapRoot(
|
|
txDigest,
|
|
sighash: sighash,
|
|
tweak: utxo.utxo.isSilentPayment != true,
|
|
)
|
|
: key.signInput(txDigest, sigHash: sighash);
|
|
}, (txId, vout) async {
|
|
final txHex = await electrumClient.getTransactionHex(hash: txId);
|
|
final output = BtcTransaction.fromRaw(txHex).outputs[vout];
|
|
return TaprootAmountScriptPair(output.amount, output.scriptPubKey);
|
|
});
|
|
|
|
psbt.finalizeV0();
|
|
return base64Encode(psbt.asPsbtV0());
|
|
}
|
|
|
|
Future<void> commitPsbtUR(List<String> urCodes) async {
|
|
if (urCodes.isEmpty) throw Exception("No QR code got scanned");
|
|
bool isUr = urCodes.any((str) {
|
|
return str.startsWith("ur:psbt/");
|
|
});
|
|
if (isUr) {
|
|
final ur = URDecoder();
|
|
for (final inp in urCodes) {
|
|
ur.receivePart(inp);
|
|
}
|
|
final result = (ur.result as UR);
|
|
final cbor = result.cbor;
|
|
final cborDecoder = CBORDecoder(cbor);
|
|
final out = cborDecoder.decodeBytes();
|
|
final bytes = out.$1;
|
|
final base64psbt = base64Encode(bytes);
|
|
final psbt = PsbtV2()..deserializeV0(base64Decode(base64psbt));
|
|
|
|
// psbt.finalize();
|
|
final finalized = base64Encode(psbt.serialize());
|
|
await commitPsbt(finalized);
|
|
} else {
|
|
final btcTx = BtcTransaction.fromRaw(urCodes.first);
|
|
|
|
return PendingBitcoinTransaction(
|
|
btcTx,
|
|
type,
|
|
electrumClient: electrumClient,
|
|
amount: 0,
|
|
fee: 0,
|
|
feeRate: "",
|
|
network: network,
|
|
hasChange: true,
|
|
isViewOnly: false,
|
|
).commit();
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<String> signMessage(String message, {String? address = null}) async {
|
|
if (walletInfo.isHardwareWallet) {
|
|
final addressEntry = address != null
|
|
? walletAddresses.allAddresses
|
|
.firstWhere((element) => element.address == address)
|
|
: null;
|
|
final index = addressEntry?.index ?? 0;
|
|
final isChange = addressEntry?.isHidden == true ? 1 : 0;
|
|
final accountPath = walletInfo.derivationInfo?.derivationPath;
|
|
final derivationPath =
|
|
accountPath != null ? "$accountPath/$isChange/$index" : null;
|
|
|
|
final signature = await hardwareWalletService!
|
|
.signMessage(message: ascii.encode(message), derivationPath: derivationPath);
|
|
return base64Encode(signature);
|
|
}
|
|
|
|
return super.signMessage(message, address: address);
|
|
}
|
|
}
|