mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 19:49:22 +00:00
fix: some fixes for stored silent payment data, and fee usage
This commit is contained in:
parent
ea2161010f
commit
c74e335876
4 changed files with 149 additions and 50 deletions
|
@ -8,16 +8,30 @@ class BitcoinUnspent extends Unspent {
|
|||
: bitcoinAddressRecord = addressRecord,
|
||||
super(addressRecord.address, hash, value, vout, null);
|
||||
|
||||
factory BitcoinUnspent.fromJSON(BitcoinAddressRecord address, Map<String, dynamic> json) =>
|
||||
factory BitcoinUnspent.fromJSON(BitcoinAddressRecord? address, Map<String, dynamic> json) =>
|
||||
BitcoinUnspent(
|
||||
address,
|
||||
address ?? BitcoinAddressRecord.fromJSON(json['address_record'] as String),
|
||||
json['tx_hash'] as String,
|
||||
json['value'] as int,
|
||||
json['tx_pos'] as int,
|
||||
silentPaymentTweak: json['silent_payment_tweak'] as String?,
|
||||
type: json['type'] == null ? null : AddressType.values[json['type'] as int],
|
||||
type: json['type'] == null
|
||||
? null
|
||||
: AddressType.values.firstWhere((e) => e.toString() == json['type']),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{
|
||||
'address_record': bitcoinAddressRecord.toJSON(),
|
||||
'tx_hash': hash,
|
||||
'value': value,
|
||||
'tx_pos': vout,
|
||||
'silent_payment_tweak': silentPaymentTweak,
|
||||
'type': type.toString(),
|
||||
};
|
||||
return json;
|
||||
}
|
||||
|
||||
final BitcoinAddressRecord bitcoinAddressRecord;
|
||||
String? silentPaymentTweak;
|
||||
AddressType? type;
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
|||
import 'package:cw_bitcoin/address_from_output.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/format_amount.dart';
|
||||
|
@ -18,6 +19,8 @@ class ElectrumTransactionBundle {
|
|||
}
|
||||
|
||||
class ElectrumTransactionInfo extends TransactionInfo {
|
||||
BitcoinUnspent? unspent;
|
||||
|
||||
ElectrumTransactionInfo(this.type,
|
||||
{required String id,
|
||||
required int height,
|
||||
|
@ -26,7 +29,9 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
required TransactionDirection direction,
|
||||
required bool isPending,
|
||||
required DateTime date,
|
||||
required int confirmations}) {
|
||||
required int confirmations,
|
||||
String? to,
|
||||
this.unspent}) {
|
||||
this.id = id;
|
||||
this.height = height;
|
||||
this.amount = amount;
|
||||
|
@ -35,6 +40,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
this.date = date;
|
||||
this.isPending = isPending;
|
||||
this.confirmations = confirmations;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type,
|
||||
|
@ -168,7 +174,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
}
|
||||
|
||||
factory ElectrumTransactionInfo.fromJson(Map<String, dynamic> data, WalletType type) {
|
||||
return ElectrumTransactionInfo(type,
|
||||
return ElectrumTransactionInfo(
|
||||
type,
|
||||
id: data['id'] as String,
|
||||
height: data['height'] as int,
|
||||
amount: data['amount'] as int,
|
||||
|
@ -176,7 +183,12 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
|
||||
isPending: data['isPending'] as bool,
|
||||
confirmations: data['confirmations'] as int);
|
||||
confirmations: data['confirmations'] as int,
|
||||
to: data['to'] as String?,
|
||||
unspent: data['unspent'] != null
|
||||
? BitcoinUnspent.fromJSON(null, data['unspent'] as Map<String, dynamic>)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
final WalletType type;
|
||||
|
@ -220,6 +232,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
m['isPending'] = isPending;
|
||||
m['confirmations'] = confirmations;
|
||||
m['fee'] = fee;
|
||||
m['to'] = to;
|
||||
m['unspent'] = unspent?.toJson() ?? <String, dynamic>{};
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import 'dart:io';
|
|||
import 'dart:isolate';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
@ -165,10 +167,7 @@ abstract class ElectrumWalletBase
|
|||
startRefresh,
|
||||
ScanData(
|
||||
sendPort: receivePort.sendPort,
|
||||
scanPrivkeyCompressed:
|
||||
walletAddresses.primarySilentAddress!.scanPrivkey.toCompressedHex().fromHex,
|
||||
spendPubkeyCompressed:
|
||||
walletAddresses.primarySilentAddress!.spendPubkey.toCompressedHex().fromHex,
|
||||
primarySilentAddress: walletAddresses.primarySilentAddress!,
|
||||
networkType: networkType,
|
||||
height: height,
|
||||
chainTip: currentChainTip,
|
||||
|
@ -180,13 +179,31 @@ abstract class ElectrumWalletBase
|
|||
|
||||
await for (var message in receivePort) {
|
||||
if (message is BitcoinUnspent) {
|
||||
if (!unspentCoins.any((utx) =>
|
||||
utx.hash.contains(message.hash) &&
|
||||
utx.vout == message.vout &&
|
||||
utx.address.contains(message.address))) {
|
||||
unspentCoins.add(message);
|
||||
await _addCoinInfo(message);
|
||||
balance[currency] = await _fetchBalances();
|
||||
|
||||
if (unspentCoinsInfo.values.any((element) =>
|
||||
element.walletId.contains(id) &&
|
||||
element.hash.contains(message.hash) &&
|
||||
element.address.contains(message.address))) {
|
||||
_addCoinInfo(message);
|
||||
|
||||
await walletInfo.save();
|
||||
await save();
|
||||
}
|
||||
|
||||
balance[currency] = await _fetchBalances();
|
||||
}
|
||||
}
|
||||
|
||||
if (message is Map<String, ElectrumTransactionInfo>) {
|
||||
transactionHistory.addMany(message);
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
// check if is a SyncStatus type since "is SyncStatus" doesn't work here
|
||||
if (message is SyncResponse) {
|
||||
syncStatus = message.syncStatus;
|
||||
|
@ -268,7 +285,7 @@ abstract class ElectrumWalletBase
|
|||
throw BitcoinTransactionNoInputsException();
|
||||
}
|
||||
|
||||
final minAmount = networkType == bitcoin.testnet ? 0 : 546;
|
||||
final minAmount = 546;
|
||||
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
||||
final outputs = transactionCredentials.outputs;
|
||||
final hasMultiDestination = outputs.length > 1;
|
||||
|
@ -284,7 +301,7 @@ abstract class ElectrumWalletBase
|
|||
var fee = 0;
|
||||
|
||||
if (hasMultiDestination) {
|
||||
if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) {
|
||||
if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 1)) {
|
||||
throw BitcoinTransactionWrongBalanceException(currency);
|
||||
}
|
||||
|
||||
|
@ -324,15 +341,10 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
}
|
||||
|
||||
if (fee == 0 && networkType == bitcoin.bitcoin) {
|
||||
if (fee == 0) {
|
||||
throw BitcoinTransactionWrongBalanceException(currency);
|
||||
}
|
||||
|
||||
if (networkType == bitcoin.testnet) {
|
||||
fee += 50;
|
||||
amount -= 50;
|
||||
}
|
||||
|
||||
final totalAmount = amount + fee;
|
||||
|
||||
if ((totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) &&
|
||||
|
@ -656,6 +668,18 @@ abstract class ElectrumWalletBase
|
|||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||
|
||||
Future<void> updateUnspent() async {
|
||||
// Update unspents stored from scanned silent payment transactions
|
||||
transactionHistory.transactions.values.forEach((tx) {
|
||||
if (tx.unspent != null) {
|
||||
if (!unspentCoins.any((utx) =>
|
||||
utx.hash.contains(tx.unspent!.hash) &&
|
||||
utx.vout == tx.unspent!.vout &&
|
||||
utx.address.contains(tx.to!))) {
|
||||
unspentCoins.add(tx.unspent!);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
final unspent = await Future.wait(walletAddresses.addresses.map((address) => electrumClient
|
||||
.getListUnspentWithAddress(address.address, networkType)
|
||||
.then((unspent) => unspent.map((unspent) {
|
||||
|
@ -763,6 +787,7 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) {
|
||||
try {
|
||||
return fetchTransactionInfo(
|
||||
|
@ -776,12 +801,25 @@ abstract class ElectrumWalletBase
|
|||
return Future.value(null);
|
||||
}
|
||||
}));
|
||||
|
||||
transactionHistory.transactions.values.forEach((tx) {
|
||||
if (tx.direction == TransactionDirection.incoming &&
|
||||
!walletAddresses.addresses.any((addr) => tx.to?.contains(addr.address) ?? false))
|
||||
historiesWithDetails.add(tx);
|
||||
});
|
||||
|
||||
return historiesWithDetails
|
||||
.fold<Map<String, ElectrumTransactionInfo>>(<String, ElectrumTransactionInfo>{}, (acc, tx) {
|
||||
if (tx == null) {
|
||||
return acc;
|
||||
}
|
||||
acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx;
|
||||
|
||||
if (tx.to != null && tx.to!.isNotEmpty && acc[tx.id] != null) {
|
||||
final updatedConf = acc[tx.id]!;
|
||||
updatedConf.confirmations = walletInfo.restoreHeight - tx.height;
|
||||
acc[tx.id] = updatedConf;
|
||||
}
|
||||
return acc;
|
||||
});
|
||||
}
|
||||
|
@ -824,6 +862,7 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
await _chainTipUpdateSubject?.close();
|
||||
_chainTipUpdateSubject = electrumClient.chainTipUpdate();
|
||||
_chainTipUpdateSubject?.listen((_) async {
|
||||
|
@ -856,15 +895,18 @@ abstract class ElectrumWalletBase
|
|||
var totalConfirmed = 0;
|
||||
var totalUnconfirmed = 0;
|
||||
|
||||
// Add values from unspent coins that are not fetched by the address list
|
||||
// i.e. scanned silent payments
|
||||
unspentCoinsInfo.values.forEach((info) {
|
||||
unspentCoins.forEach((element) {
|
||||
if (element.hash == info.hash &&
|
||||
element.bitcoinAddressRecord.address == info.address &&
|
||||
element.value == info.value) {
|
||||
if (info.isFrozen) totalFrozen += element.value;
|
||||
if (element.bitcoinAddressRecord.silentPaymentTweak != null)
|
||||
if (element.bitcoinAddressRecord.silentPaymentTweak != null) {
|
||||
totalConfirmed += element.value;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -982,8 +1024,7 @@ Future<ElectrumTransactionBundle> getTransactionExpanded(
|
|||
|
||||
class ScanData {
|
||||
final SendPort sendPort;
|
||||
final Uint8List scanPrivkeyCompressed;
|
||||
final Uint8List spendPubkeyCompressed;
|
||||
final bitcoin.SilentPaymentReceiver primarySilentAddress;
|
||||
final int height;
|
||||
final String node;
|
||||
final bitcoin.NetworkType networkType;
|
||||
|
@ -994,8 +1035,7 @@ class ScanData {
|
|||
|
||||
ScanData({
|
||||
required this.sendPort,
|
||||
required this.scanPrivkeyCompressed,
|
||||
required this.spendPubkeyCompressed,
|
||||
required this.primarySilentAddress,
|
||||
required this.height,
|
||||
required this.node,
|
||||
required this.networkType,
|
||||
|
@ -1008,8 +1048,7 @@ class ScanData {
|
|||
factory ScanData.fromHeight(ScanData scanData, int newHeight) {
|
||||
return ScanData(
|
||||
sendPort: scanData.sendPort,
|
||||
scanPrivkeyCompressed: scanData.scanPrivkeyCompressed,
|
||||
spendPubkeyCompressed: scanData.spendPubkeyCompressed,
|
||||
primarySilentAddress: scanData.primarySilentAddress,
|
||||
height: newHeight,
|
||||
node: scanData.node,
|
||||
networkType: scanData.networkType,
|
||||
|
@ -1179,10 +1218,10 @@ Future<void> startRefresh(ScanData scanData) async {
|
|||
// break;
|
||||
// }
|
||||
|
||||
// final p2tr = bitcoin.P2trAddress(program: script.sublist(2).hex);
|
||||
// final address = p2tr.toAddress(scanData.networkType);
|
||||
final p2tr = bitcoin.P2trAddress(program: script.sublist(2).hex);
|
||||
final address = p2tr.toAddress(scanData.networkType);
|
||||
|
||||
// print(["Verifying taproot address:", address]);
|
||||
print(["Verifying taproot address:", address]);
|
||||
|
||||
outpointsByP2TRpubkey[script.sublist(2).hex] =
|
||||
bitcoin.Outpoint(txid: txid, index: i, value: output["value"] as int);
|
||||
|
@ -1198,8 +1237,10 @@ Future<void> startRefresh(ScanData scanData) async {
|
|||
final curve = bitcoin.getSecp256k1();
|
||||
|
||||
final result = bitcoin.scanOutputs(
|
||||
bitcoin.PrivateKey.fromHex(curve, scanData.scanPrivkeyCompressed.hex),
|
||||
bitcoin.PublicKey.fromHex(curve, scanData.spendPubkeyCompressed.hex),
|
||||
bitcoin.PrivateKey.fromHex(
|
||||
curve, scanData.primarySilentAddress.scanPrivkey.toCompressedHex()),
|
||||
bitcoin.PublicKey.fromHex(
|
||||
curve, scanData.primarySilentAddress.spendPubkey.toCompressedHex()),
|
||||
bitcoin.getSumInputPubKeys(pubkeys),
|
||||
outpointHash,
|
||||
outpointsByP2TRpubkey.keys.map((e) => e.fromHex).toList(),
|
||||
|
@ -1225,24 +1266,54 @@ Future<void> startRefresh(ScanData scanData) async {
|
|||
return;
|
||||
}
|
||||
|
||||
// found utxo for tx
|
||||
scanData.sendPort.send(BitcoinUnspent(
|
||||
final tweak = value[0];
|
||||
String? label;
|
||||
if (value.length > 1) label = value[1];
|
||||
|
||||
final unspent = BitcoinUnspent(
|
||||
BitcoinAddressRecord(
|
||||
bitcoin.P2trAddress(program: key, network: scanData.networkType)
|
||||
.toAddress(scanData.networkType),
|
||||
index: 0,
|
||||
isHidden: false,
|
||||
isHidden: true,
|
||||
isUsed: true,
|
||||
silentAddressLabel: null,
|
||||
silentPaymentTweak: value,
|
||||
silentPaymentTweak: tweak,
|
||||
type: bitcoin.AddressType.p2tr,
|
||||
),
|
||||
outpoint.txid,
|
||||
txid,
|
||||
outpoint.value!,
|
||||
outpoint.index,
|
||||
silentPaymentTweak: value,
|
||||
silentPaymentTweak: tweak,
|
||||
type: bitcoin.AddressType.p2tr,
|
||||
));
|
||||
);
|
||||
|
||||
// found utxo for tx, send unspent coin to main isolate
|
||||
scanData.sendPort.send(unspent);
|
||||
|
||||
// also send tx data for tx history
|
||||
scanData.sendPort.send({
|
||||
txid: ElectrumTransactionInfo(
|
||||
WalletType.bitcoin,
|
||||
id: txid,
|
||||
height: syncHeight,
|
||||
amount: outpoint.value!,
|
||||
fee: 0,
|
||||
direction: TransactionDirection.incoming,
|
||||
isPending: false,
|
||||
date:
|
||||
DateTime.fromMillisecondsSinceEpoch((blockJson["timestamp"] as int) * 1000),
|
||||
confirmations: currentChainTip - syncHeight,
|
||||
to: bitcoin.SilentPaymentAddress.createLabeledSilentPaymentAddress(
|
||||
scanData.primarySilentAddress.scanPubkey,
|
||||
scanData.primarySilentAddress.spendPubkey,
|
||||
label != null ? label.fromHex : "0".fromHex,
|
||||
hrp: scanData.primarySilentAddress.hrp,
|
||||
version: scanData.primarySilentAddress.version)
|
||||
.toString(),
|
||||
unspent: unspent,
|
||||
)
|
||||
});
|
||||
});
|
||||
} catch (_) {}
|
||||
}
|
||||
|
|
|
@ -743,7 +743,7 @@
|
|||
"unspent_change": "Mudar",
|
||||
"Block_remaining": "${status} bloco restante",
|
||||
"labeled_silent_addresses": "Endereços silenciosos rotulados",
|
||||
"use_testnet": "Use testNet",
|
||||
"use_testnet": "Use Testnet",
|
||||
"tor_connection": "Conexão Tor",
|
||||
"seed_hex_form": "Semente de carteira (forma hexadecimal)",
|
||||
"seedtype": "SeedType",
|
||||
|
|
Loading…
Reference in a new issue