mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-01-09 20:39:35 +00:00
add transaction creation
This commit is contained in:
parent
c563e300f8
commit
b4316581c8
13 changed files with 425 additions and 129 deletions
|
@ -19,7 +19,7 @@ class BitcoinAddressRecord {
|
||||||
bool operator ==(Object o) =>
|
bool operator ==(Object o) =>
|
||||||
o is BitcoinAddressRecord && address == o.address;
|
o is BitcoinAddressRecord && address == o.address;
|
||||||
|
|
||||||
final String address;
|
String address;
|
||||||
final bool isHidden;
|
final bool isHidden;
|
||||||
final int index;
|
final int index;
|
||||||
bool get isUsed => _isUsed;
|
bool get isUsed => _isUsed;
|
||||||
|
|
|
@ -21,4 +21,8 @@ class BitcoinUnspent {
|
||||||
bool isSending;
|
bool isSending;
|
||||||
bool isFrozen;
|
bool isFrozen;
|
||||||
String note;
|
String note;
|
||||||
|
|
||||||
|
void updateAddress(String newAddress) {
|
||||||
|
address.address = newAddress;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
|
||||||
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
@ -11,6 +12,7 @@ import 'package:mobx/mobx.dart';
|
||||||
import 'package:rxdart/subjects.dart';
|
import 'package:rxdart/subjects.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
|
import 'package:bitbox/bitbox.dart';
|
||||||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_bitcoin/address_to_output_script.dart';
|
import 'package:cw_bitcoin/address_to_output_script.dart';
|
||||||
|
@ -191,7 +193,7 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PendingBitcoinTransaction> createTransaction(Object credentials) async {
|
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||||
const minAmount = 546;
|
const minAmount = 546;
|
||||||
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
||||||
final inputs = <BitcoinUnspent>[];
|
final inputs = <BitcoinUnspent>[];
|
||||||
|
@ -668,7 +670,8 @@ abstract class ElectrumWalletBase
|
||||||
final addresses = walletAddresses.addresses.toList();
|
final addresses = walletAddresses.addresses.toList();
|
||||||
final balanceFutures = <Future<Map<String, dynamic>>>[];
|
final balanceFutures = <Future<Map<String, dynamic>>>[];
|
||||||
for (var i = 0; i < addresses.length; i++) {
|
for (var i = 0; i < addresses.length; i++) {
|
||||||
final addressRecord = addresses[i];
|
// walletInfo.type != WalletType.bitcoinCash ? Address :
|
||||||
|
final addressRecord = addresses[i] ;
|
||||||
final sh = scriptHash(addressRecord.address, networkType: networkType);
|
final sh = scriptHash(addressRecord.address, networkType: networkType);
|
||||||
final balanceFuture = electrumClient.getBalance(sh);
|
final balanceFuture = electrumClient.getBalance(sh);
|
||||||
balanceFutures.add(balanceFuture);
|
balanceFutures.add(balanceFuture);
|
||||||
|
|
|
@ -66,6 +66,15 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.6"
|
version: "1.0.6"
|
||||||
|
bitbox:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "."
|
||||||
|
ref: main
|
||||||
|
resolved-ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11
|
||||||
|
url: "https://github.com/Serhii-Borodenko/cw_bitbox.git"
|
||||||
|
source: git
|
||||||
|
version: "1.0.1"
|
||||||
bitcoin_flutter:
|
bitcoin_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -23,6 +23,10 @@ dependencies:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/bitcoin_flutter.git
|
url: https://github.com/cake-tech/bitcoin_flutter.git
|
||||||
ref: cake-update-v2
|
ref: cake-update-v2
|
||||||
|
bitbox:
|
||||||
|
git:
|
||||||
|
url: https://github.com/Serhii-Borodenko/cw_bitbox.git
|
||||||
|
ref: main
|
||||||
rxdart: ^0.27.5
|
rxdart: ^0.27.5
|
||||||
unorm_dart: ^0.2.0
|
unorm_dart: ^0.2.0
|
||||||
cryptography: ^2.0.5
|
cryptography: ^2.0.5
|
||||||
|
|
|
@ -2,7 +2,6 @@ export 'bitcoin_cash_balance.dart';
|
||||||
export 'bitcoin_cash_client.dart';
|
export 'bitcoin_cash_client.dart';
|
||||||
export 'bitcoin_cash_transaction_history.dart';
|
export 'bitcoin_cash_transaction_history.dart';
|
||||||
export 'bitcoin_cash_transaction_info.dart';
|
export 'bitcoin_cash_transaction_info.dart';
|
||||||
export 'bitcoin_cash_transaction_priority.dart';
|
|
||||||
export 'bitcoin_cash_wallet.dart';
|
export 'bitcoin_cash_wallet.dart';
|
||||||
export 'bitcoin_cash_wallet_addresses.dart';
|
export 'bitcoin_cash_wallet_addresses.dart';
|
||||||
export 'bitcoin_cash_wallet_creation_credentials.dart';
|
export 'bitcoin_cash_wallet_creation_credentials.dart';
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
|
||||||
|
|
||||||
class BitcoinCashTransactionPriority extends BitcoinTransactionPriority {
|
|
||||||
const BitcoinCashTransactionPriority({required String title, required int raw})
|
|
||||||
: super(title: title, raw: raw);
|
|
||||||
|
|
||||||
static const List<BitcoinCashTransactionPriority> all = [fast, medium, slow];
|
|
||||||
static const BitcoinCashTransactionPriority slow =
|
|
||||||
BitcoinCashTransactionPriority(title: 'Slow', raw: 0);
|
|
||||||
static const BitcoinCashTransactionPriority medium =
|
|
||||||
BitcoinCashTransactionPriority(title: 'Medium', raw: 1);
|
|
||||||
static const BitcoinCashTransactionPriority fast =
|
|
||||||
BitcoinCashTransactionPriority(title: 'Fast', raw: 2);
|
|
||||||
|
|
||||||
static BitcoinCashTransactionPriority deserialize({required int raw}) {
|
|
||||||
switch (raw) {
|
|
||||||
case 0:
|
|
||||||
return slow;
|
|
||||||
case 1:
|
|
||||||
return medium;
|
|
||||||
case 2:
|
|
||||||
return fast;
|
|
||||||
default:
|
|
||||||
throw Exception('Unexpected token: $raw for BitcoinTransactionPriority deserialize');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String get units => 'sat';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
var label = '';
|
|
||||||
|
|
||||||
switch (this) {
|
|
||||||
case BitcoinCashTransactionPriority.slow:
|
|
||||||
label = 'Slow ~24hrs'; // '${S.current.transaction_priority_slow} ~24hrs';
|
|
||||||
break;
|
|
||||||
case BitcoinCashTransactionPriority.medium:
|
|
||||||
label = 'Medium'; // S.current.transaction_priority_medium;
|
|
||||||
break;
|
|
||||||
case BitcoinCashTransactionPriority.fast:
|
|
||||||
label = 'Fast'; // S.current.transaction_priority_fast;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
String labelWithRate(int rate) => '${toString()} ($rate ${units}/byte)';
|
|
||||||
}
|
|
|
@ -1,14 +1,22 @@
|
||||||
|
import 'package:bitbox/bitbox.dart' as bitbox;
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_transaction_no_inputs_exception.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_transaction_wrong_balance_exception.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
||||||
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
import 'package:cw_bitcoin_cash/src/pending_bitcoin_cash_transaction.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:hex/hex.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
@ -91,69 +99,325 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PendingBitcoinTransaction> createTransaction(Object credentials,
|
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||||
[List<Object>? unspents, Object? wallet]) async {
|
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
||||||
|
|
||||||
// final utxoSigningData = await fetchBuildTxData(unspents as List<BitcoinUnspent>, wallet as BitcoinCashWalletBase);
|
const minAmount = 546;
|
||||||
// final builder = bitbox.Bitbox.transactionBuilder(testnet: false);
|
final inputs = <BitcoinUnspent>[];
|
||||||
// final utxosToUse = unspents as List<UnspentCash>;
|
var allInputsAmount = 0;
|
||||||
// final _wallet = wallet as BitcoinCashWallet;
|
final outputs = transactionCredentials.outputs;
|
||||||
// print('unspents: ${unspents.first.address}');
|
final hasMultiDestination = outputs.length > 1;
|
||||||
//
|
|
||||||
// List<bitbox.Utxo> _utxos = [];
|
if (unspentCoins.isEmpty) {
|
||||||
// for (var element in utxosToUse) {
|
await updateUnspent();
|
||||||
// _utxos.add(bitbox.Utxo(element.hash, element.vout,
|
|
||||||
// bitbox.BitcoinCash.fromSatoshi(element.value), element.value, 0, 1));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// final signatures = <Map>[];
|
|
||||||
// int totalBalance = 0;
|
|
||||||
//
|
|
||||||
// _utxos.forEach((bitbox.Utxo utxo) {
|
|
||||||
// // add the utxo as an input for the transaction
|
|
||||||
// builder.addInput(utxo.txid, utxo.vout);
|
|
||||||
//
|
|
||||||
// final ec = utxoSigningData.firstWhere((e) => e.utxo.hash == utxo.txid).keyPair!;
|
|
||||||
//
|
|
||||||
// final bitboxEC = bitbox.ECPair.fromWIF(ec.toWIF());
|
|
||||||
//
|
|
||||||
// // add a signature to the list to be used later
|
|
||||||
// signatures
|
|
||||||
// .add({"vin": signatures.length, "key_pair": bitboxEC, "original_amount": utxo.satoshis});
|
|
||||||
//
|
|
||||||
// totalBalance += utxo.satoshis;
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// // set an address to send the remaining balance to
|
|
||||||
// final outputAddress = "13Hvge9HRduGiXMfcJHFn6sggequmaKqsZ";
|
|
||||||
//
|
|
||||||
// // if there is an unspent balance, create a spending transaction
|
|
||||||
// if (totalBalance > 0 && outputAddress != "") {
|
|
||||||
// // calculate the fee based on number of inputs and one expected output
|
|
||||||
// final fee = bitbox.BitcoinCash.getByteCount(signatures.length, 1);
|
|
||||||
//
|
|
||||||
// // calculate how much balance will be left over to spend after the fee
|
|
||||||
// final sendAmount = totalBalance - fee;
|
|
||||||
//
|
|
||||||
// // add the output based on the address provided in the testing data
|
|
||||||
// builder.addOutput(outputAddress, sendAmount);
|
|
||||||
//
|
|
||||||
// // sign all inputs
|
|
||||||
// signatures.forEach((signature) {
|
|
||||||
// builder.sign(signature["vin"], signature["key_pair"], signature["original_amount"]);
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// // build the transaction
|
|
||||||
// final tx = builder.build();
|
|
||||||
//
|
|
||||||
// // broadcast the transaction
|
|
||||||
// final result = await electrumClient.broadcastTransaction(transactionRaw: tx.toHex());
|
|
||||||
//
|
|
||||||
// // Yatta!
|
|
||||||
// print("Transaction broadcasted: $result");
|
|
||||||
// }
|
|
||||||
return PendingBitcoinTransaction(bitcoin.Transaction(), type,
|
|
||||||
electrumClient: electrumClient, amount: 1, fee: 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (final utx in unspentCoins) {
|
||||||
|
if (utx.isSending) {
|
||||||
|
allInputsAmount += utx.value;
|
||||||
|
inputs.add(utx);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputs.isEmpty) {
|
||||||
|
throw BitcoinTransactionNoInputsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final int feeRate = transactionCredentials.feeRate != null
|
||||||
|
? transactionCredentials.feeRate!
|
||||||
|
: BitcoinCashFeeRates.feeRate(transactionCredentials.priority!);
|
||||||
|
|
||||||
|
final int allAmountFee =
|
||||||
|
bitbox.BitcoinCash.getByteCount(inputs.length, transactionCredentials.outputs.length) *
|
||||||
|
feeRate;
|
||||||
|
|
||||||
|
final allAmount = allInputsAmount - allAmountFee;
|
||||||
|
var credentialsAmount = 0;
|
||||||
|
var amount = 0;
|
||||||
|
var fee = 0;
|
||||||
|
|
||||||
|
if (hasMultiDestination) {
|
||||||
|
if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) {
|
||||||
|
throw BitcoinTransactionWrongBalanceException(currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
credentialsAmount = outputs.fold(0, (acc, value) {
|
||||||
|
return acc + value.formattedCryptoAmount!;
|
||||||
|
});
|
||||||
|
|
||||||
|
print(credentialsAmount);
|
||||||
|
|
||||||
|
if (allAmount - credentialsAmount < minAmount) {
|
||||||
|
throw BitcoinTransactionWrongBalanceException(currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
amount = credentialsAmount;
|
||||||
|
|
||||||
|
if (transactionCredentials.feeRate != null) {
|
||||||
|
fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount,
|
||||||
|
outputsCount: outputs.length + 1);
|
||||||
|
} else {
|
||||||
|
fee = calculateEstimatedFee(transactionCredentials.priority, amount,
|
||||||
|
outputsCount: outputs.length + 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final output = outputs.first;
|
||||||
|
credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0;
|
||||||
|
|
||||||
|
if (credentialsAmount > allAmount) {
|
||||||
|
throw BitcoinTransactionWrongBalanceException(currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
amount = output.sendAll || allAmount - credentialsAmount < minAmount
|
||||||
|
? allAmount
|
||||||
|
: credentialsAmount;
|
||||||
|
|
||||||
|
if (output.sendAll || amount == allAmount) {
|
||||||
|
fee = allAmountFee;
|
||||||
|
} else if (transactionCredentials.feeRate != null) {
|
||||||
|
fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount);
|
||||||
|
} else {
|
||||||
|
fee = calculateEstimatedFee(transactionCredentials.priority, amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fee == 0) {
|
||||||
|
throw BitcoinTransactionWrongBalanceException(currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
final totalAmount = amount + fee;
|
||||||
|
|
||||||
|
if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) {
|
||||||
|
throw BitcoinTransactionWrongBalanceException(currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
// final changeAddress = await walletAddresses.getChangeAddress(); TODO: BCH: implement change address
|
||||||
|
var leftAmount = totalAmount;
|
||||||
|
var totalInputAmount = 0;
|
||||||
|
|
||||||
|
inputs.clear();
|
||||||
|
|
||||||
|
for (final utx in unspentCoins) {
|
||||||
|
if (utx.isSending) {
|
||||||
|
leftAmount = leftAmount - utx.value;
|
||||||
|
totalInputAmount += utx.value;
|
||||||
|
inputs.add(utx);
|
||||||
|
|
||||||
|
if (leftAmount <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputs.isEmpty) {
|
||||||
|
throw BitcoinTransactionNoInputsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount <= 0 || totalInputAmount < totalAmount) {
|
||||||
|
throw BitcoinTransactionWrongBalanceException(currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
final builder = bitbox.Bitbox.transactionBuilder(testnet: false);
|
||||||
|
final _wallet = hd;
|
||||||
|
|
||||||
|
final utxoSigningData = await fetchBuildTxData(inputs, _wallet);
|
||||||
|
|
||||||
|
List<bitbox.Utxo> _utxos = [];
|
||||||
|
for (var element in inputs) {
|
||||||
|
_utxos.add(bitbox.Utxo(element.hash, element.vout,
|
||||||
|
bitbox.BitcoinCash.fromSatoshi(element.value), element.value, 0, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
final signatures = <Map>[];
|
||||||
|
int totalBalance = 0;
|
||||||
|
|
||||||
|
_utxos.forEach((bitbox.Utxo utxo) {
|
||||||
|
builder.addInput(utxo.txid, utxo.vout);
|
||||||
|
|
||||||
|
final ec = utxoSigningData.firstWhere((e) => e.utxo.hash == utxo.txid).keyPair!;
|
||||||
|
|
||||||
|
final bitboxEC = bitbox.ECPair.fromWIF(ec.toWIF());
|
||||||
|
|
||||||
|
signatures
|
||||||
|
.add({"vin": signatures.length, "key_pair": bitboxEC, "original_amount": utxo.satoshis});
|
||||||
|
|
||||||
|
totalBalance += utxo.satoshis;
|
||||||
|
});
|
||||||
|
|
||||||
|
outputs.forEach((item) {
|
||||||
|
final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount;
|
||||||
|
final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address;
|
||||||
|
builder.addOutput(outputAddress, outputAmount!);
|
||||||
|
});
|
||||||
|
|
||||||
|
signatures.forEach((signature) {
|
||||||
|
builder.sign(signature["vin"], signature["key_pair"], signature["original_amount"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// build the transaction
|
||||||
|
final tx = builder.build();
|
||||||
|
return PendingBitcoinCashTransaction(tx, type,
|
||||||
|
electrumClient: electrumClient, amount: amount, fee: fee)
|
||||||
|
..addListener((transaction) async {
|
||||||
|
transactionHistory.addOne(transaction);
|
||||||
|
await updateBalance();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<SigningData>> fetchBuildTxData(
|
||||||
|
List<BitcoinUnspent> utxosToUse, HDWallet wallet) async {
|
||||||
|
// Initialize the list to store signing data
|
||||||
|
List<SigningData> signingData = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Iterate over UTXOs to populate the addresses and fetch transaction details
|
||||||
|
for (var i = 0; i < utxosToUse.length; i++) {
|
||||||
|
final txid = utxosToUse[i].hash;
|
||||||
|
final tx = await electrumClient.getTransactionRaw(
|
||||||
|
hash: txid); //TODO: BCH: replace with getting from local storage if possible
|
||||||
|
|
||||||
|
// Iterate through transaction outputs to find corresponding addresses
|
||||||
|
for (final output in tx["vout"] as List) {
|
||||||
|
// Handle each transaction output
|
||||||
|
await handleTransactionOutput(output, utxosToUse[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine address type and create signing data object
|
||||||
|
signingData.add(SigningData(
|
||||||
|
derivePathType: DerivePathType.bch44,
|
||||||
|
utxo: utxosToUse[i])); //TODO: BCH: hardcoded DerivePathType.bch44
|
||||||
|
|
||||||
|
// Determine public key (pubKey) and Wallet Import Format (wif) here
|
||||||
|
// TODO: You need to implement logic to determine pubKey and wif
|
||||||
|
String? pubKey = wallet.pubKey;
|
||||||
|
String? wif = wallet.wif;
|
||||||
|
|
||||||
|
// Then call the preparePaymentData function
|
||||||
|
preparePaymentData(
|
||||||
|
signingData[i], pubKey, wif, bitcoincash); //TODO: BCH: hardcoded bitcoincash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the signing data for later use
|
||||||
|
return signingData;
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to handle each transaction output
|
||||||
|
Future<void> handleTransactionOutput(Map output, BitcoinUnspent utxo) async {
|
||||||
|
final n = output["n"];
|
||||||
|
if (n != null && n == utxo.vout) {
|
||||||
|
String address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||||
|
output["scriptPubKey"]["address"] as String;
|
||||||
|
|
||||||
|
// Convert to Cash Address format if needed
|
||||||
|
if (bitbox.Address.detectFormat(address) != bitbox.Address.formatCashAddr) {
|
||||||
|
try {
|
||||||
|
address = bitbox.Address.toCashAddress(address);
|
||||||
|
} catch (_) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update UTXO with the new address
|
||||||
|
utxo.updateAddress(address); // Make sure 'address' is mutable or create a method to update it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to prepare payment data
|
||||||
|
void preparePaymentData(
|
||||||
|
SigningData sd, String? pubKey, String? wif, bitcoin.NetworkType _network) {
|
||||||
|
if (wif != null && pubKey != null) {
|
||||||
|
PaymentData data; // Removed 'final' modifier
|
||||||
|
final Uint8List? redeemScript;
|
||||||
|
|
||||||
|
switch (sd.derivePathType) {
|
||||||
|
case DerivePathType.bip44:
|
||||||
|
case DerivePathType.bch44:
|
||||||
|
data = P2PKH(
|
||||||
|
data: PaymentData(
|
||||||
|
pubkey: Uint8List.fromList(HEX.decode(pubKey)),
|
||||||
|
),
|
||||||
|
network: _network,
|
||||||
|
).data;
|
||||||
|
redeemScript = null;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw Exception("DerivePathType unsupported");
|
||||||
|
}
|
||||||
|
|
||||||
|
final keyPair = ECPair.fromWIF(
|
||||||
|
wif,
|
||||||
|
network: _network,
|
||||||
|
);
|
||||||
|
|
||||||
|
sd.redeemScript = redeemScript;
|
||||||
|
sd.output = data.output;
|
||||||
|
sd.keyPair = keyPair;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SigningData {
|
||||||
|
SigningData({
|
||||||
|
required this.derivePathType,
|
||||||
|
required this.utxo,
|
||||||
|
this.output,
|
||||||
|
this.keyPair,
|
||||||
|
this.redeemScript,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DerivePathType derivePathType;
|
||||||
|
final BitcoinUnspent utxo;
|
||||||
|
Uint8List? output;
|
||||||
|
ECPair? keyPair;
|
||||||
|
Uint8List? redeemScript;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DerivePathType {
|
||||||
|
bip44,
|
||||||
|
bch44,
|
||||||
|
bip49,
|
||||||
|
bip84,
|
||||||
|
eth,
|
||||||
|
eCash44,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bitcoincash Network
|
||||||
|
final bitcoincash = bitcoin.NetworkType(
|
||||||
|
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||||
|
bech32: 'bc',
|
||||||
|
bip32: bitcoin.Bip32Type(public: 0x0488b21e, private: 0x0488ade4),
|
||||||
|
pubKeyHash: 0x00,
|
||||||
|
scriptHash: 0x05,
|
||||||
|
wif: 0x80);
|
||||||
|
|
||||||
|
final bitcoincashtestnet = bitcoin.NetworkType(
|
||||||
|
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||||
|
bech32: 'tb',
|
||||||
|
bip32: bitcoin.Bip32Type(public: 0x043587cf, private: 0x04358394),
|
||||||
|
pubKeyHash: 0x6f,
|
||||||
|
scriptHash: 0xc4,
|
||||||
|
wif: 0xef);
|
||||||
|
|
||||||
|
class BitcoinCashFeeRates {
|
||||||
|
static const int highPriority = 10;
|
||||||
|
static const int mediumPriority = 5;
|
||||||
|
static const int lowPriority = 1;
|
||||||
|
|
||||||
|
static int feeRate(BitcoinTransactionPriority priority) {
|
||||||
|
switch (priority) {
|
||||||
|
case BitcoinTransactionPriority.fast:
|
||||||
|
return highPriority;
|
||||||
|
case BitcoinTransactionPriority.medium:
|
||||||
|
return mediumPriority;
|
||||||
|
case BitcoinTransactionPriority.slow:
|
||||||
|
return lowPriority;
|
||||||
|
default:
|
||||||
|
throw Exception("Unknown priority level");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import 'package:cw_bitcoin/bitcoin_commit_transaction_exception.dart';
|
||||||
|
import 'package:bitbox/bitbox.dart' as bitbox;
|
||||||
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
|
class PendingBitcoinCashTransaction with PendingTransaction {
|
||||||
|
PendingBitcoinCashTransaction(this._tx, this.type,
|
||||||
|
{required this.electrumClient,
|
||||||
|
required this.amount,
|
||||||
|
required this.fee})
|
||||||
|
: _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
|
||||||
|
|
||||||
|
final WalletType type;
|
||||||
|
final bitbox.Transaction _tx;
|
||||||
|
final ElectrumClient electrumClient;
|
||||||
|
final int amount;
|
||||||
|
final int fee;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get id => _tx.getId();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hex => _tx.toHex();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get amountFormatted => bitcoinAmountToString(amount: amount);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get feeFormatted => bitcoinAmountToString(amount: fee);
|
||||||
|
|
||||||
|
final List<void Function(ElectrumTransactionInfo transaction)> _listeners;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> commit() async {
|
||||||
|
final result =
|
||||||
|
await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex());
|
||||||
|
|
||||||
|
if (result.isEmpty) {
|
||||||
|
throw BitcoinCommitTransactionException();
|
||||||
|
}
|
||||||
|
|
||||||
|
_listeners?.forEach((listener) => listener(transactionInfo()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void addListener(
|
||||||
|
void Function(ElectrumTransactionInfo transaction) listener) =>
|
||||||
|
_listeners.add(listener);
|
||||||
|
|
||||||
|
ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type,
|
||||||
|
id: id,
|
||||||
|
height: 0,
|
||||||
|
amount: amount,
|
||||||
|
direction: TransactionDirection.outgoing,
|
||||||
|
date: DateTime.now(),
|
||||||
|
isPending: true,
|
||||||
|
confirmations: 0,
|
||||||
|
fee: fee);
|
||||||
|
}
|
|
@ -50,7 +50,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<TransactionPriority> getBitcoinCashTransactionPriorities()
|
List<TransactionPriority> getBitcoinCashTransactionPriorities()
|
||||||
=> BitcoinCashTransactionPriority.all;
|
=> BitcoinTransactionPriority.all;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority deserializeBitcoinTransactionPriority(int raw)
|
TransactionPriority deserializeBitcoinTransactionPriority(int raw)
|
||||||
|
@ -62,7 +62,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority deserializeBitcoinCashTransactionPriority(int raw)
|
TransactionPriority deserializeBitcoinCashTransactionPriority(int raw)
|
||||||
=> BitcoinCashTransactionPriority.deserialize(raw: raw);
|
=> BitcoinTransactionPriority.deserialize(raw: raw);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int getFeeRate(Object wallet, TransactionPriority priority) {
|
int getFeeRate(Object wallet, TransactionPriority priority) {
|
||||||
|
|
|
@ -28,11 +28,11 @@ class CWBitcoinCash extends BitcoinCash {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority deserializeBitcoinCashTransactionPriority(int raw) =>
|
TransactionPriority deserializeBitcoinCashTransactionPriority(int raw) =>
|
||||||
BitcoinCashTransactionPriority.deserialize(raw: raw);
|
BitcoinTransactionPriority.deserialize(raw: raw);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority getDefaultTransactionPriority() => BitcoinCashTransactionPriority.medium;
|
TransactionPriority getDefaultTransactionPriority() => BitcoinTransactionPriority.medium;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<TransactionPriority> getTransactionPriorities() => BitcoinCashTransactionPriority.all;
|
List<TransactionPriority> getTransactionPriorities() => BitcoinTransactionPriority.all;
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,7 @@ class AddressValidator extends TextValidator {
|
||||||
case CryptoCurrency.dash:
|
case CryptoCurrency.dash:
|
||||||
case CryptoCurrency.eos:
|
case CryptoCurrency.eos:
|
||||||
case CryptoCurrency.bch:
|
case CryptoCurrency.bch:
|
||||||
|
return '[0-9a-zA-Z,:]';
|
||||||
case CryptoCurrency.bnb:
|
case CryptoCurrency.bnb:
|
||||||
return '[0-9a-zA-Z]';
|
return '[0-9a-zA-Z]';
|
||||||
case CryptoCurrency.ltc:
|
case CryptoCurrency.ltc:
|
||||||
|
@ -171,7 +172,7 @@ class AddressValidator extends TextValidator {
|
||||||
case CryptoCurrency.shib:
|
case CryptoCurrency.shib:
|
||||||
case CryptoCurrency.avaxc:
|
case CryptoCurrency.avaxc:
|
||||||
case CryptoCurrency.bch:
|
case CryptoCurrency.bch:
|
||||||
return [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44]; //TODO: BCH: replace with correct length
|
return [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,54,55,56]; //TODO: BCH: replace with correct length
|
||||||
case CryptoCurrency.bnb:
|
case CryptoCurrency.bnb:
|
||||||
return [42];
|
return [42];
|
||||||
case CryptoCurrency.ltc:
|
case CryptoCurrency.ltc:
|
||||||
|
|
|
@ -81,10 +81,8 @@ abstract class OutputBase with Store {
|
||||||
_amount = monero!.formatterMoneroParseAmount(amount: _cryptoAmount);
|
_amount = monero!.formatterMoneroParseAmount(amount: _cryptoAmount);
|
||||||
break;
|
break;
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
_amount =
|
|
||||||
bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount);
|
|
||||||
break;
|
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
|
case WalletType.bitcoinCash:
|
||||||
_amount =
|
_amount =
|
||||||
bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount);
|
bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount);
|
||||||
break;
|
break;
|
||||||
|
@ -116,7 +114,8 @@ abstract class OutputBase with Store {
|
||||||
_settingsStore.priority[_wallet.type]!, formattedCryptoAmount);
|
_settingsStore.priority[_wallet.type]!, formattedCryptoAmount);
|
||||||
|
|
||||||
if (_wallet.type == WalletType.bitcoin ||
|
if (_wallet.type == WalletType.bitcoin ||
|
||||||
_wallet.type == WalletType.litecoin) {
|
_wallet.type == WalletType.litecoin ||
|
||||||
|
_wallet.type == WalletType.bitcoinCash) {
|
||||||
return bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
|
return bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,6 +233,9 @@ abstract class OutputBase with Store {
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
maximumFractionDigits = 8;
|
maximumFractionDigits = 8;
|
||||||
break;
|
break;
|
||||||
|
case WalletType.bitcoinCash:
|
||||||
|
maximumFractionDigits = 8;
|
||||||
|
break;
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
maximumFractionDigits = 12;
|
maximumFractionDigits = 12;
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in a new issue