mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-23 03:49:22 +00:00
WIP txV2 for bch for now
This commit is contained in:
parent
94fd8bc0ec
commit
45737084fd
5 changed files with 296 additions and 151 deletions
|
@ -1,28 +1,84 @@
|
|||
class InputV2 {
|
||||
final String scriptSigHex;
|
||||
final int sequence;
|
||||
final String txid;
|
||||
final int vout;
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
InputV2({
|
||||
required this.scriptSigHex,
|
||||
required this.sequence,
|
||||
required this.txid,
|
||||
required this.vout,
|
||||
});
|
||||
part 'input_v2.g.dart';
|
||||
|
||||
static InputV2 fromElectrumXJson(Map<String, dynamic> json) {
|
||||
try {
|
||||
return InputV2(
|
||||
scriptSigHex: json["scriptSig"]["hex"] as String,
|
||||
sequence: json["sequence"] as int,
|
||||
txid: json["txid"] as String,
|
||||
vout: json["vout"] as int);
|
||||
} catch (e) {
|
||||
throw Exception("Failed to parse InputV2 from $json");
|
||||
}
|
||||
@Embedded()
|
||||
class OutpointV2 {
|
||||
late final String txid;
|
||||
late final int vout;
|
||||
|
||||
OutpointV2();
|
||||
|
||||
static OutpointV2 isarCantDoRequiredInDefaultConstructor({
|
||||
required String txid,
|
||||
required int vout,
|
||||
}) =>
|
||||
OutpointV2()
|
||||
..vout = vout
|
||||
..txid = txid;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'OutpointV2(\n'
|
||||
' txid: $txid,\n'
|
||||
' vout: $vout,\n'
|
||||
')';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is OutpointV2 && other.txid == txid && other.vout == vout;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return Object.hash(
|
||||
txid.hashCode,
|
||||
vout.hashCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Embedded()
|
||||
class InputV2 {
|
||||
late final String? scriptSigHex;
|
||||
late final int? sequence;
|
||||
late final OutpointV2? outpoint;
|
||||
late final List<String> addresses;
|
||||
late final String valueStringSats;
|
||||
|
||||
late final String? coinbase;
|
||||
|
||||
late final String? witness;
|
||||
late final String? innerRedeemScriptAsm;
|
||||
|
||||
@ignore
|
||||
BigInt get value => BigInt.parse(valueStringSats);
|
||||
|
||||
InputV2();
|
||||
|
||||
static InputV2 isarCantDoRequiredInDefaultConstructor({
|
||||
required String? scriptSigHex,
|
||||
required int? sequence,
|
||||
required OutpointV2? outpoint,
|
||||
required List<String> addresses,
|
||||
required String valueStringSats,
|
||||
required String? witness,
|
||||
required String? innerRedeemScriptAsm,
|
||||
required String? coinbase,
|
||||
}) =>
|
||||
InputV2()
|
||||
..scriptSigHex = scriptSigHex
|
||||
..sequence = sequence
|
||||
..sequence = sequence
|
||||
..addresses = List.unmodifiable(addresses)
|
||||
..valueStringSats = valueStringSats
|
||||
..witness = witness
|
||||
..innerRedeemScriptAsm = innerRedeemScriptAsm
|
||||
..coinbase = coinbase;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
@ -30,16 +86,14 @@ class InputV2 {
|
|||
return other is InputV2 &&
|
||||
other.scriptSigHex == scriptSigHex &&
|
||||
other.sequence == sequence &&
|
||||
other.txid == txid &&
|
||||
other.vout == vout;
|
||||
other.outpoint == outpoint;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
scriptSigHex,
|
||||
sequence,
|
||||
txid,
|
||||
vout,
|
||||
outpoint,
|
||||
);
|
||||
|
||||
@override
|
||||
|
@ -47,8 +101,12 @@ class InputV2 {
|
|||
return 'InputV2(\n'
|
||||
' scriptSigHex: $scriptSigHex,\n'
|
||||
' sequence: $sequence,\n'
|
||||
' txid: $txid,\n'
|
||||
' vout: $vout,\n'
|
||||
' outpoint: $outpoint,\n'
|
||||
' addresses: $addresses,\n'
|
||||
' valueStringSats: $valueStringSats,\n'
|
||||
' coinbase: $coinbase,\n'
|
||||
' witness: $witness,\n'
|
||||
' innerRedeemScriptAsm: $innerRedeemScriptAsm,\n'
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,48 +1,83 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
part 'output_v2.g.dart';
|
||||
|
||||
@Embedded()
|
||||
class OutputV2 {
|
||||
final String scriptPubKeyHex;
|
||||
final String valueStringSats;
|
||||
late final String scriptPubKeyHex;
|
||||
late final String valueStringSats;
|
||||
late final List<String> addresses;
|
||||
|
||||
@ignore
|
||||
BigInt get value => BigInt.parse(valueStringSats);
|
||||
|
||||
OutputV2({
|
||||
required this.scriptPubKeyHex,
|
||||
required this.valueStringSats,
|
||||
});
|
||||
OutputV2();
|
||||
|
||||
// TODO: move this to a subclass based on coin since we don't know if the value will be sats or a decimal amount
|
||||
// For now assume 8 decimal places
|
||||
@Deprecated("See TODO and comments")
|
||||
static OutputV2 fromElectrumXJson(Map<String, dynamic> json) {
|
||||
static OutputV2 isarCantDoRequiredInDefaultConstructor({
|
||||
required String scriptPubKeyHex,
|
||||
required String valueStringSats,
|
||||
required List<String> addresses,
|
||||
}) =>
|
||||
OutputV2()
|
||||
..scriptPubKeyHex = scriptPubKeyHex
|
||||
..valueStringSats = valueStringSats
|
||||
..addresses = List.unmodifiable(addresses);
|
||||
|
||||
static OutputV2 fromElectrumXJson(
|
||||
Map<String, dynamic> json, {
|
||||
required int decimalPlaces,
|
||||
}) {
|
||||
try {
|
||||
final temp = Decimal.parse(json["value"].toString());
|
||||
if (temp < Decimal.zero) {
|
||||
throw Exception("Negative value found");
|
||||
List<String> addresses = [];
|
||||
|
||||
if (json["scriptPubKey"]?["addresses"] is List) {
|
||||
for (final e in json["scriptPubKey"]["addresses"] as List) {
|
||||
addresses.add(e as String);
|
||||
}
|
||||
} else if (json["scriptPubKey"]?["address"] is String) {
|
||||
addresses.add(json["scriptPubKey"]?["address"] as String);
|
||||
}
|
||||
|
||||
final String valueStringSats;
|
||||
if (temp.isInteger) {
|
||||
valueStringSats = temp.toString();
|
||||
} else {
|
||||
valueStringSats = temp.shift(8).toBigInt().toString();
|
||||
}
|
||||
|
||||
return OutputV2(
|
||||
return OutputV2.isarCantDoRequiredInDefaultConstructor(
|
||||
scriptPubKeyHex: json["scriptPubKey"]["hex"] as String,
|
||||
valueStringSats: valueStringSats,
|
||||
valueStringSats: parseOutputAmountString(
|
||||
json["value"].toString(),
|
||||
decimalPlaces: decimalPlaces,
|
||||
),
|
||||
addresses: addresses,
|
||||
);
|
||||
} catch (e) {
|
||||
throw Exception("Failed to parse OutputV2 from $json");
|
||||
}
|
||||
}
|
||||
|
||||
static String parseOutputAmountString(
|
||||
String amount, {
|
||||
required int decimalPlaces,
|
||||
}) {
|
||||
final temp = Decimal.parse(amount);
|
||||
if (temp < Decimal.zero) {
|
||||
throw Exception("Negative value found");
|
||||
}
|
||||
|
||||
final String valueStringSats;
|
||||
if (temp.isInteger) {
|
||||
valueStringSats = temp.toString();
|
||||
} else {
|
||||
valueStringSats = temp.shift(decimalPlaces).toBigInt().toString();
|
||||
}
|
||||
|
||||
return valueStringSats;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is OutputV2 &&
|
||||
other.scriptPubKeyHex == scriptPubKeyHex &&
|
||||
_listEquals(other.addresses, addresses) &&
|
||||
other.valueStringSats == valueStringSats;
|
||||
}
|
||||
|
||||
|
@ -57,6 +92,25 @@ class OutputV2 {
|
|||
return 'OutputV2(\n'
|
||||
' scriptPubKeyHex: $scriptPubKeyHex,\n'
|
||||
' value: $value,\n'
|
||||
' addresses: $addresses,\n'
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
||||
bool _listEquals<T, U>(List<T> a, List<U> b) {
|
||||
if (T != U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.length != b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,125 +1,67 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
|
||||
|
||||
part 'transaction_v2.g.dart';
|
||||
|
||||
@Collection()
|
||||
class TransactionV2 {
|
||||
final String hash;
|
||||
final Id id = Isar.autoIncrement;
|
||||
|
||||
@Index()
|
||||
final String walletId;
|
||||
|
||||
@Index(unique: true, composite: [CompositeIndex("walletId")])
|
||||
final String txid;
|
||||
|
||||
final int size;
|
||||
final int lockTime;
|
||||
final String hash;
|
||||
|
||||
final DateTime? blockTime;
|
||||
@Index()
|
||||
late final int timestamp;
|
||||
|
||||
final int? height;
|
||||
final String? blockHash;
|
||||
final int version;
|
||||
|
||||
final List<InputV2> inputs;
|
||||
final List<OutputV2> outputs;
|
||||
|
||||
TransactionV2({
|
||||
required this.walletId,
|
||||
required this.blockHash,
|
||||
required this.hash,
|
||||
required this.txid,
|
||||
required this.lockTime,
|
||||
required this.size,
|
||||
required this.blockTime,
|
||||
required this.timestamp,
|
||||
required this.height,
|
||||
required this.inputs,
|
||||
required this.outputs,
|
||||
required this.version,
|
||||
});
|
||||
|
||||
static TransactionV2 fromElectrumXJson(Map<String, dynamic> json) {
|
||||
try {
|
||||
final inputs = (json["vin"] as List).map(
|
||||
(e) => InputV2.fromElectrumXJson(
|
||||
Map<String, dynamic>.from(e as Map),
|
||||
),
|
||||
);
|
||||
final outputs = (json["vout"] as List).map(
|
||||
(e) => OutputV2.fromElectrumXJson(
|
||||
Map<String, dynamic>.from(e as Map),
|
||||
),
|
||||
);
|
||||
|
||||
final blockTimeUnix = json["blocktime"] as int?;
|
||||
DateTime? blockTime;
|
||||
if (blockTimeUnix != null) {
|
||||
blockTime = DateTime.fromMillisecondsSinceEpoch(
|
||||
blockTimeUnix * 1000,
|
||||
isUtc: true,
|
||||
);
|
||||
}
|
||||
|
||||
return TransactionV2(
|
||||
blockHash: json["blockhash"] as String?,
|
||||
hash: json["hash"] as String,
|
||||
txid: json["txid"] as String,
|
||||
lockTime: json["locktime"] as int,
|
||||
size: json["size"] as int,
|
||||
blockTime: blockTime,
|
||||
inputs: List.unmodifiable(inputs),
|
||||
outputs: List.unmodifiable(outputs),
|
||||
);
|
||||
} catch (e) {
|
||||
throw Exception(
|
||||
"Failed to parse TransactionV2 for txid=${json["txid"]}: $e",
|
||||
);
|
||||
}
|
||||
int getConfirmations(int currentChainHeight) {
|
||||
if (height == null || height! <= 0) return 0;
|
||||
return max(0, currentChainHeight - (height! - 1));
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is TransactionV2 &&
|
||||
other.hash == hash &&
|
||||
other.txid == txid &&
|
||||
other.size == size &&
|
||||
other.lockTime == lockTime &&
|
||||
other.blockTime == blockTime &&
|
||||
other.blockHash == blockHash &&
|
||||
_listEquals(other.inputs, inputs) &&
|
||||
_listEquals(other.outputs, outputs);
|
||||
bool isConfirmed(int currentChainHeight, int minimumConfirms) {
|
||||
final confirmations = getConfirmations(currentChainHeight);
|
||||
return confirmations >= minimumConfirms;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
hash,
|
||||
txid,
|
||||
size,
|
||||
lockTime,
|
||||
blockTime,
|
||||
blockHash,
|
||||
inputs,
|
||||
outputs,
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TransactionV2(\n'
|
||||
' walletId: $walletId,\n'
|
||||
' hash: $hash,\n'
|
||||
' txid: $txid,\n'
|
||||
' size: $size,\n'
|
||||
' lockTime: $lockTime,\n'
|
||||
' blockTime: $blockTime,\n'
|
||||
' timestamp: $timestamp,\n'
|
||||
' height: $height,\n'
|
||||
' blockHash: $blockHash,\n'
|
||||
' version: $version,\n'
|
||||
' inputs: $inputs,\n'
|
||||
' outputs: $outputs,\n'
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
||||
bool _listEquals<T, U>(List<T> a, List<U> b) {
|
||||
if (T != U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.length != b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:stackwallet/db/hive/db.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/providers/global/debug_service_provider.dart';
|
||||
|
@ -379,6 +380,9 @@ class HiddenSettings extends StatelessWidget {
|
|||
failovers: [],
|
||||
);
|
||||
|
||||
final ce =
|
||||
CachedElectrumX(electrumXClient: e);
|
||||
|
||||
final txids = [
|
||||
"", // cashTokenTxid
|
||||
"6a0444358bc41913c5b04a8dc06896053184b3641bc62502d18f954865b6ce1e", // normalTxid
|
||||
|
@ -400,15 +404,23 @@ class HiddenSettings extends StatelessWidget {
|
|||
// // await e.getTransaction(txHash: txids[2]);
|
||||
// // await p.parseBchTx(json3);
|
||||
//
|
||||
final json4 =
|
||||
await e.getTransaction(txHash: txids[3]);
|
||||
await p.parseBchTx(
|
||||
json4, "SLP TOKEN SEND TXID:");
|
||||
await p.getTransaction(
|
||||
txids[3],
|
||||
Coin.bitcoincash,
|
||||
"lol",
|
||||
ce,
|
||||
"SLP TOKEN SEND TXID:");
|
||||
await p.getTransaction(
|
||||
"009d31380d2dbfb5c91500c861d55b531a8b762b0abb19353db884548dbac8b6",
|
||||
Coin.bitcoincash,
|
||||
"lol",
|
||||
ce,
|
||||
"COINBASE TXID:");
|
||||
|
||||
final json5 =
|
||||
await e.getTransaction(txHash: txids[4]);
|
||||
await p.parseBchTx(
|
||||
json5, "SLP TOKEN GENESIS TXID:");
|
||||
// final json5 =
|
||||
// await e.getTransaction(txHash: txids[4]);
|
||||
// await p.parseBchTx(
|
||||
// json5, "SLP TOKEN GENESIS TXID:");
|
||||
} catch (e, s) {
|
||||
print("$e\n$s");
|
||||
}
|
||||
|
|
|
@ -12,6 +12,9 @@ import 'dart:convert';
|
|||
|
||||
import 'package:bip47/src/util.dart';
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
|
@ -25,15 +28,91 @@ class TT with ElectrumXParsing {
|
|||
}
|
||||
|
||||
mixin ElectrumXParsing {
|
||||
Future<TransactionV2> parseBchTx(
|
||||
Map<String, dynamic> json, [
|
||||
Future<TransactionV2> getTransaction(
|
||||
String txHash,
|
||||
Coin coin,
|
||||
String walletId,
|
||||
CachedElectrumX cachedElectrumX, [
|
||||
String? debugTitle,
|
||||
]) async {
|
||||
final jsonTx = await cachedElectrumX.getTransaction(
|
||||
txHash: txHash,
|
||||
coin: coin,
|
||||
);
|
||||
print("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
|
||||
util.Util.printJson(json, debugTitle);
|
||||
util.Util.printJson(jsonTx, debugTitle);
|
||||
print("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
|
||||
|
||||
return TransactionV2.fromElectrumXJson(json);
|
||||
// parse inputs
|
||||
final List<InputV2> inputs = [];
|
||||
for (final jsonInput in jsonTx["vin"] as List) {
|
||||
final map = Map<String, dynamic>.from(jsonInput as Map);
|
||||
|
||||
final List<String> addresses = [];
|
||||
String valueStringSats = "0";
|
||||
OutpointV2? outpoint;
|
||||
|
||||
final coinbase = map["coinbase"] as String?;
|
||||
|
||||
if (coinbase == null) {
|
||||
final txid = map["txid"] as String;
|
||||
final vout = map["vout"] as int;
|
||||
|
||||
final inputTx =
|
||||
await cachedElectrumX.getTransaction(txHash: txid, coin: coin);
|
||||
|
||||
final prevOutJson = Map<String, dynamic>.from(
|
||||
(inputTx["vout"] as List).firstWhere((e) => e["n"] == vout) as Map);
|
||||
|
||||
final prevOut = OutputV2.fromElectrumXJson(
|
||||
prevOutJson,
|
||||
decimalPlaces: coin.decimals,
|
||||
);
|
||||
|
||||
outpoint = OutpointV2.isarCantDoRequiredInDefaultConstructor(
|
||||
txid: txid,
|
||||
vout: vout,
|
||||
);
|
||||
valueStringSats = prevOut.valueStringSats;
|
||||
addresses.addAll(prevOut.addresses);
|
||||
}
|
||||
|
||||
final input = InputV2.isarCantDoRequiredInDefaultConstructor(
|
||||
scriptSigHex: map["scriptSig"]?["hex"] as String?,
|
||||
sequence: map["sequence"] as int?,
|
||||
outpoint: outpoint,
|
||||
valueStringSats: valueStringSats,
|
||||
addresses: addresses,
|
||||
witness: map["witness"] as String?,
|
||||
coinbase: coinbase,
|
||||
innerRedeemScriptAsm: map["innerRedeemscriptAsm"] as String?,
|
||||
);
|
||||
|
||||
inputs.add(input);
|
||||
}
|
||||
|
||||
// parse outputs
|
||||
final List<OutputV2> outputs = [];
|
||||
for (final outputJson in jsonTx["vout"] as List) {
|
||||
final output = OutputV2.fromElectrumXJson(
|
||||
Map<String, dynamic>.from(outputJson as Map),
|
||||
decimalPlaces: coin.decimals,
|
||||
);
|
||||
outputs.add(output);
|
||||
}
|
||||
|
||||
return TransactionV2(
|
||||
walletId: walletId,
|
||||
blockHash: jsonTx["blockhash"] as String?,
|
||||
hash: jsonTx["hash"] as String,
|
||||
txid: jsonTx["txid"] as String,
|
||||
height: jsonTx["height"] as int?,
|
||||
version: jsonTx["version"] as int,
|
||||
timestamp: jsonTx["blocktime"] as int? ??
|
||||
DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
|
||||
inputs: List.unmodifiable(inputs),
|
||||
outputs: List.unmodifiable(outputs),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Tuple2<Transaction, Address>> parseTransaction(
|
||||
|
|
Loading…
Reference in a new issue