mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-05-09 21:42:56 +00:00
608 lines
20 KiB
Dart
608 lines
20 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/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/exceptions.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_finalizer_v0.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_core/cake_hive.dart';
|
|
import 'package:cw_core/crypto_currency.dart';
|
|
import 'package:cw_core/encryption_file_utils.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/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';
|
|
|
|
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;
|
|
});
|
|
}
|
|
|
|
static Future<BitcoinWallet> create({
|
|
required String mnemonic,
|
|
required String password,
|
|
required WalletInfo walletInfo,
|
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
|
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;
|
|
}
|
|
|
|
final payjoinBox = await CakeHive.openBox<PayjoinSession>(PayjoinSession.boxName);
|
|
|
|
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 String password,
|
|
required EncryptionFileUtils encryptionFileUtils,
|
|
required bool alwaysScan,
|
|
}) 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;
|
|
}
|
|
}
|
|
|
|
final payjoinBox = await CakeHive.openBox<PayjoinSession>(PayjoinSession.boxName);
|
|
|
|
return BitcoinWallet(
|
|
mnemonic: mnemonic,
|
|
xpub: keysData.xPub,
|
|
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: alwaysScan,
|
|
payjoinBox: payjoinBox
|
|
);
|
|
}
|
|
|
|
LedgerConnection? _ledgerConnection;
|
|
BitcoinLedgerApp? _bitcoinLedgerApp;
|
|
|
|
@override
|
|
void setLedgerConnection(LedgerConnection connection) {
|
|
_ledgerConnection = connection;
|
|
_bitcoinLedgerApp = BitcoinLedgerApp(_ledgerConnection!,
|
|
derivationPath: walletInfo.derivationInfo!.derivationPath!);
|
|
}
|
|
|
|
late final PayjoinManager payjoinManager;
|
|
|
|
Future<PsbtV2> buildPsbt({
|
|
required List<BitcoinBaseOutput> outputs,
|
|
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 UtxoWithAddress 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)
|
|
.psbt;
|
|
}
|
|
|
|
@override
|
|
Future<BtcTransaction> buildHardwareWalletTransaction({
|
|
required List<BitcoinBaseOutput> outputs,
|
|
required BigInt fee,
|
|
required BasedUtxoNetwork network,
|
|
required List<UtxoWithAddress> utxos,
|
|
required Map<String, PublicKeyWithDerivationPath> publicKeys,
|
|
String? memo,
|
|
bool enableRBF = false,
|
|
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
|
|
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
|
|
}) async {
|
|
final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint();
|
|
|
|
final psbt = await buildPsbt(
|
|
outputs: outputs,
|
|
fee: fee,
|
|
network: network,
|
|
utxos: utxos,
|
|
publicKeys: publicKeys,
|
|
masterFingerprint: masterFingerprint,
|
|
memo: memo,
|
|
enableRBF: enableRBF,
|
|
inputOrdering: inputOrdering,
|
|
outputOrdering: outputOrdering,
|
|
);
|
|
|
|
final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt);
|
|
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) return tx;
|
|
|
|
final transaction = await buildPsbt(
|
|
utxos: tx.utxos,
|
|
outputs: tx.outputs.map((e) => BitcoinOutput(
|
|
address: BitcoinBaseAddress.fromString(e.scriptPubKey.toAddress()),
|
|
value: e.amount,
|
|
isSilentPayment: e.isSilentPayment,
|
|
isChange: e.isChange,
|
|
)).toList(),
|
|
fee: BigInt.from(tx.fee),
|
|
network: network,
|
|
memo: credentials.outputs.first.memo,
|
|
outputOrdering: BitcoinOrdering.none,
|
|
enableRBF: true,
|
|
publicKeys: tx.publicKeys!,
|
|
masterFingerprint: Uint8List(0)
|
|
);
|
|
|
|
final originalPsbt = await signPsbt(base64.encode(transaction.asPsbtV0()), getUtxoWithPrivateKeys());
|
|
|
|
tx.commitOverride = () async {
|
|
final sender =
|
|
await payjoinManager.initSender(payjoinUri, originalPsbt, tx.fee);
|
|
await payjoinManager.spawnNewSender(sender: sender, pjUrl: payjoinUri);
|
|
};
|
|
|
|
return tx;
|
|
}
|
|
|
|
Future<PsbtV2> createPayjoinTransaction(
|
|
BitcoinTransactionCredentials credentials) async {
|
|
try {
|
|
final outputs = <BitcoinOutput>[];
|
|
final hasMultiDestination = credentials.outputs.length > 1;
|
|
final sendAll = !hasMultiDestination && credentials.outputs.first.sendAll;
|
|
final memo = credentials.outputs.first.memo;
|
|
|
|
int credentialsAmount = 0;
|
|
bool hasSilentPayment = false;
|
|
|
|
for (final out in credentials.outputs) {
|
|
final outputAmount = out.formattedCryptoAmount!;
|
|
|
|
if (!sendAll && outputAmount <= 546) {
|
|
throw BitcoinTransactionNoDustException();
|
|
}
|
|
|
|
if (hasMultiDestination) {
|
|
if (out.sendAll) {
|
|
throw BitcoinTransactionWrongBalanceException();
|
|
}
|
|
}
|
|
|
|
credentialsAmount += outputAmount;
|
|
|
|
final addressStr =
|
|
out.isParsedAddress ? out.extractedAddress! : out.address;
|
|
|
|
print('[+] ElectrumWallet || createTx => addressStr: $addressStr');
|
|
|
|
final address = RegexUtils.addressTypeFromStr(addressStr, network);
|
|
|
|
if (address is SilentPaymentAddress) {
|
|
hasSilentPayment = true;
|
|
}
|
|
print('[+] ElectrumWallet => createTransaction() Running here');
|
|
|
|
if (sendAll) {
|
|
// The value will be changed after estimating the Tx size and deducting the fee from the total to be sent
|
|
outputs.add(BitcoinOutput(address: address, value: BigInt.from(0)));
|
|
} else {
|
|
outputs.add(BitcoinOutput(
|
|
address: address, value: BigInt.from(outputAmount)));
|
|
}
|
|
}
|
|
|
|
final feeRateInt = credentials.feeRate != null
|
|
? credentials.feeRate!
|
|
: feeRate(credentials.priority!);
|
|
|
|
EstimatedTxResult estimatedTx;
|
|
final updatedOutputs = outputs
|
|
.map((e) => BitcoinOutput(
|
|
address: e.address,
|
|
value: e.value,
|
|
isSilentPayment: e.isSilentPayment,
|
|
isChange: e.isChange,
|
|
))
|
|
.toList();
|
|
if (sendAll) {
|
|
estimatedTx = await estimateSendAllTx(
|
|
outputs,
|
|
feeRateInt,
|
|
memo: memo,
|
|
hasSilentPayment: hasSilentPayment,
|
|
);
|
|
} else {
|
|
estimatedTx = await estimateTxForAmount(
|
|
credentialsAmount,
|
|
outputs,
|
|
updatedOutputs,
|
|
feeRateInt,
|
|
memo: memo,
|
|
hasSilentPayment: hasSilentPayment,
|
|
);
|
|
}
|
|
|
|
final transaction = await buildPsbt(
|
|
utxos: estimatedTx.utxos,
|
|
outputs: outputs,
|
|
fee: BigInt.from(estimatedTx.fee),
|
|
network: network,
|
|
memo: estimatedTx.memo,
|
|
outputOrdering: BitcoinOrdering.none,
|
|
enableRBF: true,
|
|
publicKeys: estimatedTx.publicKeys,
|
|
masterFingerprint: Uint8List(0));
|
|
|
|
transaction.signWithUTXO(
|
|
estimatedTx.utxos
|
|
.map((e) =>
|
|
UtxoWithPrivateKey.fromUtxo(e, estimatedTx.inputPrivKeyInfos))
|
|
.toList(), (txDigest, utxo, key, sighash) {
|
|
if (utxo.utxo.isP2tr()) {
|
|
return key.signTapRoot(
|
|
txDigest,
|
|
sighash: sighash,
|
|
tweak: utxo.utxo.isSilentPayment != true,
|
|
);
|
|
} else {
|
|
return key.signInput(txDigest, sigHash: sighash);
|
|
}
|
|
});
|
|
|
|
return transaction;
|
|
} catch (e, st) {
|
|
print('[!] ElectrumWallet || e: $e and st: $st');
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
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,
|
|
).commit();
|
|
}
|
|
|
|
Future<PendingBitcoinTransaction> psbtToPendingTx(String preProcessedPsbt) async {
|
|
final psbt = PsbtV2()..deserializeV0(base64.decode(preProcessedPsbt));
|
|
|
|
final inputCount = psbt.getGlobalInputCount();
|
|
|
|
final unsignedTx = [];
|
|
for (var i = 0; i < inputCount; i++) {
|
|
if (psbt.getInputFinalScriptsig(i) == null) {
|
|
try {
|
|
psbt.getInputFinalScriptwitness(i);
|
|
} catch (_) {
|
|
unsignedTx.add(BytesUtils.toHexString(
|
|
psbt.getInputPreviousTxid(i).reversed.toList()));
|
|
}
|
|
}
|
|
}
|
|
|
|
psbt.signWithUTXO(
|
|
getUtxoWithPrivateKeys()
|
|
.where((e) => unsignedTx.contains(e.utxo.txHash))
|
|
.toList(), (txDigest, utxo, key, sighash) {
|
|
if (utxo.utxo.isP2tr()) {
|
|
return key.signTapRoot(
|
|
txDigest,
|
|
sighash: sighash,
|
|
tweak: utxo.utxo.isSilentPayment != true,
|
|
);
|
|
} else {
|
|
return key.signInput(txDigest, sigHash: sighash);
|
|
}
|
|
});
|
|
|
|
psbt.finalizeV0();
|
|
|
|
final btcTx =
|
|
BtcTransaction.fromRaw(BytesUtils.toHexString(psbt.extract()));
|
|
|
|
return PendingBitcoinTransaction(
|
|
btcTx,
|
|
type,
|
|
electrumClient: electrumClient,
|
|
amount: psbt.getOutputAmount(0),
|
|
// ToDo
|
|
fee: 0,
|
|
// ToDo
|
|
feeRate: "Payjoin",
|
|
// ToDo
|
|
network: network,
|
|
hasChange: true,
|
|
isSendAll: true,
|
|
hasTaprootInputs: false, // ToDo: (Konsti) Support Taproot
|
|
)..addListener(
|
|
(transaction) async {
|
|
transactionHistory.addOne(transaction);
|
|
await updateBalance();
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<String> signPsbt(
|
|
String preProcessedPsbt, List<UtxoWithPrivateKey> utxos) async {
|
|
final psbt = PsbtV2()..deserializeV0(base64Decode(preProcessedPsbt));
|
|
|
|
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);
|
|
});
|
|
|
|
psbt.finalizeV0();
|
|
return base64Encode(psbt.asPsbtV0());
|
|
}
|
|
|
|
@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 _bitcoinLedgerApp!.signMessage(
|
|
message: ascii.encode(message), signDerivationPath: derivationPath);
|
|
return base64Encode(signature);
|
|
}
|
|
|
|
return super.signMessage(message, address: address);
|
|
}
|
|
}
|