Merge remote-tracking branch 'origin/fusion' into fusion

This commit is contained in:
sneurlax 2023-10-19 16:38:13 -05:00
commit bafc4d302f
13 changed files with 1160 additions and 247 deletions

View file

@ -13,6 +13,7 @@ import 'package:flutter_native_splash/cli_commands.dart';
import 'package:isar/isar.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/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/models/isar/models/contact_entry.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/isar/ordinal.dart';
@ -57,6 +58,7 @@ class MainDB {
ContactEntrySchema,
OrdinalSchema,
LelantusCoinSchema,
TransactionV2Schema,
],
directory: (await StackFileSystem.applicationIsarDirectory()).path,
// 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 =======================================================
// eth contracts

View file

@ -251,5 +251,6 @@ enum TransactionSubType {
bip47Notification, // bip47 payment code notification transaction flag
mint, // firo specific
join, // firo specific
ethToken; // eth token
ethToken, // eth token
cashFusion;
}

View file

@ -364,6 +364,7 @@ const _TransactionsubTypeEnumValueMap = {
'mint': 2,
'join': 3,
'ethToken': 4,
'cashFusion': 5,
};
const _TransactionsubTypeValueEnumMap = {
0: TransactionSubType.none,
@ -371,6 +372,7 @@ const _TransactionsubTypeValueEnumMap = {
2: TransactionSubType.mint,
3: TransactionSubType.join,
4: TransactionSubType.ethToken,
5: TransactionSubType.cashFusion,
};
const _TransactiontypeEnumValueMap = {
'outgoing': 0,

View file

@ -72,7 +72,7 @@ class InputV2 {
InputV2()
..scriptSigHex = scriptSigHex
..sequence = sequence
..sequence = sequence
..outpoint = outpoint
..addresses = List.unmodifiable(addresses)
..valueStringSats = valueStringSats
..witness = witness

View file

@ -1,14 +1,17 @@
import 'dart:math';
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/output_v2.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
part 'transaction_v2.g.dart';
@Collection()
class TransactionV2 {
final Id id = Isar.autoIncrement;
Id id = Isar.autoIncrement;
@Index()
final String walletId;
@ -28,6 +31,12 @@ class TransactionV2 {
final List<InputV2> inputs;
final List<OutputV2> outputs;
@enumerated
final TransactionType type;
@enumerated
final TransactionSubType subType;
TransactionV2({
required this.walletId,
required this.blockHash,
@ -38,6 +47,8 @@ class TransactionV2 {
required this.inputs,
required this.outputs,
required this.version,
required this.type,
required this.subType,
});
int getConfirmations(int currentChainHeight) {
@ -50,12 +61,32 @@ class TransactionV2 {
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
String toString() {
return 'TransactionV2(\n'
' walletId: $walletId,\n'
' hash: $hash,\n'
' txid: $txid,\n'
' type: $type,\n'
' subType: $subType,\n'
' timestamp: $timestamp,\n'
' height: $height,\n'
' blockHash: $blockHash,\n'
@ -65,3 +96,12 @@ class TransactionV2 {
')';
}
}
enum TxDirection {
outgoing,
incoming;
}
enum TxType {
normal,
}

View file

@ -44,23 +44,35 @@ const TransactionV2Schema = CollectionSchema(
type: IsarType.objectList,
target: r'OutputV2',
),
r'timestamp': PropertySchema(
r'subType': PropertySchema(
id: 5,
name: r'subType',
type: IsarType.byte,
enumMap: _TransactionV2subTypeEnumValueMap,
),
r'timestamp': PropertySchema(
id: 6,
name: r'timestamp',
type: IsarType.long,
),
r'txid': PropertySchema(
id: 6,
id: 7,
name: r'txid',
type: IsarType.string,
),
r'type': PropertySchema(
id: 8,
name: r'type',
type: IsarType.byte,
enumMap: _TransactionV2typeEnumValueMap,
),
r'version': PropertySchema(
id: 7,
id: 9,
name: r'version',
type: IsarType.long,
),
r'walletId': PropertySchema(
id: 8,
id: 10,
name: r'walletId',
type: IsarType.string,
)
@ -183,10 +195,12 @@ void _transactionV2Serialize(
OutputV2Schema.serialize,
object.outputs,
);
writer.writeLong(offsets[5], object.timestamp);
writer.writeString(offsets[6], object.txid);
writer.writeLong(offsets[7], object.version);
writer.writeString(offsets[8], object.walletId);
writer.writeByte(offsets[5], object.subType.index);
writer.writeLong(offsets[6], object.timestamp);
writer.writeString(offsets[7], object.txid);
writer.writeByte(offsets[8], object.type.index);
writer.writeLong(offsets[9], object.version);
writer.writeString(offsets[10], object.walletId);
}
TransactionV2 _transactionV2Deserialize(
@ -213,11 +227,17 @@ TransactionV2 _transactionV2Deserialize(
OutputV2(),
) ??
[],
timestamp: reader.readLong(offsets[5]),
txid: reader.readString(offsets[6]),
version: reader.readLong(offsets[7]),
walletId: reader.readString(offsets[8]),
subType:
_TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[5])] ??
TransactionSubType.none,
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;
}
@ -251,18 +271,54 @@ P _transactionV2DeserializeProp<P>(
) ??
[]) as P;
case 5:
return (reader.readLong(offset)) as P;
return (_TransactionV2subTypeValueEnumMap[
reader.readByteOrNull(offset)] ??
TransactionSubType.none) as P;
case 6:
return (reader.readString(offset)) as P;
case 7:
return (reader.readLong(offset)) as P;
case 7:
return (reader.readString(offset)) as P;
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;
default:
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) {
return object.id;
}
@ -272,7 +328,9 @@ List<IsarLinkBase<dynamic>> _transactionV2GetLinks(TransactionV2 object) {
}
void _transactionV2Attach(
IsarCollection<dynamic> col, Id id, TransactionV2 object) {}
IsarCollection<dynamic> col, Id id, TransactionV2 object) {
object.id = id;
}
extension TransactionV2ByIndex on IsarCollection<TransactionV2> {
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>
timestampEqualTo(int value) {
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>
versionEqualTo(int value) {
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() {
return QueryBuilder.apply(this, (query) {
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() {
return QueryBuilder.apply(this, (query) {
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() {
return QueryBuilder.apply(this, (query) {
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() {
return QueryBuilder.apply(this, (query) {
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() {
return QueryBuilder.apply(this, (query) {
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() {
return QueryBuilder.apply(this, (query) {
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() {
return QueryBuilder.apply(this, (query) {
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() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'version');

View 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),
);
},
),
),
),
],
),
],
),
),
],
),
),
),
),
);
}
}

View file

@ -13,6 +13,7 @@ import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/stack_theme.dart';
import 'package:stackwallet/themes/theme_providers.dart';
@ -27,15 +28,24 @@ class TxIcon extends ConsumerWidget {
required this.coin,
}) : super(key: key);
final Transaction transaction;
final Object transaction;
final int currentHeight;
final Coin coin;
static const Size size = Size(32, 32);
String _getAssetName(
bool isCancelled, bool isReceived, bool isPending, IThemeAssets assets) {
if (!isReceived && transaction.subType == TransactionSubType.mint) {
bool isCancelled,
bool isReceived,
bool isPending,
TransactionSubType subType,
IThemeAssets assets,
) {
if (subType == TransactionSubType.cashFusion) {
return Assets.svg.cashFusion;
}
if (!isReceived && subType == TransactionSubType.mint) {
if (isCancelled) {
return Assets.svg.anonymizeFailed;
}
@ -66,37 +76,61 @@ class TxIcon extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final txIsReceived = transaction.type == TransactionType.incoming;
final bool txIsReceived;
final String assetName;
final assetName = _getAssetName(
transaction.isCancelled,
txIsReceived,
!transaction.isConfirmed(
currentHeight,
coin.requiredConfirmations,
),
ref.watch(themeAssetsProvider),
);
if (transaction is Transaction) {
final tx = transaction as Transaction;
txIsReceived = tx.type == TransactionType.incoming;
assetName = _getAssetName(
tx.isCancelled,
txIsReceived,
!tx.isConfirmed(
currentHeight,
coin.requiredConfirmations,
),
tx.subType,
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(
width: size.width,
height: size.height,
child: Center(
// if it starts with "assets" we assume its local
// TODO: a more thorough check
child: assetName.startsWith("assets")
? SvgPicture.asset(
// if it starts with "assets" we assume its local
// TODO: a more thorough check
child: assetName.startsWith("assets")
? SvgPicture.asset(
assetName,
width: size.width,
height: size.height,
)
: SvgPicture.file(
File(
assetName,
width: size.width,
height: size.height,
)
: SvgPicture.file(
File(
assetName,
),
width: size.width,
height: size.height,
)),
),
width: size.width,
height: size.height,
),
),
);
}
}

View file

@ -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/special/firo_rescan_recovery_error_dialog.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/transaction_views/all_transactions_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart';
@ -514,13 +515,18 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
? MyTokensView(
walletId: widget.walletId,
)
: TransactionsList(
managerProvider: ref.watch(
walletsChangeNotifierProvider.select(
(value) => value.getManagerProvider(
widget.walletId))),
walletId: widget.walletId,
),
: coin == Coin.bitcoincash ||
coin == Coin.bitcoincashTestnet
? TransactionsV2List(
walletId: widget.walletId,
)
: TransactionsList(
managerProvider: ref.watch(
walletsChangeNotifierProvider.select(
(value) => value.getManagerProvider(
widget.walletId))),
walletId: widget.walletId,
),
),
],
),

View file

@ -19,7 +19,6 @@ import 'package:bip39/bip39.dart' as bip39;
import 'package:bitbox/bitbox.dart' as bitbox;
import 'package:bitcoindart/bitcoindart.dart';
import 'package:bs58check/bs58check.dart' as bs58check;
import 'package:decimal/decimal.dart';
import 'package:flutter/foundation.dart';
import 'package:isar/isar.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/isar/models/blockchain_data/address.dart'
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/paymint/fee_object_model.dart';
import 'package:stackwallet/models/signing_data.dart';
@ -1944,7 +1946,7 @@ class BitcoinCashWallet extends CoinServiceAPI
}
Future<List<Map<String, dynamic>>> _fetchHistory(
List<String> allAddresses) async {
Iterable<String> allAddresses) async {
try {
List<Map<String, dynamic>> allTxHashes = [];
@ -1956,9 +1958,10 @@ class BitcoinCashWallet extends CoinServiceAPI
if (batches[batchNumber] == null) {
batches[batchNumber] = {};
}
final scripthash = _convertToScriptHash(allAddresses[i], _network);
final scripthash =
_convertToScriptHash(allAddresses.elementAt(i), _network);
final id = Logger.isTestEnv ? "$i" : const Uuid().v1();
requestIdToAddressMap[id] = allAddresses[i];
requestIdToAddressMap[id] = allAddresses.elementAt(i);
batches[batchNumber]!.addAll({
id: [scripthash]
});
@ -2024,25 +2027,22 @@ class BitcoinCashWallet extends CoinServiceAPI
}
}).toSet();
final allAddressesSet = {...receivingAddresses, ...changeAddresses};
final List<Map<String, dynamic>> allTxHashes =
await _fetchHistory([...receivingAddresses, ...changeAddresses]);
await _fetchHistory(allAddressesSet);
List<Map<String, dynamic>> allTransactions = [];
for (final txHash in allTxHashes) {
final storedTx = await db
.getTransactions(walletId)
.filter()
.txidEqualTo(txHash["tx_hash"] as String)
final storedTx = await db.isar.transactionV2s
.where()
.txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
.findFirst();
if (storedTx == null ||
storedTx.address.value == null ||
storedTx.height == null ||
(storedTx.height != null && storedTx.height! <= 0)
// zero conf messes this up
// !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)
) {
storedTx.height == null ||
(storedTx.height != null && storedTx.height! <= 0)) {
final tx = await cachedElectrumXClient.getTransaction(
txHash: txHash["tx_hash"] as String,
verbose: true,
@ -2051,225 +2051,167 @@ class BitcoinCashWallet extends CoinServiceAPI
// Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}");
if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) {
tx["address"] = await db
.getAddresses(walletId)
.filter()
.valueEqualTo(txHash["address"] as String)
.findFirst();
tx["height"] = txHash["height"];
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) {
Set<String> inputAddresses = {};
Set<String> outputAddresses = {};
Amount totalInputValue = Amount(
rawValue: BigInt.from(0),
fractionDigits: coin.decimals,
);
Amount totalOutputValue = Amount(
rawValue: BigInt.from(0),
fractionDigits: coin.decimals,
);
// set to true if any inputs were detected as owned by this wallet
bool wasSentFromThisWallet = false;
BigInt amountSentFromThisWallet = BigInt.zero;
Amount amountSentFromWallet = Amount(
rawValue: BigInt.from(0),
fractionDigits: coin.decimals,
);
Amount amountReceivedInWallet = Amount(
rawValue: BigInt.from(0),
fractionDigits: coin.decimals,
);
Amount changeAmount = Amount(
rawValue: BigInt.from(0),
fractionDigits: coin.decimals,
);
// set to true if any outputs were detected as owned by this wallet
bool wasReceivedInThisWallet = false;
BigInt amountReceivedInThisWallet = BigInt.zero;
BigInt changeAmountReceivedInThisWallet = BigInt.zero;
// parse inputs
for (final input in txData["vin"] as List) {
final prevTxid = input["txid"] as String;
final prevOut = input["vout"] as int;
final List<InputV2> inputs = [];
for (final jsonInput in txData["vin"] as List) {
final map = Map<String, dynamic>.from(jsonInput as Map);
// fetch input tx to get address
final inputTx = await cachedElectrumXClient.getTransaction(
txHash: prevTxid,
coin: coin,
);
final List<String> addresses = [];
String valueStringSats = "0";
OutpointV2? outpoint;
for (final output in inputTx["vout"] as List) {
// check matching output
if (prevOut == output["n"]) {
// get value
final value = Amount.fromDecimal(
Decimal.parse(output["value"].toString()),
fractionDigits: coin.decimals,
);
final coinbase = map["coinbase"] as String?;
// add value to total
totalInputValue = totalInputValue + value;
if (coinbase == null) {
final txid = map["txid"] as String;
final vout = map["vout"] as int;
// get input(prevOut) address
final address =
output["scriptPubKey"]?["addresses"]?[0] as String? ??
output["scriptPubKey"]?["address"] as String?;
final inputTx = await cachedElectrumXClient.getTransaction(
txHash: txid, coin: coin);
if (address != null) {
inputAddresses.add(address);
final prevOutJson = Map<String, dynamic>.from(
(inputTx["vout"] as List).firstWhere((e) => e["n"] == vout)
as Map);
// 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,
final prevOut = OutputV2.fromElectrumXJson(
prevOutJson,
decimalPlaces: coin.decimals,
);
outpoint = OutpointV2.isarCantDoRequiredInDefaultConstructor(
txid: txid,
vout: vout,
);
valueStringSats = prevOut.valueStringSats;
addresses.addAll(prevOut.addresses);
}
} 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?,
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);
}
for (final json in txData["vout"] as List) {
final output = isar_models.Output(
scriptPubKey: json['scriptPubKey']?['hex'] as String?,
scriptPubKeyAsm: json['scriptPubKey']?['asm'] as String?,
scriptPubKeyType: json['scriptPubKey']?['type'] as String?,
scriptPubKeyAddress:
json["scriptPubKey"]?["addresses"]?[0] as String? ??
json['scriptPubKey']['type'] as String,
value: Amount.fromDecimal(
Decimal.parse(json["value"].toString()),
fractionDigits: coin.decimals,
).raw.toInt(),
for (final input in inputs) {
if (allAddressesSet.intersection(input.addresses.toSet()).isNotEmpty) {
wasSentFromThisWallet = true;
amountSentFromThisWallet += input.value;
}
inputAddresses.addAll(input.addresses);
}
// parse outputs
final List<OutputV2> outputs = [];
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);
}
final tx = isar_models.Transaction(
for (final output in outputs) {
if (allAddressesSet
.intersection(output.addresses.toSet())
.isNotEmpty) {}
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;
}
}
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.now().millisecondsSinceEpoch ~/ 1000),
DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
inputs: List.unmodifiable(inputs),
outputs: List.unmodifiable(outputs),
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));
txns.add(tx);
}
await db.addNewTransactionData(txns, walletId);
await db.updateOrPutTransactionV2s(txns);
// quick hack to notify manager to call notifyListeners if
// transactions changed

View file

@ -112,6 +112,8 @@ mixin ElectrumXParsing {
DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
inputs: List.unmodifiable(inputs),
outputs: List.unmodifiable(outputs),
subType: TransactionSubType.none,
type: TransactionType.unknown,
);
}

View file

@ -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/electrumx.dart' as _i3;
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/isar_models.dart' as _i12;
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(),
) as _i5.Future<void>);
@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>
getEthContracts() => (super.noSuchMethod(
Invocation.method(

View file

@ -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/models/balance.dart' as _i9;
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/isar_models.dart' as _i24;
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(),
) as _i21.Future<void>);
@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>
getEthContracts() => (super.noSuchMethod(
Invocation.method(