mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-24 12:29:37 +00:00
Merge remote-tracking branch 'origin/fusion' into fusion
This commit is contained in:
commit
bafc4d302f
13 changed files with 1160 additions and 247 deletions
|
@ -13,6 +13,7 @@ import 'package:flutter_native_splash/cli_commands.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:stackwallet/exceptions/main_db/main_db_exception.dart';
|
import 'package:stackwallet/exceptions/main_db/main_db_exception.dart';
|
||||||
import 'package:stackwallet/models/isar/models/block_explorer.dart';
|
import 'package:stackwallet/models/isar/models/block_explorer.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||||
import 'package:stackwallet/models/isar/models/contact_entry.dart';
|
import 'package:stackwallet/models/isar/models/contact_entry.dart';
|
||||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||||
import 'package:stackwallet/models/isar/ordinal.dart';
|
import 'package:stackwallet/models/isar/ordinal.dart';
|
||||||
|
@ -57,6 +58,7 @@ class MainDB {
|
||||||
ContactEntrySchema,
|
ContactEntrySchema,
|
||||||
OrdinalSchema,
|
OrdinalSchema,
|
||||||
LelantusCoinSchema,
|
LelantusCoinSchema,
|
||||||
|
TransactionV2Schema,
|
||||||
],
|
],
|
||||||
directory: (await StackFileSystem.applicationIsarDirectory()).path,
|
directory: (await StackFileSystem.applicationIsarDirectory()).path,
|
||||||
// inspector: kDebugMode,
|
// inspector: kDebugMode,
|
||||||
|
@ -506,6 +508,35 @@ class MainDB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<int>> updateOrPutTransactionV2s(
|
||||||
|
List<TransactionV2> transactions,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
List<int> ids = [];
|
||||||
|
await isar.writeTxn(() async {
|
||||||
|
for (final tx in transactions) {
|
||||||
|
final storedTx = await isar.transactionV2s
|
||||||
|
.where()
|
||||||
|
.txidWalletIdEqualTo(tx.txid, tx.walletId)
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
|
Id id;
|
||||||
|
if (storedTx == null) {
|
||||||
|
id = await isar.transactionV2s.put(tx);
|
||||||
|
} else {
|
||||||
|
tx.id = storedTx.id;
|
||||||
|
await isar.transactionV2s.delete(storedTx.id);
|
||||||
|
id = await isar.transactionV2s.put(tx);
|
||||||
|
}
|
||||||
|
ids.add(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return ids;
|
||||||
|
} catch (e) {
|
||||||
|
throw MainDBException("failed updateOrPutAddresses: $transactions", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Ethereum =======================================================
|
// ========== Ethereum =======================================================
|
||||||
|
|
||||||
// eth contracts
|
// eth contracts
|
||||||
|
|
|
@ -251,5 +251,6 @@ enum TransactionSubType {
|
||||||
bip47Notification, // bip47 payment code notification transaction flag
|
bip47Notification, // bip47 payment code notification transaction flag
|
||||||
mint, // firo specific
|
mint, // firo specific
|
||||||
join, // firo specific
|
join, // firo specific
|
||||||
ethToken; // eth token
|
ethToken, // eth token
|
||||||
|
cashFusion;
|
||||||
}
|
}
|
||||||
|
|
|
@ -364,6 +364,7 @@ const _TransactionsubTypeEnumValueMap = {
|
||||||
'mint': 2,
|
'mint': 2,
|
||||||
'join': 3,
|
'join': 3,
|
||||||
'ethToken': 4,
|
'ethToken': 4,
|
||||||
|
'cashFusion': 5,
|
||||||
};
|
};
|
||||||
const _TransactionsubTypeValueEnumMap = {
|
const _TransactionsubTypeValueEnumMap = {
|
||||||
0: TransactionSubType.none,
|
0: TransactionSubType.none,
|
||||||
|
@ -371,6 +372,7 @@ const _TransactionsubTypeValueEnumMap = {
|
||||||
2: TransactionSubType.mint,
|
2: TransactionSubType.mint,
|
||||||
3: TransactionSubType.join,
|
3: TransactionSubType.join,
|
||||||
4: TransactionSubType.ethToken,
|
4: TransactionSubType.ethToken,
|
||||||
|
5: TransactionSubType.cashFusion,
|
||||||
};
|
};
|
||||||
const _TransactiontypeEnumValueMap = {
|
const _TransactiontypeEnumValueMap = {
|
||||||
'outgoing': 0,
|
'outgoing': 0,
|
||||||
|
|
|
@ -72,7 +72,7 @@ class InputV2 {
|
||||||
InputV2()
|
InputV2()
|
||||||
..scriptSigHex = scriptSigHex
|
..scriptSigHex = scriptSigHex
|
||||||
..sequence = sequence
|
..sequence = sequence
|
||||||
..sequence = sequence
|
..outpoint = outpoint
|
||||||
..addresses = List.unmodifiable(addresses)
|
..addresses = List.unmodifiable(addresses)
|
||||||
..valueStringSats = valueStringSats
|
..valueStringSats = valueStringSats
|
||||||
..witness = witness
|
..witness = witness
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.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/output_v2.dart';
|
||||||
|
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
|
||||||
part 'transaction_v2.g.dart';
|
part 'transaction_v2.g.dart';
|
||||||
|
|
||||||
@Collection()
|
@Collection()
|
||||||
class TransactionV2 {
|
class TransactionV2 {
|
||||||
final Id id = Isar.autoIncrement;
|
Id id = Isar.autoIncrement;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
final String walletId;
|
final String walletId;
|
||||||
|
@ -28,6 +31,12 @@ class TransactionV2 {
|
||||||
final List<InputV2> inputs;
|
final List<InputV2> inputs;
|
||||||
final List<OutputV2> outputs;
|
final List<OutputV2> outputs;
|
||||||
|
|
||||||
|
@enumerated
|
||||||
|
final TransactionType type;
|
||||||
|
|
||||||
|
@enumerated
|
||||||
|
final TransactionSubType subType;
|
||||||
|
|
||||||
TransactionV2({
|
TransactionV2({
|
||||||
required this.walletId,
|
required this.walletId,
|
||||||
required this.blockHash,
|
required this.blockHash,
|
||||||
|
@ -38,6 +47,8 @@ class TransactionV2 {
|
||||||
required this.inputs,
|
required this.inputs,
|
||||||
required this.outputs,
|
required this.outputs,
|
||||||
required this.version,
|
required this.version,
|
||||||
|
required this.type,
|
||||||
|
required this.subType,
|
||||||
});
|
});
|
||||||
|
|
||||||
int getConfirmations(int currentChainHeight) {
|
int getConfirmations(int currentChainHeight) {
|
||||||
|
@ -50,12 +61,32 @@ class TransactionV2 {
|
||||||
return confirmations >= minimumConfirms;
|
return confirmations >= minimumConfirms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Amount getFee({required Coin coin}) {
|
||||||
|
final inSum =
|
||||||
|
inputs.map((e) => e.value).reduce((value, element) => value += element);
|
||||||
|
final outSum = outputs
|
||||||
|
.map((e) => e.value)
|
||||||
|
.reduce((value, element) => value += element);
|
||||||
|
|
||||||
|
return Amount(rawValue: inSum - outSum, fractionDigits: coin.decimals);
|
||||||
|
}
|
||||||
|
|
||||||
|
Amount getAmount({required Coin coin}) {
|
||||||
|
final outSum = outputs
|
||||||
|
.map((e) => e.value)
|
||||||
|
.reduce((value, element) => value += element);
|
||||||
|
|
||||||
|
return Amount(rawValue: outSum, fractionDigits: coin.decimals);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'TransactionV2(\n'
|
return 'TransactionV2(\n'
|
||||||
' walletId: $walletId,\n'
|
' walletId: $walletId,\n'
|
||||||
' hash: $hash,\n'
|
' hash: $hash,\n'
|
||||||
' txid: $txid,\n'
|
' txid: $txid,\n'
|
||||||
|
' type: $type,\n'
|
||||||
|
' subType: $subType,\n'
|
||||||
' timestamp: $timestamp,\n'
|
' timestamp: $timestamp,\n'
|
||||||
' height: $height,\n'
|
' height: $height,\n'
|
||||||
' blockHash: $blockHash,\n'
|
' blockHash: $blockHash,\n'
|
||||||
|
@ -65,3 +96,12 @@ class TransactionV2 {
|
||||||
')';
|
')';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum TxDirection {
|
||||||
|
outgoing,
|
||||||
|
incoming;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TxType {
|
||||||
|
normal,
|
||||||
|
}
|
||||||
|
|
|
@ -44,23 +44,35 @@ const TransactionV2Schema = CollectionSchema(
|
||||||
type: IsarType.objectList,
|
type: IsarType.objectList,
|
||||||
target: r'OutputV2',
|
target: r'OutputV2',
|
||||||
),
|
),
|
||||||
r'timestamp': PropertySchema(
|
r'subType': PropertySchema(
|
||||||
id: 5,
|
id: 5,
|
||||||
|
name: r'subType',
|
||||||
|
type: IsarType.byte,
|
||||||
|
enumMap: _TransactionV2subTypeEnumValueMap,
|
||||||
|
),
|
||||||
|
r'timestamp': PropertySchema(
|
||||||
|
id: 6,
|
||||||
name: r'timestamp',
|
name: r'timestamp',
|
||||||
type: IsarType.long,
|
type: IsarType.long,
|
||||||
),
|
),
|
||||||
r'txid': PropertySchema(
|
r'txid': PropertySchema(
|
||||||
id: 6,
|
id: 7,
|
||||||
name: r'txid',
|
name: r'txid',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
|
r'type': PropertySchema(
|
||||||
|
id: 8,
|
||||||
|
name: r'type',
|
||||||
|
type: IsarType.byte,
|
||||||
|
enumMap: _TransactionV2typeEnumValueMap,
|
||||||
|
),
|
||||||
r'version': PropertySchema(
|
r'version': PropertySchema(
|
||||||
id: 7,
|
id: 9,
|
||||||
name: r'version',
|
name: r'version',
|
||||||
type: IsarType.long,
|
type: IsarType.long,
|
||||||
),
|
),
|
||||||
r'walletId': PropertySchema(
|
r'walletId': PropertySchema(
|
||||||
id: 8,
|
id: 10,
|
||||||
name: r'walletId',
|
name: r'walletId',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
)
|
)
|
||||||
|
@ -183,10 +195,12 @@ void _transactionV2Serialize(
|
||||||
OutputV2Schema.serialize,
|
OutputV2Schema.serialize,
|
||||||
object.outputs,
|
object.outputs,
|
||||||
);
|
);
|
||||||
writer.writeLong(offsets[5], object.timestamp);
|
writer.writeByte(offsets[5], object.subType.index);
|
||||||
writer.writeString(offsets[6], object.txid);
|
writer.writeLong(offsets[6], object.timestamp);
|
||||||
writer.writeLong(offsets[7], object.version);
|
writer.writeString(offsets[7], object.txid);
|
||||||
writer.writeString(offsets[8], object.walletId);
|
writer.writeByte(offsets[8], object.type.index);
|
||||||
|
writer.writeLong(offsets[9], object.version);
|
||||||
|
writer.writeString(offsets[10], object.walletId);
|
||||||
}
|
}
|
||||||
|
|
||||||
TransactionV2 _transactionV2Deserialize(
|
TransactionV2 _transactionV2Deserialize(
|
||||||
|
@ -213,11 +227,17 @@ TransactionV2 _transactionV2Deserialize(
|
||||||
OutputV2(),
|
OutputV2(),
|
||||||
) ??
|
) ??
|
||||||
[],
|
[],
|
||||||
timestamp: reader.readLong(offsets[5]),
|
subType:
|
||||||
txid: reader.readString(offsets[6]),
|
_TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[5])] ??
|
||||||
version: reader.readLong(offsets[7]),
|
TransactionSubType.none,
|
||||||
walletId: reader.readString(offsets[8]),
|
timestamp: reader.readLong(offsets[6]),
|
||||||
|
txid: reader.readString(offsets[7]),
|
||||||
|
type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[8])] ??
|
||||||
|
TransactionType.outgoing,
|
||||||
|
version: reader.readLong(offsets[9]),
|
||||||
|
walletId: reader.readString(offsets[10]),
|
||||||
);
|
);
|
||||||
|
object.id = id;
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,18 +271,54 @@ P _transactionV2DeserializeProp<P>(
|
||||||
) ??
|
) ??
|
||||||
[]) as P;
|
[]) as P;
|
||||||
case 5:
|
case 5:
|
||||||
return (reader.readLong(offset)) as P;
|
return (_TransactionV2subTypeValueEnumMap[
|
||||||
|
reader.readByteOrNull(offset)] ??
|
||||||
|
TransactionSubType.none) as P;
|
||||||
case 6:
|
case 6:
|
||||||
return (reader.readString(offset)) as P;
|
|
||||||
case 7:
|
|
||||||
return (reader.readLong(offset)) as P;
|
return (reader.readLong(offset)) as P;
|
||||||
|
case 7:
|
||||||
|
return (reader.readString(offset)) as P;
|
||||||
case 8:
|
case 8:
|
||||||
|
return (_TransactionV2typeValueEnumMap[reader.readByteOrNull(offset)] ??
|
||||||
|
TransactionType.outgoing) as P;
|
||||||
|
case 9:
|
||||||
|
return (reader.readLong(offset)) as P;
|
||||||
|
case 10:
|
||||||
return (reader.readString(offset)) as P;
|
return (reader.readString(offset)) as P;
|
||||||
default:
|
default:
|
||||||
throw IsarError('Unknown property with id $propertyId');
|
throw IsarError('Unknown property with id $propertyId');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _TransactionV2subTypeEnumValueMap = {
|
||||||
|
'none': 0,
|
||||||
|
'bip47Notification': 1,
|
||||||
|
'mint': 2,
|
||||||
|
'join': 3,
|
||||||
|
'ethToken': 4,
|
||||||
|
'cashFusion': 5,
|
||||||
|
};
|
||||||
|
const _TransactionV2subTypeValueEnumMap = {
|
||||||
|
0: TransactionSubType.none,
|
||||||
|
1: TransactionSubType.bip47Notification,
|
||||||
|
2: TransactionSubType.mint,
|
||||||
|
3: TransactionSubType.join,
|
||||||
|
4: TransactionSubType.ethToken,
|
||||||
|
5: TransactionSubType.cashFusion,
|
||||||
|
};
|
||||||
|
const _TransactionV2typeEnumValueMap = {
|
||||||
|
'outgoing': 0,
|
||||||
|
'incoming': 1,
|
||||||
|
'sentToSelf': 2,
|
||||||
|
'unknown': 3,
|
||||||
|
};
|
||||||
|
const _TransactionV2typeValueEnumMap = {
|
||||||
|
0: TransactionType.outgoing,
|
||||||
|
1: TransactionType.incoming,
|
||||||
|
2: TransactionType.sentToSelf,
|
||||||
|
3: TransactionType.unknown,
|
||||||
|
};
|
||||||
|
|
||||||
Id _transactionV2GetId(TransactionV2 object) {
|
Id _transactionV2GetId(TransactionV2 object) {
|
||||||
return object.id;
|
return object.id;
|
||||||
}
|
}
|
||||||
|
@ -272,7 +328,9 @@ List<IsarLinkBase<dynamic>> _transactionV2GetLinks(TransactionV2 object) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _transactionV2Attach(
|
void _transactionV2Attach(
|
||||||
IsarCollection<dynamic> col, Id id, TransactionV2 object) {}
|
IsarCollection<dynamic> col, Id id, TransactionV2 object) {
|
||||||
|
object.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
extension TransactionV2ByIndex on IsarCollection<TransactionV2> {
|
extension TransactionV2ByIndex on IsarCollection<TransactionV2> {
|
||||||
Future<TransactionV2?> getByTxidWalletId(String txid, String walletId) {
|
Future<TransactionV2?> getByTxidWalletId(String txid, String walletId) {
|
||||||
|
@ -1275,6 +1333,62 @@ extension TransactionV2QueryFilter
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||||
|
subTypeEqualTo(TransactionSubType value) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'subType',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||||
|
subTypeGreaterThan(
|
||||||
|
TransactionSubType value, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
include: include,
|
||||||
|
property: r'subType',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||||
|
subTypeLessThan(
|
||||||
|
TransactionSubType value, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.lessThan(
|
||||||
|
include: include,
|
||||||
|
property: r'subType',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||||
|
subTypeBetween(
|
||||||
|
TransactionSubType lower,
|
||||||
|
TransactionSubType upper, {
|
||||||
|
bool includeLower = true,
|
||||||
|
bool includeUpper = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.between(
|
||||||
|
property: r'subType',
|
||||||
|
lower: lower,
|
||||||
|
includeLower: includeLower,
|
||||||
|
upper: upper,
|
||||||
|
includeUpper: includeUpper,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||||
timestampEqualTo(int value) {
|
timestampEqualTo(int value) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
@ -1466,6 +1580,61 @@ extension TransactionV2QueryFilter
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition> typeEqualTo(
|
||||||
|
TransactionType value) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'type',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||||
|
typeGreaterThan(
|
||||||
|
TransactionType value, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
include: include,
|
||||||
|
property: r'type',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||||
|
typeLessThan(
|
||||||
|
TransactionType value, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.lessThan(
|
||||||
|
include: include,
|
||||||
|
property: r'type',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition> typeBetween(
|
||||||
|
TransactionType lower,
|
||||||
|
TransactionType upper, {
|
||||||
|
bool includeLower = true,
|
||||||
|
bool includeUpper = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.between(
|
||||||
|
property: r'type',
|
||||||
|
lower: lower,
|
||||||
|
includeLower: includeLower,
|
||||||
|
upper: upper,
|
||||||
|
includeUpper: includeUpper,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||||
versionEqualTo(int value) {
|
versionEqualTo(int value) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
@ -1718,6 +1887,18 @@ extension TransactionV2QuerySortBy
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> sortBySubType() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'subType', Sort.asc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> sortBySubTypeDesc() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'subType', Sort.desc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> sortByTimestamp() {
|
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> sortByTimestamp() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'timestamp', Sort.asc);
|
return query.addSortBy(r'timestamp', Sort.asc);
|
||||||
|
@ -1743,6 +1924,18 @@ extension TransactionV2QuerySortBy
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> sortByType() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'type', Sort.asc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> sortByTypeDesc() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'type', Sort.desc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> sortByVersion() {
|
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> sortByVersion() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'version', Sort.asc);
|
return query.addSortBy(r'version', Sort.asc);
|
||||||
|
@ -1820,6 +2013,18 @@ extension TransactionV2QuerySortThenBy
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> thenBySubType() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'subType', Sort.asc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> thenBySubTypeDesc() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'subType', Sort.desc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> thenByTimestamp() {
|
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> thenByTimestamp() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'timestamp', Sort.asc);
|
return query.addSortBy(r'timestamp', Sort.asc);
|
||||||
|
@ -1845,6 +2050,18 @@ extension TransactionV2QuerySortThenBy
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> thenByType() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'type', Sort.asc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> thenByTypeDesc() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'type', Sort.desc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> thenByVersion() {
|
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> thenByVersion() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'version', Sort.asc);
|
return query.addSortBy(r'version', Sort.asc);
|
||||||
|
@ -1893,6 +2110,12 @@ extension TransactionV2QueryWhereDistinct
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QDistinct> distinctBySubType() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addDistinctBy(r'subType');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<TransactionV2, TransactionV2, QDistinct> distinctByTimestamp() {
|
QueryBuilder<TransactionV2, TransactionV2, QDistinct> distinctByTimestamp() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addDistinctBy(r'timestamp');
|
return query.addDistinctBy(r'timestamp');
|
||||||
|
@ -1906,6 +2129,12 @@ extension TransactionV2QueryWhereDistinct
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionV2, QDistinct> distinctByType() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addDistinctBy(r'type');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<TransactionV2, TransactionV2, QDistinct> distinctByVersion() {
|
QueryBuilder<TransactionV2, TransactionV2, QDistinct> distinctByVersion() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addDistinctBy(r'version');
|
return query.addDistinctBy(r'version');
|
||||||
|
@ -1960,6 +2189,13 @@ extension TransactionV2QueryProperty
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionSubType, QQueryOperations>
|
||||||
|
subTypeProperty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addPropertyName(r'subType');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<TransactionV2, int, QQueryOperations> timestampProperty() {
|
QueryBuilder<TransactionV2, int, QQueryOperations> timestampProperty() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addPropertyName(r'timestamp');
|
return query.addPropertyName(r'timestamp');
|
||||||
|
@ -1972,6 +2208,13 @@ extension TransactionV2QueryProperty
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<TransactionV2, TransactionType, QQueryOperations>
|
||||||
|
typeProperty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addPropertyName(r'type');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<TransactionV2, int, QQueryOperations> versionProperty() {
|
QueryBuilder<TransactionV2, int, QQueryOperations> versionProperty() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addPropertyName(r'version');
|
return query.addPropertyName(r'version');
|
||||||
|
|
588
lib/pages/wallet_view/sub_widgets/transaction_v2_list.dart
Normal file
588
lib/pages/wallet_view/sub_widgets/transaction_v2_list.dart
Normal file
|
@ -0,0 +1,588 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Stack Wallet.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Cypher Stack
|
||||||
|
* All Rights Reserved.
|
||||||
|
* The code is distributed under GPLv3 license, see LICENSE file for details.
|
||||||
|
* Generated by Cypher Stack on 2023-10-19
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:isar/isar.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/pages/exchange_view/trade_details_view.dart';
|
||||||
|
import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart';
|
||||||
|
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
|
||||||
|
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
||||||
|
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
||||||
|
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||||
|
import 'package:stackwallet/providers/global/locale_provider.dart';
|
||||||
|
import 'package:stackwallet/providers/global/prefs_provider.dart';
|
||||||
|
import 'package:stackwallet/providers/global/price_provider.dart';
|
||||||
|
import 'package:stackwallet/providers/global/trades_service_provider.dart';
|
||||||
|
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||||
|
import 'package:stackwallet/route_generator.dart';
|
||||||
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||||
|
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
|
||||||
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
import 'package:stackwallet/utilities/format.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||||
|
import 'package:stackwallet/widgets/trade_card.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
class TransactionsV2List extends ConsumerStatefulWidget {
|
||||||
|
const TransactionsV2List({
|
||||||
|
Key? key,
|
||||||
|
required this.walletId,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String walletId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<TransactionsV2List> createState() => _TransactionsV2ListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TxListItem extends ConsumerWidget {
|
||||||
|
const _TxListItem({
|
||||||
|
super.key,
|
||||||
|
required this.tx,
|
||||||
|
this.radius,
|
||||||
|
required this.coin,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TransactionV2 tx;
|
||||||
|
final BorderRadius? radius;
|
||||||
|
final Coin coin;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final matchingTrades = ref
|
||||||
|
.read(tradesServiceProvider)
|
||||||
|
.trades
|
||||||
|
.where((e) => e.payInTxid == tx.txid || e.payOutTxid == tx.txid);
|
||||||
|
|
||||||
|
if (tx.type == TransactionType.outgoing && matchingTrades.isNotEmpty) {
|
||||||
|
final trade = matchingTrades.first;
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||||
|
borderRadius: radius,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
TransactionCardV2(
|
||||||
|
key: UniqueKey(),
|
||||||
|
transaction: tx,
|
||||||
|
),
|
||||||
|
TradeCard(
|
||||||
|
key: Key(tx.txid +
|
||||||
|
tx.type.name +
|
||||||
|
tx.hashCode.toString() +
|
||||||
|
trade.uuid), //
|
||||||
|
trade: trade,
|
||||||
|
onTap: () async {
|
||||||
|
if (Util.isDesktop) {
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => Navigator(
|
||||||
|
initialRoute: TradeDetailsView.routeName,
|
||||||
|
onGenerateRoute: RouteGenerator.generateRoute,
|
||||||
|
onGenerateInitialRoutes: (_, __) {
|
||||||
|
return [
|
||||||
|
FadePageRoute(
|
||||||
|
DesktopDialog(
|
||||||
|
maxHeight: null,
|
||||||
|
maxWidth: 580,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 32,
|
||||||
|
bottom: 16,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Trade details",
|
||||||
|
style: STextStyles.desktopH3(context),
|
||||||
|
),
|
||||||
|
DesktopDialogCloseButton(
|
||||||
|
onPressedOverride: Navigator.of(
|
||||||
|
context,
|
||||||
|
rootNavigator: true,
|
||||||
|
).pop,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: TradeDetailsView(
|
||||||
|
tradeId: trade.tradeId,
|
||||||
|
// TODO
|
||||||
|
// transactionIfSentFromStack: tx,
|
||||||
|
transactionIfSentFromStack: null,
|
||||||
|
walletName: ref.watch(
|
||||||
|
walletsChangeNotifierProvider.select(
|
||||||
|
(value) => value
|
||||||
|
.getManager(tx.walletId)
|
||||||
|
.walletName,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
walletId: tx.walletId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const RouteSettings(
|
||||||
|
name: TradeDetailsView.routeName,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
unawaited(
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
TradeDetailsView.routeName,
|
||||||
|
arguments: Tuple4(
|
||||||
|
trade.tradeId,
|
||||||
|
tx,
|
||||||
|
tx.walletId,
|
||||||
|
ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.getManager(tx.walletId)
|
||||||
|
.walletName,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||||
|
borderRadius: radius,
|
||||||
|
),
|
||||||
|
child: TransactionCardV2(
|
||||||
|
// this may mess with combined firo transactions
|
||||||
|
key: UniqueKey(),
|
||||||
|
transaction: tx,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TransactionsV2ListState extends ConsumerState<TransactionsV2List> {
|
||||||
|
bool _hasLoaded = false;
|
||||||
|
List<TransactionV2> _transactions = [];
|
||||||
|
|
||||||
|
BorderRadius get _borderRadiusFirst {
|
||||||
|
return BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
topRight: Radius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BorderRadius get _borderRadiusLast {
|
||||||
|
return BorderRadius.only(
|
||||||
|
bottomLeft: Radius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
bottomRight: Radius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final manager = ref.watch(walletsChangeNotifierProvider
|
||||||
|
.select((value) => value.getManager(widget.walletId)));
|
||||||
|
|
||||||
|
return FutureBuilder(
|
||||||
|
future: ref
|
||||||
|
.watch(mainDBProvider)
|
||||||
|
.isar
|
||||||
|
.transactionV2s
|
||||||
|
.where()
|
||||||
|
.walletIdEqualTo(widget.walletId)
|
||||||
|
.sortByTimestampDesc()
|
||||||
|
.findAll(),
|
||||||
|
builder: (fbContext, AsyncSnapshot<List<TransactionV2>> snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done &&
|
||||||
|
snapshot.hasData) {
|
||||||
|
_transactions = snapshot.data!;
|
||||||
|
_hasLoaded = true;
|
||||||
|
}
|
||||||
|
if (!_hasLoaded) {
|
||||||
|
return const Column(
|
||||||
|
children: [
|
||||||
|
Spacer(),
|
||||||
|
Center(
|
||||||
|
child: LoadingIndicator(
|
||||||
|
height: 50,
|
||||||
|
width: 50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Spacer(
|
||||||
|
flex: 4,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (_transactions.isEmpty) {
|
||||||
|
return const NoTransActionsFound();
|
||||||
|
} else {
|
||||||
|
_transactions.sort((a, b) => b.timestamp - a.timestamp);
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
final managerProvider = ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.getManagerProvider(widget.walletId);
|
||||||
|
if (!ref.read(managerProvider).isRefreshing) {
|
||||||
|
unawaited(ref.read(managerProvider).refresh());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Util.isDesktop
|
||||||
|
? ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
BorderRadius? radius;
|
||||||
|
if (_transactions.length == 1) {
|
||||||
|
radius = BorderRadius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
);
|
||||||
|
} else if (index == _transactions.length - 1) {
|
||||||
|
radius = _borderRadiusLast;
|
||||||
|
} else if (index == 0) {
|
||||||
|
radius = _borderRadiusFirst;
|
||||||
|
}
|
||||||
|
final tx = _transactions[index];
|
||||||
|
return _TxListItem(
|
||||||
|
tx: tx,
|
||||||
|
coin: manager.coin,
|
||||||
|
radius: radius,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (context, index) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 2,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.background,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: _transactions.length,
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
itemCount: _transactions.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
BorderRadius? radius;
|
||||||
|
bool shouldWrap = false;
|
||||||
|
if (_transactions.length == 1) {
|
||||||
|
radius = BorderRadius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
);
|
||||||
|
} else if (index == _transactions.length - 1) {
|
||||||
|
radius = _borderRadiusLast;
|
||||||
|
shouldWrap = true;
|
||||||
|
} else if (index == 0) {
|
||||||
|
radius = _borderRadiusFirst;
|
||||||
|
}
|
||||||
|
final tx = _transactions[index];
|
||||||
|
if (shouldWrap) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
_TxListItem(
|
||||||
|
tx: tx,
|
||||||
|
coin: manager.coin,
|
||||||
|
radius: radius,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: WalletView.navBarHeight + 14,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return _TxListItem(
|
||||||
|
tx: tx,
|
||||||
|
coin: manager.coin,
|
||||||
|
radius: radius,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TransactionCardV2 extends ConsumerStatefulWidget {
|
||||||
|
const TransactionCardV2({
|
||||||
|
Key? key,
|
||||||
|
required this.transaction,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final TransactionV2 transaction;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<TransactionCardV2> createState() => _TransactionCardStateV2();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TransactionCardStateV2 extends ConsumerState<TransactionCardV2> {
|
||||||
|
late final TransactionV2 _transaction;
|
||||||
|
late final String walletId;
|
||||||
|
late final String prefix;
|
||||||
|
late final String unit;
|
||||||
|
late final Coin coin;
|
||||||
|
late final TransactionType txType;
|
||||||
|
|
||||||
|
String whatIsIt(
|
||||||
|
TransactionType type,
|
||||||
|
Coin coin,
|
||||||
|
int currentHeight,
|
||||||
|
) {
|
||||||
|
final confirmedStatus = _transaction.isConfirmed(
|
||||||
|
currentHeight,
|
||||||
|
coin.requiredConfirmations,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (type == TransactionType.incoming) {
|
||||||
|
// if (_transaction.isMinting) {
|
||||||
|
// return "Minting";
|
||||||
|
// } else
|
||||||
|
if (confirmedStatus) {
|
||||||
|
return "Received";
|
||||||
|
} else {
|
||||||
|
return "Receiving";
|
||||||
|
}
|
||||||
|
} else if (type == TransactionType.outgoing) {
|
||||||
|
if (confirmedStatus) {
|
||||||
|
return "Sent";
|
||||||
|
} else {
|
||||||
|
return "Sending";
|
||||||
|
}
|
||||||
|
} else if (type == TransactionType.sentToSelf) {
|
||||||
|
return "Sent to self";
|
||||||
|
} else {
|
||||||
|
return type.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_transaction = widget.transaction;
|
||||||
|
walletId = _transaction.walletId;
|
||||||
|
|
||||||
|
if (Util.isDesktop) {
|
||||||
|
if (_transaction.type == TransactionType.outgoing) {
|
||||||
|
prefix = "-";
|
||||||
|
} else if (_transaction.type == TransactionType.incoming) {
|
||||||
|
prefix = "+";
|
||||||
|
} else {
|
||||||
|
prefix = "";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prefix = "";
|
||||||
|
}
|
||||||
|
coin = ref.read(walletsChangeNotifierProvider).getManager(walletId).coin;
|
||||||
|
|
||||||
|
unit = coin.ticker;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final locale = ref.watch(
|
||||||
|
localeServiceChangeNotifierProvider.select((value) => value.locale));
|
||||||
|
|
||||||
|
final baseCurrency = ref
|
||||||
|
.watch(prefsChangeNotifierProvider.select((value) => value.currency));
|
||||||
|
|
||||||
|
final price = ref
|
||||||
|
.watch(priceAnd24hChangeNotifierProvider
|
||||||
|
.select((value) => value.getPrice(coin)))
|
||||||
|
.item1;
|
||||||
|
|
||||||
|
final currentHeight = ref.watch(walletsChangeNotifierProvider
|
||||||
|
.select((value) => value.getManager(walletId).currentHeight));
|
||||||
|
|
||||||
|
final amount = _transaction.getAmount(coin: coin);
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||||
|
elevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(Constants.size.circularBorderRadius),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
child: RawMaterialButton(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
if (Util.isDesktop) {
|
||||||
|
// TODO
|
||||||
|
// await showDialog<void>(
|
||||||
|
// context: context,
|
||||||
|
// builder: (context) => DesktopDialog(
|
||||||
|
// maxHeight: MediaQuery.of(context).size.height - 64,
|
||||||
|
// maxWidth: 580,
|
||||||
|
// child: TransactionDetailsView(
|
||||||
|
// transaction: _transaction,
|
||||||
|
// coin: coin,
|
||||||
|
// walletId: walletId,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
} else {
|
||||||
|
unawaited(
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
TransactionDetailsView.routeName,
|
||||||
|
arguments: Tuple3(
|
||||||
|
_transaction,
|
||||||
|
coin,
|
||||||
|
walletId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
TxIcon(
|
||||||
|
transaction: _transaction,
|
||||||
|
coin: coin,
|
||||||
|
currentHeight: currentHeight,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 14,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(
|
||||||
|
whatIsIt(
|
||||||
|
_transaction.type,
|
||||||
|
coin,
|
||||||
|
currentHeight,
|
||||||
|
),
|
||||||
|
style: STextStyles.itemSubtitle12(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Builder(
|
||||||
|
builder: (_) {
|
||||||
|
return Text(
|
||||||
|
"$prefix${ref.watch(pAmountFormatter(coin)).format(amount)}",
|
||||||
|
style: STextStyles.itemSubtitle12(context),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 4,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(
|
||||||
|
Format.extractDateFrom(_transaction.timestamp),
|
||||||
|
style: STextStyles.label(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (ref.watch(prefsChangeNotifierProvider
|
||||||
|
.select((value) => value.externalCalls)))
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
if (ref.watch(prefsChangeNotifierProvider
|
||||||
|
.select((value) => value.externalCalls)))
|
||||||
|
Flexible(
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Builder(
|
||||||
|
builder: (_) {
|
||||||
|
return Text(
|
||||||
|
"$prefix${Amount.fromDecimal(
|
||||||
|
amount.decimal * price,
|
||||||
|
fractionDigits: 2,
|
||||||
|
).fiatString(
|
||||||
|
locale: locale,
|
||||||
|
)} $baseCurrency",
|
||||||
|
style: STextStyles.label(context),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ import 'dart:io';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.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/models/isar/models/isar_models.dart';
|
||||||
import 'package:stackwallet/models/isar/stack_theme.dart';
|
import 'package:stackwallet/models/isar/stack_theme.dart';
|
||||||
import 'package:stackwallet/themes/theme_providers.dart';
|
import 'package:stackwallet/themes/theme_providers.dart';
|
||||||
|
@ -27,15 +28,24 @@ class TxIcon extends ConsumerWidget {
|
||||||
required this.coin,
|
required this.coin,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final Transaction transaction;
|
final Object transaction;
|
||||||
final int currentHeight;
|
final int currentHeight;
|
||||||
final Coin coin;
|
final Coin coin;
|
||||||
|
|
||||||
static const Size size = Size(32, 32);
|
static const Size size = Size(32, 32);
|
||||||
|
|
||||||
String _getAssetName(
|
String _getAssetName(
|
||||||
bool isCancelled, bool isReceived, bool isPending, IThemeAssets assets) {
|
bool isCancelled,
|
||||||
if (!isReceived && transaction.subType == TransactionSubType.mint) {
|
bool isReceived,
|
||||||
|
bool isPending,
|
||||||
|
TransactionSubType subType,
|
||||||
|
IThemeAssets assets,
|
||||||
|
) {
|
||||||
|
if (subType == TransactionSubType.cashFusion) {
|
||||||
|
return Assets.svg.cashFusion;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isReceived && subType == TransactionSubType.mint) {
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
return Assets.svg.anonymizeFailed;
|
return Assets.svg.anonymizeFailed;
|
||||||
}
|
}
|
||||||
|
@ -66,17 +76,40 @@ class TxIcon extends ConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final txIsReceived = transaction.type == TransactionType.incoming;
|
final bool txIsReceived;
|
||||||
|
final String assetName;
|
||||||
|
|
||||||
final assetName = _getAssetName(
|
if (transaction is Transaction) {
|
||||||
transaction.isCancelled,
|
final tx = transaction as Transaction;
|
||||||
|
txIsReceived = tx.type == TransactionType.incoming;
|
||||||
|
assetName = _getAssetName(
|
||||||
|
tx.isCancelled,
|
||||||
txIsReceived,
|
txIsReceived,
|
||||||
!transaction.isConfirmed(
|
!tx.isConfirmed(
|
||||||
currentHeight,
|
currentHeight,
|
||||||
coin.requiredConfirmations,
|
coin.requiredConfirmations,
|
||||||
),
|
),
|
||||||
|
tx.subType,
|
||||||
ref.watch(themeAssetsProvider),
|
ref.watch(themeAssetsProvider),
|
||||||
);
|
);
|
||||||
|
} else if (transaction is TransactionV2) {
|
||||||
|
final tx = transaction as TransactionV2;
|
||||||
|
txIsReceived = tx.type == TransactionType.incoming;
|
||||||
|
assetName = _getAssetName(
|
||||||
|
false,
|
||||||
|
txIsReceived,
|
||||||
|
!tx.isConfirmed(
|
||||||
|
currentHeight,
|
||||||
|
coin.requiredConfirmations,
|
||||||
|
),
|
||||||
|
tx.subType,
|
||||||
|
ref.watch(themeAssetsProvider),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw ArgumentError(
|
||||||
|
"Unknown transaction type ${transaction.runtimeType}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: size.width,
|
width: size.width,
|
||||||
|
@ -96,7 +129,8 @@ class TxIcon extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
width: size.width,
|
width: size.width,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:stackwallet/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart';
|
import 'package:stackwallet/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart';
|
||||||
import 'package:stackwallet/pages/special/firo_rescan_recovery_error_dialog.dart';
|
import 'package:stackwallet/pages/special/firo_rescan_recovery_error_dialog.dart';
|
||||||
import 'package:stackwallet/pages/token_view/my_tokens_view.dart';
|
import 'package:stackwallet/pages/token_view/my_tokens_view.dart';
|
||||||
|
import 'package:stackwallet/pages/wallet_view/sub_widgets/transaction_v2_list.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/transactions_list.dart';
|
import 'package:stackwallet/pages/wallet_view/sub_widgets/transactions_list.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart';
|
import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart';
|
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart';
|
||||||
|
@ -514,6 +515,11 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
|
||||||
? MyTokensView(
|
? MyTokensView(
|
||||||
walletId: widget.walletId,
|
walletId: widget.walletId,
|
||||||
)
|
)
|
||||||
|
: coin == Coin.bitcoincash ||
|
||||||
|
coin == Coin.bitcoincashTestnet
|
||||||
|
? TransactionsV2List(
|
||||||
|
walletId: widget.walletId,
|
||||||
|
)
|
||||||
: TransactionsList(
|
: TransactionsList(
|
||||||
managerProvider: ref.watch(
|
managerProvider: ref.watch(
|
||||||
walletsChangeNotifierProvider.select(
|
walletsChangeNotifierProvider.select(
|
||||||
|
|
|
@ -19,7 +19,6 @@ import 'package:bip39/bip39.dart' as bip39;
|
||||||
import 'package:bitbox/bitbox.dart' as bitbox;
|
import 'package:bitbox/bitbox.dart' as bitbox;
|
||||||
import 'package:bitcoindart/bitcoindart.dart';
|
import 'package:bitcoindart/bitcoindart.dart';
|
||||||
import 'package:bs58check/bs58check.dart' as bs58check;
|
import 'package:bs58check/bs58check.dart' as bs58check;
|
||||||
import 'package:decimal/decimal.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:stackwallet/db/isar/main_db.dart';
|
import 'package:stackwallet/db/isar/main_db.dart';
|
||||||
|
@ -28,6 +27,9 @@ import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
||||||
import 'package:stackwallet/models/balance.dart';
|
import 'package:stackwallet/models/balance.dart';
|
||||||
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart'
|
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart'
|
||||||
as stack_address;
|
as stack_address;
|
||||||
|
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' as isar_models;
|
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
|
||||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||||
import 'package:stackwallet/models/signing_data.dart';
|
import 'package:stackwallet/models/signing_data.dart';
|
||||||
|
@ -1944,7 +1946,7 @@ class BitcoinCashWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Map<String, dynamic>>> _fetchHistory(
|
Future<List<Map<String, dynamic>>> _fetchHistory(
|
||||||
List<String> allAddresses) async {
|
Iterable<String> allAddresses) async {
|
||||||
try {
|
try {
|
||||||
List<Map<String, dynamic>> allTxHashes = [];
|
List<Map<String, dynamic>> allTxHashes = [];
|
||||||
|
|
||||||
|
@ -1956,9 +1958,10 @@ class BitcoinCashWallet extends CoinServiceAPI
|
||||||
if (batches[batchNumber] == null) {
|
if (batches[batchNumber] == null) {
|
||||||
batches[batchNumber] = {};
|
batches[batchNumber] = {};
|
||||||
}
|
}
|
||||||
final scripthash = _convertToScriptHash(allAddresses[i], _network);
|
final scripthash =
|
||||||
|
_convertToScriptHash(allAddresses.elementAt(i), _network);
|
||||||
final id = Logger.isTestEnv ? "$i" : const Uuid().v1();
|
final id = Logger.isTestEnv ? "$i" : const Uuid().v1();
|
||||||
requestIdToAddressMap[id] = allAddresses[i];
|
requestIdToAddressMap[id] = allAddresses.elementAt(i);
|
||||||
batches[batchNumber]!.addAll({
|
batches[batchNumber]!.addAll({
|
||||||
id: [scripthash]
|
id: [scripthash]
|
||||||
});
|
});
|
||||||
|
@ -2024,25 +2027,22 @@ class BitcoinCashWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
}).toSet();
|
}).toSet();
|
||||||
|
|
||||||
|
final allAddressesSet = {...receivingAddresses, ...changeAddresses};
|
||||||
|
|
||||||
final List<Map<String, dynamic>> allTxHashes =
|
final List<Map<String, dynamic>> allTxHashes =
|
||||||
await _fetchHistory([...receivingAddresses, ...changeAddresses]);
|
await _fetchHistory(allAddressesSet);
|
||||||
|
|
||||||
List<Map<String, dynamic>> allTransactions = [];
|
List<Map<String, dynamic>> allTransactions = [];
|
||||||
|
|
||||||
for (final txHash in allTxHashes) {
|
for (final txHash in allTxHashes) {
|
||||||
final storedTx = await db
|
final storedTx = await db.isar.transactionV2s
|
||||||
.getTransactions(walletId)
|
.where()
|
||||||
.filter()
|
.txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
|
||||||
.txidEqualTo(txHash["tx_hash"] as String)
|
|
||||||
.findFirst();
|
.findFirst();
|
||||||
|
|
||||||
if (storedTx == null ||
|
if (storedTx == null ||
|
||||||
storedTx.address.value == null ||
|
|
||||||
storedTx.height == null ||
|
storedTx.height == null ||
|
||||||
(storedTx.height != null && storedTx.height! <= 0)
|
(storedTx.height != null && storedTx.height! <= 0)) {
|
||||||
// zero conf messes this up
|
|
||||||
// !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)
|
|
||||||
) {
|
|
||||||
final tx = await cachedElectrumXClient.getTransaction(
|
final tx = await cachedElectrumXClient.getTransaction(
|
||||||
txHash: txHash["tx_hash"] as String,
|
txHash: txHash["tx_hash"] as String,
|
||||||
verbose: true,
|
verbose: true,
|
||||||
|
@ -2051,225 +2051,167 @@ class BitcoinCashWallet extends CoinServiceAPI
|
||||||
|
|
||||||
// Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}");
|
// Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}");
|
||||||
if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) {
|
if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) {
|
||||||
tx["address"] = await db
|
|
||||||
.getAddresses(walletId)
|
|
||||||
.filter()
|
|
||||||
.valueEqualTo(txHash["address"] as String)
|
|
||||||
.findFirst();
|
|
||||||
tx["height"] = txHash["height"];
|
tx["height"] = txHash["height"];
|
||||||
allTransactions.add(tx);
|
allTransactions.add(tx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//
|
|
||||||
// Logging.instance.log("addAddresses: $allAddresses", level: LogLevel.Info);
|
|
||||||
// Logging.instance.log("allTxHashes: $allTxHashes", level: LogLevel.Info);
|
|
||||||
//
|
|
||||||
// Logging.instance.log("allTransactions length: ${allTransactions.length}",
|
|
||||||
// level: LogLevel.Info);
|
|
||||||
|
|
||||||
final List<Tuple2<isar_models.Transaction, isar_models.Address?>> txns = [];
|
final List<TransactionV2> txns = [];
|
||||||
|
|
||||||
for (final txData in allTransactions) {
|
for (final txData in allTransactions) {
|
||||||
Set<String> inputAddresses = {};
|
Set<String> inputAddresses = {};
|
||||||
Set<String> outputAddresses = {};
|
Set<String> outputAddresses = {};
|
||||||
|
|
||||||
Amount totalInputValue = Amount(
|
// set to true if any inputs were detected as owned by this wallet
|
||||||
rawValue: BigInt.from(0),
|
bool wasSentFromThisWallet = false;
|
||||||
fractionDigits: coin.decimals,
|
BigInt amountSentFromThisWallet = BigInt.zero;
|
||||||
);
|
|
||||||
Amount totalOutputValue = Amount(
|
|
||||||
rawValue: BigInt.from(0),
|
|
||||||
fractionDigits: coin.decimals,
|
|
||||||
);
|
|
||||||
|
|
||||||
Amount amountSentFromWallet = Amount(
|
// set to true if any outputs were detected as owned by this wallet
|
||||||
rawValue: BigInt.from(0),
|
bool wasReceivedInThisWallet = false;
|
||||||
fractionDigits: coin.decimals,
|
BigInt amountReceivedInThisWallet = BigInt.zero;
|
||||||
);
|
BigInt changeAmountReceivedInThisWallet = BigInt.zero;
|
||||||
Amount amountReceivedInWallet = Amount(
|
|
||||||
rawValue: BigInt.from(0),
|
|
||||||
fractionDigits: coin.decimals,
|
|
||||||
);
|
|
||||||
Amount changeAmount = Amount(
|
|
||||||
rawValue: BigInt.from(0),
|
|
||||||
fractionDigits: coin.decimals,
|
|
||||||
);
|
|
||||||
|
|
||||||
// parse inputs
|
// parse inputs
|
||||||
for (final input in txData["vin"] as List) {
|
final List<InputV2> inputs = [];
|
||||||
final prevTxid = input["txid"] as String;
|
for (final jsonInput in txData["vin"] as List) {
|
||||||
final prevOut = input["vout"] as int;
|
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;
|
||||||
|
|
||||||
// fetch input tx to get address
|
|
||||||
final inputTx = await cachedElectrumXClient.getTransaction(
|
final inputTx = await cachedElectrumXClient.getTransaction(
|
||||||
txHash: prevTxid,
|
txHash: txid, coin: coin);
|
||||||
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,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (final output in inputTx["vout"] as List) {
|
outpoint = OutpointV2.isarCantDoRequiredInDefaultConstructor(
|
||||||
// check matching output
|
txid: txid,
|
||||||
if (prevOut == output["n"]) {
|
vout: vout,
|
||||||
// get value
|
);
|
||||||
final value = Amount.fromDecimal(
|
valueStringSats = prevOut.valueStringSats;
|
||||||
Decimal.parse(output["value"].toString()),
|
addresses.addAll(prevOut.addresses);
|
||||||
fractionDigits: coin.decimals,
|
}
|
||||||
|
|
||||||
|
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?,
|
||||||
);
|
);
|
||||||
|
|
||||||
// add value to total
|
|
||||||
totalInputValue = totalInputValue + value;
|
|
||||||
|
|
||||||
// get input(prevOut) address
|
|
||||||
final address =
|
|
||||||
output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
|
||||||
output["scriptPubKey"]?["address"] as String?;
|
|
||||||
|
|
||||||
if (address != null) {
|
|
||||||
inputAddresses.add(address);
|
|
||||||
|
|
||||||
// if input was from my wallet, add value to amount sent
|
|
||||||
if (receivingAddresses.contains(address) ||
|
|
||||||
changeAddresses.contains(address)) {
|
|
||||||
amountSentFromWallet = amountSentFromWallet + value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse outputs
|
|
||||||
for (final output in txData["vout"] as List) {
|
|
||||||
// get value
|
|
||||||
final value = Amount.fromDecimal(
|
|
||||||
Decimal.parse(output["value"].toString()),
|
|
||||||
fractionDigits: coin.decimals,
|
|
||||||
);
|
|
||||||
|
|
||||||
// add value to total
|
|
||||||
totalOutputValue += value;
|
|
||||||
|
|
||||||
// get output address
|
|
||||||
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
|
||||||
output["scriptPubKey"]?["address"] as String?;
|
|
||||||
if (address != null) {
|
|
||||||
outputAddresses.add(address);
|
|
||||||
|
|
||||||
// if output was to my wallet, add value to amount received
|
|
||||||
if (receivingAddresses.contains(address)) {
|
|
||||||
amountReceivedInWallet += value;
|
|
||||||
} else if (changeAddresses.contains(address)) {
|
|
||||||
changeAmount += value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final mySentFromAddresses = [
|
|
||||||
...receivingAddresses.intersection(inputAddresses),
|
|
||||||
...changeAddresses.intersection(inputAddresses)
|
|
||||||
];
|
|
||||||
final myReceivedOnAddresses =
|
|
||||||
receivingAddresses.intersection(outputAddresses);
|
|
||||||
final myChangeReceivedOnAddresses =
|
|
||||||
changeAddresses.intersection(outputAddresses);
|
|
||||||
|
|
||||||
final fee = totalInputValue - totalOutputValue;
|
|
||||||
|
|
||||||
// this is the address initially used to fetch the txid
|
|
||||||
isar_models.Address transactionAddress =
|
|
||||||
txData["address"] as isar_models.Address;
|
|
||||||
|
|
||||||
isar_models.TransactionType type;
|
|
||||||
Amount amount;
|
|
||||||
if (mySentFromAddresses.isNotEmpty && myReceivedOnAddresses.isNotEmpty) {
|
|
||||||
// tx is sent to self
|
|
||||||
type = isar_models.TransactionType.sentToSelf;
|
|
||||||
amount =
|
|
||||||
amountSentFromWallet - amountReceivedInWallet - fee - changeAmount;
|
|
||||||
} else if (mySentFromAddresses.isNotEmpty) {
|
|
||||||
// outgoing tx
|
|
||||||
type = isar_models.TransactionType.outgoing;
|
|
||||||
amount = amountSentFromWallet - changeAmount - fee;
|
|
||||||
|
|
||||||
// TODO fix this hack
|
|
||||||
final diff = outputAddresses.difference(myChangeReceivedOnAddresses);
|
|
||||||
final possible =
|
|
||||||
diff.isNotEmpty ? diff.first : myChangeReceivedOnAddresses.first;
|
|
||||||
|
|
||||||
if (transactionAddress.value != possible) {
|
|
||||||
transactionAddress = isar_models.Address(
|
|
||||||
walletId: walletId,
|
|
||||||
value: possible,
|
|
||||||
publicKey: [],
|
|
||||||
type: stack_address.AddressType.nonWallet,
|
|
||||||
derivationIndex: -1,
|
|
||||||
derivationPath: null,
|
|
||||||
subType: stack_address.AddressSubType.nonWallet,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// incoming tx
|
|
||||||
type = isar_models.TransactionType.incoming;
|
|
||||||
amount = amountReceivedInWallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<isar_models.Input> inputs = [];
|
|
||||||
List<isar_models.Output> outputs = [];
|
|
||||||
|
|
||||||
for (final json in txData["vin"] as List) {
|
|
||||||
bool isCoinBase = json['coinbase'] != null;
|
|
||||||
final input = isar_models.Input(
|
|
||||||
txid: json['txid'] as String,
|
|
||||||
vout: json['vout'] as int? ?? -1,
|
|
||||||
scriptSig: json['scriptSig']?['hex'] as String?,
|
|
||||||
scriptSigAsm: json['scriptSig']?['asm'] as String?,
|
|
||||||
isCoinbase: isCoinBase ? isCoinBase : json['is_coinbase'] as bool?,
|
|
||||||
sequence: json['sequence'] as int?,
|
|
||||||
innerRedeemScriptAsm: json['innerRedeemscriptAsm'] as String?,
|
|
||||||
);
|
|
||||||
inputs.add(input);
|
inputs.add(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final json in txData["vout"] as List) {
|
for (final input in inputs) {
|
||||||
final output = isar_models.Output(
|
if (allAddressesSet.intersection(input.addresses.toSet()).isNotEmpty) {
|
||||||
scriptPubKey: json['scriptPubKey']?['hex'] as String?,
|
wasSentFromThisWallet = true;
|
||||||
scriptPubKeyAsm: json['scriptPubKey']?['asm'] as String?,
|
amountSentFromThisWallet += input.value;
|
||||||
scriptPubKeyType: json['scriptPubKey']?['type'] as String?,
|
}
|
||||||
scriptPubKeyAddress:
|
|
||||||
json["scriptPubKey"]?["addresses"]?[0] as String? ??
|
inputAddresses.addAll(input.addresses);
|
||||||
json['scriptPubKey']['type'] as String,
|
}
|
||||||
value: Amount.fromDecimal(
|
|
||||||
Decimal.parse(json["value"].toString()),
|
// parse outputs
|
||||||
fractionDigits: coin.decimals,
|
final List<OutputV2> outputs = [];
|
||||||
).raw.toInt(),
|
for (final outputJson in txData["vout"] as List) {
|
||||||
|
final output = OutputV2.fromElectrumXJson(
|
||||||
|
Map<String, dynamic>.from(outputJson as Map),
|
||||||
|
decimalPlaces: coin.decimals,
|
||||||
);
|
);
|
||||||
outputs.add(output);
|
outputs.add(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
final tx = isar_models.Transaction(
|
for (final output in outputs) {
|
||||||
walletId: walletId,
|
if (allAddressesSet
|
||||||
txid: txData["txid"] as String,
|
.intersection(output.addresses.toSet())
|
||||||
timestamp: txData["blocktime"] as int? ??
|
.isNotEmpty) {}
|
||||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
|
||||||
type: type,
|
|
||||||
subType: isar_models.TransactionSubType.none,
|
|
||||||
amount: amount.raw.toInt(),
|
|
||||||
amountString: amount.toJsonString(),
|
|
||||||
fee: fee.raw.toInt(),
|
|
||||||
height: txData["height"] as int?,
|
|
||||||
isCancelled: false,
|
|
||||||
isLelantus: false,
|
|
||||||
slateId: null,
|
|
||||||
otherData: null,
|
|
||||||
nonce: null,
|
|
||||||
inputs: inputs,
|
|
||||||
outputs: outputs,
|
|
||||||
numberOfMessages: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
txns.add(Tuple2(tx, transactionAddress));
|
outputAddresses.addAll(output.addresses);
|
||||||
|
|
||||||
|
// if output was to my wallet, add value to amount received
|
||||||
|
if (receivingAddresses
|
||||||
|
.intersection(output.addresses.toSet())
|
||||||
|
.isNotEmpty) {
|
||||||
|
wasReceivedInThisWallet = true;
|
||||||
|
amountReceivedInThisWallet += output.value;
|
||||||
|
} else if (changeAddresses
|
||||||
|
.intersection(output.addresses.toSet())
|
||||||
|
.isNotEmpty) {
|
||||||
|
wasReceivedInThisWallet = true;
|
||||||
|
changeAmountReceivedInThisWallet += output.value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.addNewTransactionData(txns, walletId);
|
final totalIn = inputs
|
||||||
|
.map((e) => e.value)
|
||||||
|
.reduce((value, element) => value += element);
|
||||||
|
final totalOut = outputs
|
||||||
|
.map((e) => e.value)
|
||||||
|
.reduce((value, element) => value += element);
|
||||||
|
|
||||||
|
final fee = totalIn - totalOut;
|
||||||
|
|
||||||
|
isar_models.TransactionType type;
|
||||||
|
|
||||||
|
// at least one input was owned by this wallet
|
||||||
|
if (wasSentFromThisWallet) {
|
||||||
|
type = isar_models.TransactionType.outgoing;
|
||||||
|
|
||||||
|
if (wasReceivedInThisWallet) {
|
||||||
|
if (changeAmountReceivedInThisWallet + amountReceivedInThisWallet ==
|
||||||
|
totalOut) {
|
||||||
|
// definitely sent all to self
|
||||||
|
type = isar_models.TransactionType.sentToSelf;
|
||||||
|
} else if (amountReceivedInThisWallet == BigInt.zero) {
|
||||||
|
// most likely just a typical send
|
||||||
|
// do nothing here yet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (wasReceivedInThisWallet) {
|
||||||
|
// only found outputs owned by this wallet
|
||||||
|
type = isar_models.TransactionType.incoming;
|
||||||
|
} else {
|
||||||
|
throw Exception("Unexpected tx found: $txData");
|
||||||
|
}
|
||||||
|
|
||||||
|
final tx = TransactionV2(
|
||||||
|
walletId: walletId,
|
||||||
|
blockHash: txData["blockhash"] as String?,
|
||||||
|
hash: txData["hash"] as String,
|
||||||
|
txid: txData["txid"] as String,
|
||||||
|
height: txData["height"] as int?,
|
||||||
|
version: txData["version"] as int,
|
||||||
|
timestamp: txData["blocktime"] as int? ??
|
||||||
|
DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
|
||||||
|
inputs: List.unmodifiable(inputs),
|
||||||
|
outputs: List.unmodifiable(outputs),
|
||||||
|
type: type,
|
||||||
|
subType: isar_models.TransactionSubType.none,
|
||||||
|
);
|
||||||
|
|
||||||
|
txns.add(tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.updateOrPutTransactionV2s(txns);
|
||||||
|
|
||||||
// quick hack to notify manager to call notifyListeners if
|
// quick hack to notify manager to call notifyListeners if
|
||||||
// transactions changed
|
// transactions changed
|
||||||
|
|
|
@ -112,6 +112,8 @@ mixin ElectrumXParsing {
|
||||||
DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
|
DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
|
||||||
inputs: List.unmodifiable(inputs),
|
inputs: List.unmodifiable(inputs),
|
||||||
outputs: List.unmodifiable(outputs),
|
outputs: List.unmodifiable(outputs),
|
||||||
|
subType: TransactionSubType.none,
|
||||||
|
type: TransactionType.unknown,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ import 'package:stackwallet/db/isar/main_db.dart' as _i9;
|
||||||
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i6;
|
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i6;
|
||||||
import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i3;
|
import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i3;
|
||||||
import 'package:stackwallet/models/isar/models/block_explorer.dart' as _i11;
|
import 'package:stackwallet/models/isar/models/block_explorer.dart' as _i11;
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart'
|
||||||
|
as _i14;
|
||||||
import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i10;
|
import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i10;
|
||||||
import 'package:stackwallet/models/isar/models/isar_models.dart' as _i12;
|
import 'package:stackwallet/models/isar/models/isar_models.dart' as _i12;
|
||||||
import 'package:stackwallet/services/transaction_notification_tracker.dart'
|
import 'package:stackwallet/services/transaction_notification_tracker.dart'
|
||||||
|
@ -1144,6 +1146,16 @@ class MockMainDB extends _i1.Mock implements _i9.MainDB {
|
||||||
returnValueForMissingStub: _i5.Future<void>.value(),
|
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||||
) as _i5.Future<void>);
|
) as _i5.Future<void>);
|
||||||
@override
|
@override
|
||||||
|
_i5.Future<List<int>> updateOrPutTransactionV2s(
|
||||||
|
List<_i14.TransactionV2>? transactions) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(
|
||||||
|
#updateOrPutTransactionV2s,
|
||||||
|
[transactions],
|
||||||
|
),
|
||||||
|
returnValue: _i5.Future<List<int>>.value(<int>[]),
|
||||||
|
) as _i5.Future<List<int>>);
|
||||||
|
@override
|
||||||
_i4.QueryBuilder<_i12.EthContract, _i12.EthContract, _i4.QWhere>
|
_i4.QueryBuilder<_i12.EthContract, _i12.EthContract, _i4.QWhere>
|
||||||
getEthContracts() => (super.noSuchMethod(
|
getEthContracts() => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
|
|
|
@ -17,6 +17,8 @@ import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i13;
|
||||||
import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i12;
|
import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i12;
|
||||||
import 'package:stackwallet/models/balance.dart' as _i9;
|
import 'package:stackwallet/models/balance.dart' as _i9;
|
||||||
import 'package:stackwallet/models/isar/models/block_explorer.dart' as _i38;
|
import 'package:stackwallet/models/isar/models/block_explorer.dart' as _i38;
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart'
|
||||||
|
as _i39;
|
||||||
import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i37;
|
import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i37;
|
||||||
import 'package:stackwallet/models/isar/models/isar_models.dart' as _i24;
|
import 'package:stackwallet/models/isar/models/isar_models.dart' as _i24;
|
||||||
import 'package:stackwallet/models/isar/stack_theme.dart' as _i35;
|
import 'package:stackwallet/models/isar/stack_theme.dart' as _i35;
|
||||||
|
@ -3590,6 +3592,16 @@ class MockMainDB extends _i1.Mock implements _i14.MainDB {
|
||||||
returnValueForMissingStub: _i21.Future<void>.value(),
|
returnValueForMissingStub: _i21.Future<void>.value(),
|
||||||
) as _i21.Future<void>);
|
) as _i21.Future<void>);
|
||||||
@override
|
@override
|
||||||
|
_i21.Future<List<int>> updateOrPutTransactionV2s(
|
||||||
|
List<_i39.TransactionV2>? transactions) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(
|
||||||
|
#updateOrPutTransactionV2s,
|
||||||
|
[transactions],
|
||||||
|
),
|
||||||
|
returnValue: _i21.Future<List<int>>.value(<int>[]),
|
||||||
|
) as _i21.Future<List<int>>);
|
||||||
|
@override
|
||||||
_i18.QueryBuilder<_i24.EthContract, _i24.EthContract, _i18.QWhere>
|
_i18.QueryBuilder<_i24.EthContract, _i24.EthContract, _i18.QWhere>
|
||||||
getEthContracts() => (super.noSuchMethod(
|
getEthContracts() => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
|
|
Loading…
Reference in a new issue