mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-25 11:45:59 +00:00
Merge remote-tracking branch 'origin_SW/staging' into persistence
This commit is contained in:
commit
c8b7d3aab8
20 changed files with 754 additions and 453 deletions
|
@ -1 +1 @@
|
||||||
Subproject commit 81659ce57952c5ab54ffe6bacfbf43da159fff3e
|
Subproject commit 73d257ed2fe5b204cf3589822e226301b187b86d
|
|
@ -70,7 +70,7 @@ final openedFromSWBFileStringStateProvider =
|
||||||
// runs the MyApp widget and checks for new users, caching the value in the
|
// runs the MyApp widget and checks for new users, caching the value in the
|
||||||
// miscellaneous box for later use
|
// miscellaneous box for later use
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
GoogleFonts.config.allowRuntimeFetching = false;
|
GoogleFonts.config.allowRuntimeFetching = false;
|
||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
Util.libraryPath = await getLibraryDirectory();
|
Util.libraryPath = await getLibraryDirectory();
|
||||||
|
@ -179,7 +179,9 @@ void main() async {
|
||||||
}
|
}
|
||||||
|
|
||||||
monero.onStartup();
|
monero.onStartup();
|
||||||
wownero.onStartup();
|
if (!Platform.isLinux && !Platform.isWindows) {
|
||||||
|
wownero.onStartup();
|
||||||
|
}
|
||||||
|
|
||||||
// SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
// SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||||
// overlays: [SystemUiOverlay.bottom]);
|
// overlays: [SystemUiOverlay.bottom]);
|
||||||
|
|
|
@ -120,6 +120,8 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
|
||||||
if (Platform.isWindows) {
|
if (Platform.isWindows) {
|
||||||
_coins.remove(Coin.monero);
|
_coins.remove(Coin.monero);
|
||||||
_coins.remove(Coin.wownero);
|
_coins.remove(Coin.wownero);
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
_coins.remove(Coin.wownero);
|
||||||
}
|
}
|
||||||
|
|
||||||
coinEntities.addAll(_coins.map((e) => CoinEntity(e)));
|
coinEntities.addAll(_coins.map((e) => CoinEntity(e)));
|
||||||
|
|
|
@ -150,6 +150,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
||||||
|
|
||||||
String receivingAddress = widget.receivingAddress;
|
String receivingAddress = widget.receivingAddress;
|
||||||
if ((widget.coin == Coin.bitcoincash ||
|
if ((widget.coin == Coin.bitcoincash ||
|
||||||
|
widget.coin == Coin.eCash ||
|
||||||
widget.coin == Coin.bitcoincashTestnet) &&
|
widget.coin == Coin.bitcoincashTestnet) &&
|
||||||
receivingAddress.contains(":")) {
|
receivingAddress.contains(":")) {
|
||||||
// remove cash addr prefix
|
// remove cash addr prefix
|
||||||
|
@ -246,6 +247,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
||||||
|
|
||||||
String receivingAddress = widget.receivingAddress;
|
String receivingAddress = widget.receivingAddress;
|
||||||
if ((widget.coin == Coin.bitcoincash ||
|
if ((widget.coin == Coin.bitcoincash ||
|
||||||
|
widget.coin == Coin.eCash ||
|
||||||
widget.coin == Coin.bitcoincashTestnet) &&
|
widget.coin == Coin.bitcoincashTestnet) &&
|
||||||
receivingAddress.contains(":")) {
|
receivingAddress.contains(":")) {
|
||||||
// remove cash addr prefix
|
// remove cash addr prefix
|
||||||
|
|
|
@ -39,6 +39,7 @@ class _StackThemeCardState extends ConsumerState<StackThemeCard> {
|
||||||
late final StreamSubscription<void> _subscription;
|
late final StreamSubscription<void> _subscription;
|
||||||
|
|
||||||
late bool _hasTheme;
|
late bool _hasTheme;
|
||||||
|
bool _needsUpdate = false;
|
||||||
String? _cachedSize;
|
String? _cachedSize;
|
||||||
|
|
||||||
Future<bool> _downloadAndInstall() async {
|
Future<bool> _downloadAndInstall() async {
|
||||||
|
@ -84,6 +85,7 @@ class _StackThemeCardState extends ConsumerState<StackThemeCard> {
|
||||||
title: message,
|
title: message,
|
||||||
onOkPressed: (_) {
|
onOkPressed: (_) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
_needsUpdate = !result;
|
||||||
_hasTheme = result;
|
_hasTheme = result;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -141,16 +143,21 @@ class _StackThemeCardState extends ConsumerState<StackThemeCard> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StackTheme? getInstalled() => ref
|
||||||
|
.read(mainDBProvider)
|
||||||
|
.isar
|
||||||
|
.stackThemes
|
||||||
|
.where()
|
||||||
|
.themeIdEqualTo(widget.data.id)
|
||||||
|
.findFirstSync();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_hasTheme = ref
|
final installedTheme = getInstalled();
|
||||||
.read(mainDBProvider)
|
_hasTheme = installedTheme != null;
|
||||||
.isar
|
if (_hasTheme) {
|
||||||
.stackThemes
|
_needsUpdate = widget.data.version > (installedTheme?.version ?? 0);
|
||||||
.where()
|
}
|
||||||
.themeIdEqualTo(widget.data.id)
|
|
||||||
.countSync() >
|
|
||||||
0;
|
|
||||||
|
|
||||||
_subscription = ref
|
_subscription = ref
|
||||||
.read(mainDBProvider)
|
.read(mainDBProvider)
|
||||||
|
@ -158,18 +165,16 @@ class _StackThemeCardState extends ConsumerState<StackThemeCard> {
|
||||||
.stackThemes
|
.stackThemes
|
||||||
.watchLazy()
|
.watchLazy()
|
||||||
.listen((event) async {
|
.listen((event) async {
|
||||||
final hasTheme = (await ref
|
final installedTheme = getInstalled();
|
||||||
.read(mainDBProvider)
|
final hasTheme = installedTheme != null;
|
||||||
.isar
|
|
||||||
.stackThemes
|
|
||||||
.where()
|
|
||||||
.themeIdEqualTo(widget.data.id)
|
|
||||||
.count()) >
|
|
||||||
0;
|
|
||||||
if (_hasTheme != hasTheme && mounted) {
|
if (_hasTheme != hasTheme && mounted) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_hasTheme = hasTheme;
|
_hasTheme = hasTheme;
|
||||||
|
if (hasTheme) {
|
||||||
|
_needsUpdate =
|
||||||
|
widget.data.version > (installedTheme.version ?? 0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -272,6 +277,16 @@ class _StackThemeCardState extends ConsumerState<StackThemeCard> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (_hasTheme && _needsUpdate)
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
if (_hasTheme && _needsUpdate)
|
||||||
|
PrimaryButton(
|
||||||
|
label: "Update",
|
||||||
|
buttonHeight: isDesktop ? ButtonHeight.s : ButtonHeight.l,
|
||||||
|
onPressed: _downloadPressed,
|
||||||
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
|
|
|
@ -150,6 +150,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
||||||
case Coin.bitcoincash:
|
case Coin.bitcoincash:
|
||||||
case Coin.litecoin:
|
case Coin.litecoin:
|
||||||
case Coin.dogecoin:
|
case Coin.dogecoin:
|
||||||
|
case Coin.eCash:
|
||||||
case Coin.firo:
|
case Coin.firo:
|
||||||
case Coin.namecoin:
|
case Coin.namecoin:
|
||||||
case Coin.particl:
|
case Coin.particl:
|
||||||
|
|
|
@ -619,16 +619,37 @@ class _TransactionDetailsViewState
|
||||||
CustomTextButton(
|
CustomTextButton(
|
||||||
text: "Info",
|
text: "Info",
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context)
|
if (isDesktop) {
|
||||||
.pushNamed(
|
showDialog<void>(
|
||||||
AddressDetailsView
|
context: context,
|
||||||
.routeName,
|
builder: (_) =>
|
||||||
arguments: Tuple2(
|
DesktopDialog(
|
||||||
_transaction.address
|
maxHeight:
|
||||||
.value!.id,
|
double.infinity,
|
||||||
widget.walletId,
|
child:
|
||||||
),
|
AddressDetailsView(
|
||||||
);
|
addressId:
|
||||||
|
_transaction
|
||||||
|
.address
|
||||||
|
.value!
|
||||||
|
.id,
|
||||||
|
walletId: widget
|
||||||
|
.walletId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Navigator.of(context)
|
||||||
|
.pushNamed(
|
||||||
|
AddressDetailsView
|
||||||
|
.routeName,
|
||||||
|
arguments: Tuple2(
|
||||||
|
_transaction.address
|
||||||
|
.value!.id,
|
||||||
|
widget.walletId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -1012,6 +1033,7 @@ class _TransactionDetailsViewState
|
||||||
final String height;
|
final String height;
|
||||||
|
|
||||||
if (widget.coin == Coin.bitcoincash ||
|
if (widget.coin == Coin.bitcoincash ||
|
||||||
|
widget.coin == Coin.eCash ||
|
||||||
widget.coin == Coin.bitcoincashTestnet) {
|
widget.coin == Coin.bitcoincashTestnet) {
|
||||||
height =
|
height =
|
||||||
"${_transaction.height != null && _transaction.height! > 0 ? _transaction.height! : "Pending"}";
|
"${_transaction.height != null && _transaction.height! > 0 ? _transaction.height! : "Pending"}";
|
||||||
|
@ -1129,6 +1151,46 @@ class _TransactionDetailsViewState
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (kDebugMode)
|
||||||
|
isDesktop
|
||||||
|
? const _Divider()
|
||||||
|
: const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
if (kDebugMode)
|
||||||
|
RoundedWhiteContainer(
|
||||||
|
padding: isDesktop
|
||||||
|
? const EdgeInsets.all(16)
|
||||||
|
: const EdgeInsets.all(12),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Tx sub type",
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles
|
||||||
|
.desktopTextExtraExtraSmall(
|
||||||
|
context)
|
||||||
|
: STextStyles.itemSubtitle(context),
|
||||||
|
),
|
||||||
|
SelectableText(
|
||||||
|
_transaction.subType.toString(),
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles
|
||||||
|
.desktopTextExtraExtraSmall(
|
||||||
|
context)
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textDark,
|
||||||
|
)
|
||||||
|
: STextStyles.itemSubtitle12(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
isDesktop
|
isDesktop
|
||||||
? const _Divider()
|
? const _Divider()
|
||||||
: const SizedBox(
|
: const SizedBox(
|
||||||
|
|
|
@ -6,7 +6,7 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
|
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
|
||||||
import 'package:stackwallet/providers/providers.dart';
|
import 'package:stackwallet/providers/providers.dart';
|
||||||
import 'package:stackwallet/services/coins/manager.dart';
|
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||||
import 'package:stackwallet/themes/coin_icon_provider.dart';
|
import 'package:stackwallet/themes/coin_icon_provider.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||||
|
@ -24,13 +24,11 @@ class FavoriteCard extends ConsumerStatefulWidget {
|
||||||
required this.walletId,
|
required this.walletId,
|
||||||
required this.width,
|
required this.width,
|
||||||
required this.height,
|
required this.height,
|
||||||
required this.managerProvider,
|
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final String walletId;
|
final String walletId;
|
||||||
final double width;
|
final double width;
|
||||||
final double height;
|
final double height;
|
||||||
final ChangeNotifierProvider<Manager> managerProvider;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<FavoriteCard> createState() => _FavoriteCardState();
|
ConsumerState<FavoriteCard> createState() => _FavoriteCardState();
|
||||||
|
@ -38,15 +36,10 @@ class FavoriteCard extends ConsumerStatefulWidget {
|
||||||
|
|
||||||
class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
||||||
late final String walletId;
|
late final String walletId;
|
||||||
late final ChangeNotifierProvider<Manager> managerProvider;
|
|
||||||
|
|
||||||
Amount _cachedBalance = Amount.zero;
|
|
||||||
Amount _cachedFiatValue = Amount.zero;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
walletId = widget.walletId;
|
walletId = widget.walletId;
|
||||||
managerProvider = widget.managerProvider;
|
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
@ -55,9 +48,13 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final coin = ref.watch(managerProvider.select((value) => value.coin));
|
final coin = ref.watch(
|
||||||
|
walletsChangeNotifierProvider
|
||||||
|
.select((value) => value.getManager(walletId).coin),
|
||||||
|
);
|
||||||
final externalCalls = ref.watch(
|
final externalCalls = ref.watch(
|
||||||
prefsChangeNotifierProvider.select((value) => value.externalCalls));
|
prefsChangeNotifierProvider.select((value) => value.externalCalls),
|
||||||
|
);
|
||||||
|
|
||||||
return ConditionalParent(
|
return ConditionalParent(
|
||||||
condition: Util.isDesktop,
|
condition: Util.isDesktop,
|
||||||
|
@ -109,7 +106,10 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (coin == Coin.monero || coin == Coin.wownero) {
|
if (coin == Coin.monero || coin == Coin.wownero) {
|
||||||
await ref.read(managerProvider).initializeExisting();
|
await ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.getManager(walletId)
|
||||||
|
.initializeExisting();
|
||||||
}
|
}
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
if (Util.isDesktop) {
|
if (Util.isDesktop) {
|
||||||
|
@ -122,7 +122,9 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
||||||
WalletView.routeName,
|
WalletView.routeName,
|
||||||
arguments: Tuple2(
|
arguments: Tuple2(
|
||||||
walletId,
|
walletId,
|
||||||
managerProvider,
|
ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.getManagerProvider(walletId),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -205,8 +207,12 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
ref.watch(managerProvider
|
ref.watch(
|
||||||
.select((value) => value.walletName)),
|
walletsChangeNotifierProvider.select(
|
||||||
|
(value) =>
|
||||||
|
value.getManager(walletId).walletName,
|
||||||
|
),
|
||||||
|
),
|
||||||
style: STextStyles.itemSubtitle12(context).copyWith(
|
style: STextStyles.itemSubtitle12(context).copyWith(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
|
@ -225,41 +231,54 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
FutureBuilder(
|
Builder(
|
||||||
future: Future(() => ref.watch(managerProvider
|
builder: (context) {
|
||||||
.select((value) => value.balance.total))),
|
final balance = ref.watch(
|
||||||
builder: (builderContext, AsyncSnapshot<Amount> snapshot) {
|
walletsChangeNotifierProvider.select(
|
||||||
if (snapshot.connectionState == ConnectionState.done &&
|
(value) => value.getManager(walletId).balance,
|
||||||
snapshot.hasData) {
|
),
|
||||||
if (snapshot.data != null) {
|
);
|
||||||
_cachedBalance = snapshot.data!;
|
|
||||||
if (externalCalls && _cachedBalance > Amount.zero) {
|
Amount total = balance.total;
|
||||||
_cachedFiatValue = (_cachedBalance.decimal *
|
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||||
ref
|
final balancePrivate = ref.watch(
|
||||||
.watch(
|
walletsChangeNotifierProvider.select(
|
||||||
priceAnd24hChangeNotifierProvider
|
(value) => (value.getManager(walletId).wallet
|
||||||
.select(
|
as FiroWallet)
|
||||||
(value) => value.getPrice(coin),
|
.balancePrivate,
|
||||||
),
|
),
|
||||||
)
|
);
|
||||||
.item1)
|
|
||||||
.toAmount(fractionDigits: 2);
|
total += balancePrivate.total;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Amount fiatTotal = Amount.zero;
|
||||||
|
|
||||||
|
if (externalCalls && total > Amount.zero) {
|
||||||
|
fiatTotal = (total.decimal *
|
||||||
|
ref
|
||||||
|
.watch(
|
||||||
|
priceAnd24hChangeNotifierProvider.select(
|
||||||
|
(value) => value.getPrice(coin),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.item1)
|
||||||
|
.toAmount(fractionDigits: 2);
|
||||||
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
FittedBox(
|
FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
child: Text(
|
child: Text(
|
||||||
"${_cachedBalance.localizedStringAsFixed(
|
"${total.localizedStringAsFixed(
|
||||||
locale: ref.watch(
|
locale: ref.watch(
|
||||||
localeServiceChangeNotifierProvider
|
localeServiceChangeNotifierProvider.select(
|
||||||
.select((value) => value.locale),
|
(value) => value.locale,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
decimalPlaces: ref.watch(managerProvider
|
decimalPlaces: coin.decimals,
|
||||||
.select((value) => value.coin.decimals)),
|
|
||||||
)} ${coin.ticker}",
|
)} ${coin.ticker}",
|
||||||
style: STextStyles.titleBold12(context).copyWith(
|
style: STextStyles.titleBold12(context).copyWith(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
@ -275,15 +294,17 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
||||||
),
|
),
|
||||||
if (externalCalls)
|
if (externalCalls)
|
||||||
Text(
|
Text(
|
||||||
"${_cachedFiatValue.localizedStringAsFixed(
|
"${fiatTotal.localizedStringAsFixed(
|
||||||
locale: ref.watch(
|
locale: ref.watch(
|
||||||
localeServiceChangeNotifierProvider
|
localeServiceChangeNotifierProvider.select(
|
||||||
.select((value) => value.locale),
|
(value) => value.locale,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
decimalPlaces: 2,
|
decimalPlaces: 2,
|
||||||
)} ${ref.watch(
|
)} ${ref.watch(
|
||||||
prefsChangeNotifierProvider
|
prefsChangeNotifierProvider.select(
|
||||||
.select((value) => value.currency),
|
(value) => value.currency,
|
||||||
|
),
|
||||||
)}",
|
)}",
|
||||||
style:
|
style:
|
||||||
STextStyles.itemSubtitle12(context).copyWith(
|
STextStyles.itemSubtitle12(context).copyWith(
|
||||||
|
|
|
@ -211,7 +211,6 @@ class _FavoriteWalletsState extends ConsumerState<FavoriteWallets> {
|
||||||
child: FavoriteCard(
|
child: FavoriteCard(
|
||||||
key: Key("favCard_$walletId"),
|
key: Key("favCard_$walletId"),
|
||||||
walletId: walletId!,
|
walletId: walletId!,
|
||||||
managerProvider: managerProvider!,
|
|
||||||
width: cardWidth,
|
width: cardWidth,
|
||||||
height: cardHeight,
|
height: cardHeight,
|
||||||
),
|
),
|
||||||
|
@ -219,7 +218,6 @@ class _FavoriteWalletsState extends ConsumerState<FavoriteWallets> {
|
||||||
: FavoriteCard(
|
: FavoriteCard(
|
||||||
key: Key("favCard_$walletId"),
|
key: Key("favCard_$walletId"),
|
||||||
walletId: walletId!,
|
walletId: walletId!,
|
||||||
managerProvider: managerProvider!,
|
|
||||||
width: cardWidth,
|
width: cardWidth,
|
||||||
height: cardHeight,
|
height: cardHeight,
|
||||||
)
|
)
|
||||||
|
|
|
@ -74,7 +74,6 @@ class DesktopFavoriteWallets extends ConsumerWidget {
|
||||||
key: Key(walletName),
|
key: Key(walletName),
|
||||||
width: cardWidth,
|
width: cardWidth,
|
||||||
height: cardHeight,
|
height: cardHeight,
|
||||||
managerProvider: managerProvider,
|
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
|
|
|
@ -58,6 +58,8 @@ class _NodesSettings extends ConsumerState<NodesSettings> {
|
||||||
if (Platform.isWindows) {
|
if (Platform.isWindows) {
|
||||||
_coins.remove(Coin.monero);
|
_coins.remove(Coin.monero);
|
||||||
_coins.remove(Coin.wownero);
|
_coins.remove(Coin.wownero);
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
_coins.remove(Coin.wownero);
|
||||||
}
|
}
|
||||||
|
|
||||||
searchNodeController = TextEditingController();
|
searchNodeController = TextEditingController();
|
||||||
|
|
|
@ -47,7 +47,7 @@ import 'package:stackwallet/widgets/crypto_notifications.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
const int MINIMUM_CONFIRMATIONS = 1;
|
const int MINIMUM_CONFIRMATIONS = 0;
|
||||||
|
|
||||||
const String GENESIS_HASH_MAINNET =
|
const String GENESIS_HASH_MAINNET =
|
||||||
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
|
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
|
||||||
|
@ -361,9 +361,9 @@ class ECashWallet extends CoinServiceAPI
|
||||||
print("format $format");
|
print("format $format");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_coin == Coin.bitcoincashTestnet) {
|
// if (_coin == Coin.bitcoincashTestnet) {
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (format == bitbox.Address.formatCashAddr) {
|
if (format == bitbox.Address.formatCashAddr) {
|
||||||
return validateCashAddr(address);
|
return validateCashAddr(address);
|
||||||
|
@ -1897,7 +1897,7 @@ class ECashWallet extends CoinServiceAPI
|
||||||
required List<int> satoshiAmounts,
|
required List<int> satoshiAmounts,
|
||||||
}) async {
|
}) async {
|
||||||
final builder = bitbox.Bitbox.transactionBuilder(
|
final builder = bitbox.Bitbox.transactionBuilder(
|
||||||
testnet: coin == Coin.bitcoincashTestnet,
|
testnet: false, //coin == Coin.bitcoincashTestnet,
|
||||||
);
|
);
|
||||||
|
|
||||||
// retrieve address' utxos from the rest api
|
// retrieve address' utxos from the rest api
|
||||||
|
|
|
@ -405,7 +405,7 @@ Future<Map<dynamic, dynamic>> staticProcessRestore(
|
||||||
tx = null;
|
tx = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tx == null) {
|
if (tx == null || tx.subType == isar_models.TransactionSubType.join) {
|
||||||
// This is a jmint.
|
// This is a jmint.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1928,12 +1928,13 @@ class FiroWallet extends CoinServiceAPI
|
||||||
if ((await db
|
if ((await db
|
||||||
.getTransactions(walletId)
|
.getTransactions(walletId)
|
||||||
.filter()
|
.filter()
|
||||||
.txidMatches(txid)
|
.txidEqualTo(txid)
|
||||||
.findFirst()) ==
|
.count()) ==
|
||||||
null) {
|
0) {
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
" txid not found in address history already ${transaction['tx_hash']}",
|
" txid not found in address history already ${transaction['tx_hash']}",
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
needsRefresh = true;
|
needsRefresh = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1956,79 +1957,27 @@ class FiroWallet extends CoinServiceAPI
|
||||||
|
|
||||||
final currentChainHeight = await chainHeight;
|
final currentChainHeight = await chainHeight;
|
||||||
|
|
||||||
final txTxns = await db
|
final txCount = await db.getTransactions(walletId).count();
|
||||||
.getTransactions(walletId)
|
|
||||||
.filter()
|
|
||||||
.isLelantusIsNull()
|
|
||||||
.or()
|
|
||||||
.isLelantusEqualTo(false)
|
|
||||||
.findAll();
|
|
||||||
final ltxTxns = await db
|
|
||||||
.getTransactions(walletId)
|
|
||||||
.filter()
|
|
||||||
.isLelantusEqualTo(true)
|
|
||||||
.findAll();
|
|
||||||
|
|
||||||
for (isar_models.Transaction tx in txTxns) {
|
const paginateLimit = 50;
|
||||||
isar_models.Transaction? lTx;
|
|
||||||
try {
|
|
||||||
lTx = ltxTxns.firstWhere((e) => e.txid == tx.txid);
|
|
||||||
} catch (_) {
|
|
||||||
lTx = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) {
|
for (int i = 0; i < txCount; i += paginateLimit) {
|
||||||
if (txTracker.wasNotifiedPending(tx.txid) &&
|
final transactions = await db
|
||||||
!txTracker.wasNotifiedConfirmed(tx.txid)) {
|
.getTransactions(walletId)
|
||||||
|
.offset(i)
|
||||||
|
.limit(paginateLimit)
|
||||||
|
.findAll();
|
||||||
|
for (final tx in transactions) {
|
||||||
|
if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) {
|
||||||
// get all transactions that were notified as pending but not as confirmed
|
// get all transactions that were notified as pending but not as confirmed
|
||||||
unconfirmedTxnsToNotifyConfirmed.add(tx);
|
if (txTracker.wasNotifiedPending(tx.txid) &&
|
||||||
}
|
!txTracker.wasNotifiedConfirmed(tx.txid)) {
|
||||||
if (lTx != null &&
|
unconfirmedTxnsToNotifyConfirmed.add(tx);
|
||||||
(lTx.inputs.isEmpty || lTx.inputs.first.txid.isEmpty) &&
|
|
||||||
lTx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) ==
|
|
||||||
false &&
|
|
||||||
tx.type == isar_models.TransactionType.incoming) {
|
|
||||||
// If this is a received that is past 1 or more confirmations and has not been minted,
|
|
||||||
if (!txTracker.wasNotifiedPending(tx.txid)) {
|
|
||||||
unconfirmedTxnsToNotifyPending.add(tx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!txTracker.wasNotifiedPending(tx.txid)) {
|
|
||||||
// get all transactions that were not notified as pending yet
|
|
||||||
unconfirmedTxnsToNotifyPending.add(tx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (isar_models.Transaction tx in txTxns) {
|
|
||||||
if (!tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) &&
|
|
||||||
tx.inputs.first.txid.isNotEmpty) {
|
|
||||||
// Get all normal txs that are at 0 confirmations
|
|
||||||
unconfirmedTxnsToNotifyPending
|
|
||||||
.removeWhere((e) => e.txid == tx.inputs.first.txid);
|
|
||||||
Logging.instance.log("removed tx: ${tx.txid}", level: LogLevel.Info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (isar_models.Transaction lTX in ltxTxns) {
|
|
||||||
isar_models.Transaction? tx;
|
|
||||||
try {
|
|
||||||
tx = ltxTxns.firstWhere((e) => e.txid == lTX.txid);
|
|
||||||
} catch (_) {
|
|
||||||
tx = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tx == null) {
|
|
||||||
// if this is a ltx transaction that is unconfirmed and not represented in the normal transaction set.
|
|
||||||
if (!lTX.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) {
|
|
||||||
if (!txTracker.wasNotifiedPending(lTX.txid)) {
|
|
||||||
unconfirmedTxnsToNotifyPending.add(lTX);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (txTracker.wasNotifiedPending(lTX.txid) &&
|
// get all transactions that were not notified as pending yet
|
||||||
!txTracker.wasNotifiedConfirmed(lTX.txid)) {
|
if (!txTracker.wasNotifiedPending(tx.txid)) {
|
||||||
unconfirmedTxnsToNotifyConfirmed.add(lTX);
|
unconfirmedTxnsToNotifyPending.add(tx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2604,7 +2553,7 @@ class FiroWallet extends CoinServiceAPI
|
||||||
var tmpTotal = total;
|
var tmpTotal = total;
|
||||||
var index = 1;
|
var index = 1;
|
||||||
var mints = <Map<String, dynamic>>[];
|
var mints = <Map<String, dynamic>>[];
|
||||||
final nextFreeMintIndex = firoGetMintIndex()!;
|
final nextFreeMintIndex = firoGetMintIndex();
|
||||||
while (tmpTotal > 0) {
|
while (tmpTotal > 0) {
|
||||||
final mintValue = min(tmpTotal, MINT_LIMIT);
|
final mintValue = min(tmpTotal, MINT_LIMIT);
|
||||||
final mint = await _getMintHex(
|
final mint = await _getMintHex(
|
||||||
|
@ -2769,7 +2718,7 @@ class FiroWallet extends CoinServiceAPI
|
||||||
amount += utxosToUse[i].value;
|
amount += utxosToUse[i].value;
|
||||||
}
|
}
|
||||||
|
|
||||||
final index = firoGetMintIndex()!;
|
final index = firoGetMintIndex();
|
||||||
Logging.instance.log("index of mint $index", level: LogLevel.Info);
|
Logging.instance.log("index of mint $index", level: LogLevel.Info);
|
||||||
|
|
||||||
for (var mintsElement in mintsMap) {
|
for (var mintsElement in mintsMap) {
|
||||||
|
@ -3036,7 +2985,7 @@ class FiroWallet extends CoinServiceAPI
|
||||||
|
|
||||||
// if a jmint was made add it to the unspent coin index
|
// if a jmint was made add it to the unspent coin index
|
||||||
LelantusCoin jmint = LelantusCoin(
|
LelantusCoin jmint = LelantusCoin(
|
||||||
index!,
|
index,
|
||||||
transactionInfo['jmintValue'] as int? ?? 0,
|
transactionInfo['jmintValue'] as int? ?? 0,
|
||||||
transactionInfo['publicCoin'] as String,
|
transactionInfo['publicCoin'] as String,
|
||||||
transactionInfo['txid'] as String,
|
transactionInfo['txid'] as String,
|
||||||
|
@ -3213,17 +3162,18 @@ class FiroWallet extends CoinServiceAPI
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO call get transaction and check each tx to see if it is a "received" tx?
|
Future<int> _getTxCount({required String address}) async {
|
||||||
Future<int> _getReceivedTxCount({required String address}) async {
|
|
||||||
try {
|
try {
|
||||||
final scripthash = AddressUtils.convertToScriptHash(address, _network);
|
final scriptHash = AddressUtils.convertToScriptHash(address, _network);
|
||||||
final transactions =
|
final transactions = await electrumXClient.getHistory(
|
||||||
await electrumXClient.getHistory(scripthash: scripthash);
|
scripthash: scriptHash,
|
||||||
|
);
|
||||||
return transactions.length;
|
return transactions.length;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
"Exception rethrown in _getReceivedTxCount(address: $address): $e",
|
"Exception rethrown in _getReceivedTxCount(address: $address): $e",
|
||||||
level: LogLevel.Error);
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3232,8 +3182,7 @@ class FiroWallet extends CoinServiceAPI
|
||||||
try {
|
try {
|
||||||
final currentReceiving = await _currentReceivingAddress;
|
final currentReceiving = await _currentReceivingAddress;
|
||||||
|
|
||||||
final int txCount =
|
final int txCount = await _getTxCount(address: currentReceiving.value);
|
||||||
await _getReceivedTxCount(address: currentReceiving.value);
|
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
'Number of txs for current receiving address $currentReceiving: $txCount',
|
'Number of txs for current receiving address $currentReceiving: $txCount',
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info);
|
||||||
|
@ -3279,8 +3228,7 @@ class FiroWallet extends CoinServiceAPI
|
||||||
Future<void> checkChangeAddressForTransactions() async {
|
Future<void> checkChangeAddressForTransactions() async {
|
||||||
try {
|
try {
|
||||||
final currentChange = await _currentChangeAddress;
|
final currentChange = await _currentChangeAddress;
|
||||||
final int txCount =
|
final int txCount = await _getTxCount(address: currentChange.value);
|
||||||
await _getReceivedTxCount(address: currentChange.value);
|
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
'Number of txs for current change address: $currentChange: $txCount',
|
'Number of txs for current change address: $currentChange: $txCount',
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info);
|
||||||
|
@ -3328,27 +3276,13 @@ class FiroWallet extends CoinServiceAPI
|
||||||
.getAddresses(walletId)
|
.getAddresses(walletId)
|
||||||
.filter()
|
.filter()
|
||||||
.not()
|
.not()
|
||||||
.typeEqualTo(isar_models.AddressType.nonWallet)
|
.group(
|
||||||
.and()
|
(q) => q
|
||||||
.group((q) => q
|
.typeEqualTo(isar_models.AddressType.nonWallet)
|
||||||
.subTypeEqualTo(isar_models.AddressSubType.receiving)
|
.or()
|
||||||
.or()
|
.subTypeEqualTo(isar_models.AddressSubType.nonWallet),
|
||||||
.subTypeEqualTo(isar_models.AddressSubType.change))
|
)
|
||||||
.findAll();
|
.findAll();
|
||||||
// final List<String> allAddresses = [];
|
|
||||||
// final receivingAddresses =
|
|
||||||
// DB.instance.get<dynamic>(boxName: walletId, key: 'receivingAddresses')
|
|
||||||
// as List<dynamic>;
|
|
||||||
// final changeAddresses =
|
|
||||||
// DB.instance.get<dynamic>(boxName: walletId, key: 'changeAddresses')
|
|
||||||
// as List<dynamic>;
|
|
||||||
//
|
|
||||||
// for (var i = 0; i < receivingAddresses.length; i++) {
|
|
||||||
// allAddresses.add(receivingAddresses[i] as String);
|
|
||||||
// }
|
|
||||||
// for (var i = 0; i < changeAddresses.length; i++) {
|
|
||||||
// allAddresses.add(changeAddresses[i] as String);
|
|
||||||
// }
|
|
||||||
return allAddresses;
|
return allAddresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3411,252 +3345,451 @@ class FiroWallet extends CoinServiceAPI
|
||||||
final List<isar_models.Address> allAddresses =
|
final List<isar_models.Address> allAddresses =
|
||||||
await _fetchAllOwnAddresses();
|
await _fetchAllOwnAddresses();
|
||||||
|
|
||||||
final List<Map<String, dynamic>> allTxHashes =
|
Set<String> receivingAddresses = allAddresses
|
||||||
await _fetchHistory(allAddresses.map((e) => e.value).toList());
|
.where((e) => e.subType == isar_models.AddressSubType.receiving)
|
||||||
|
.map((e) => e.value)
|
||||||
List<Map<String, dynamic>> allTransactions = [];
|
.toSet();
|
||||||
|
|
||||||
final currentHeight = await chainHeight;
|
|
||||||
|
|
||||||
for (final txHash in allTxHashes) {
|
|
||||||
final storedTx = await db
|
|
||||||
.getTransactions(walletId)
|
|
||||||
.filter()
|
|
||||||
.txidEqualTo(txHash["tx_hash"] as String)
|
|
||||||
.findFirst();
|
|
||||||
|
|
||||||
if (storedTx == null ||
|
|
||||||
!storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) {
|
|
||||||
final tx = await cachedElectrumXClient.getTransaction(
|
|
||||||
txHash: txHash["tx_hash"] as String,
|
|
||||||
verbose: true,
|
|
||||||
coin: coin,
|
|
||||||
);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<Tuple2<isar_models.Transaction, isar_models.Address?>> txnsData =
|
|
||||||
[];
|
|
||||||
|
|
||||||
Set<String> changeAddresses = allAddresses
|
Set<String> changeAddresses = allAddresses
|
||||||
.where((e) => e.subType == isar_models.AddressSubType.change)
|
.where((e) => e.subType == isar_models.AddressSubType.change)
|
||||||
.map((e) => e.value)
|
.map((e) => e.value)
|
||||||
.toSet();
|
.toSet();
|
||||||
|
|
||||||
for (final txObject in allTransactions) {
|
final List<Map<String, dynamic>> allTxHashes =
|
||||||
// Logging.instance.log(txObject);
|
await _fetchHistory(allAddresses.map((e) => e.value).toList());
|
||||||
List<String> sendersArray = [];
|
|
||||||
List<String> recipientsArray = [];
|
|
||||||
|
|
||||||
// Usually only has value when txType = 'Send'
|
List<Map<String, dynamic>> allTransactions = [];
|
||||||
int inputAmtSentFromWallet = 0;
|
|
||||||
// Usually has value regardless of txType due to change addresses
|
|
||||||
int outputAmtAddressedToWallet = 0;
|
|
||||||
|
|
||||||
for (final input in txObject["vin"] as List) {
|
// final currentHeight = await chainHeight;
|
||||||
final address = input["address"] as String?;
|
|
||||||
if (address != null) {
|
|
||||||
sendersArray.add(address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logging.instance.log("sendersArray: $sendersArray");
|
for (final txHash in allTxHashes) {
|
||||||
|
// final storedTx = await db
|
||||||
|
// .getTransactions(walletId)
|
||||||
|
// .filter()
|
||||||
|
// .txidEqualTo(txHash["tx_hash"] as String)
|
||||||
|
// .findFirst();
|
||||||
|
|
||||||
for (final output in txObject["vout"] as List) {
|
// if (storedTx == null ||
|
||||||
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
// !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) {
|
||||||
output["scriptPubKey"]?["address"] as String?;
|
final tx = await cachedElectrumXClient.getTransaction(
|
||||||
if (address != null) {
|
txHash: txHash["tx_hash"] as String,
|
||||||
recipientsArray.add(address);
|
verbose: true,
|
||||||
}
|
coin: coin,
|
||||||
}
|
|
||||||
// Logging.instance.log("recipientsArray: $recipientsArray");
|
|
||||||
|
|
||||||
final foundInSenders =
|
|
||||||
allAddresses.any((element) => sendersArray.contains(element.value));
|
|
||||||
// Logging.instance.log("foundInSenders: $foundInSenders");
|
|
||||||
|
|
||||||
String outAddress = "";
|
|
||||||
|
|
||||||
int fees = 0;
|
|
||||||
|
|
||||||
// If txType = Sent, then calculate inputAmtSentFromWallet, calculate who received how much in aliens array (check outputs)
|
|
||||||
if (foundInSenders) {
|
|
||||||
int outAmount = 0;
|
|
||||||
int inAmount = 0;
|
|
||||||
bool nFeesUsed = false;
|
|
||||||
|
|
||||||
for (final input in txObject["vin"] as List) {
|
|
||||||
final nFees = input["nFees"];
|
|
||||||
if (nFees != null) {
|
|
||||||
nFeesUsed = true;
|
|
||||||
fees = (Decimal.parse(nFees.toString()) *
|
|
||||||
Decimal.fromInt(Constants.satsPerCoin(coin)))
|
|
||||||
.toBigInt()
|
|
||||||
.toInt();
|
|
||||||
}
|
|
||||||
final address = input["address"] as String?;
|
|
||||||
final value = input["valueSat"] as int?;
|
|
||||||
if (address != null && value != null) {
|
|
||||||
if (allAddresses.where((e) => e.value == address).isNotEmpty) {
|
|
||||||
inputAmtSentFromWallet += value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value != null) {
|
|
||||||
inAmount += value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final output in txObject["vout"] as List) {
|
|
||||||
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
|
||||||
output["scriptPubKey"]?["address"] as String?;
|
|
||||||
final value = output["value"];
|
|
||||||
|
|
||||||
if (value != null) {
|
|
||||||
outAmount += (Decimal.parse(value.toString()) *
|
|
||||||
Decimal.fromInt(Constants.satsPerCoin(coin)))
|
|
||||||
.toBigInt()
|
|
||||||
.toInt();
|
|
||||||
|
|
||||||
if (address != null) {
|
|
||||||
if (changeAddresses.contains(address)) {
|
|
||||||
inputAmtSentFromWallet -= (Decimal.parse(value.toString()) *
|
|
||||||
Decimal.fromInt(Constants.satsPerCoin(coin)))
|
|
||||||
.toBigInt()
|
|
||||||
.toInt();
|
|
||||||
} else {
|
|
||||||
outAddress = address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fees = nFeesUsed ? fees : inAmount - outAmount;
|
|
||||||
inputAmtSentFromWallet -= inAmount - outAmount;
|
|
||||||
} else {
|
|
||||||
for (final input in txObject["vin"] as List) {
|
|
||||||
final nFees = input["nFees"];
|
|
||||||
if (nFees != null) {
|
|
||||||
fees += (Decimal.parse(nFees.toString()) *
|
|
||||||
Decimal.fromInt(Constants.satsPerCoin(coin)))
|
|
||||||
.toBigInt()
|
|
||||||
.toInt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final output in txObject["vout"] as List) {
|
|
||||||
final addresses = output["scriptPubKey"]["addresses"] as List?;
|
|
||||||
if (addresses != null && addresses.isNotEmpty) {
|
|
||||||
final address = addresses[0] as String;
|
|
||||||
final value = output["value"] ?? 0;
|
|
||||||
// Logging.instance.log(address + value.toString());
|
|
||||||
|
|
||||||
if (allAddresses.where((e) => e.value == address).isNotEmpty) {
|
|
||||||
outputAmtAddressedToWallet += (Decimal.parse(value.toString()) *
|
|
||||||
Decimal.fromInt(Constants.satsPerCoin(coin)))
|
|
||||||
.toBigInt()
|
|
||||||
.toInt();
|
|
||||||
outAddress = address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isar_models.TransactionType type;
|
|
||||||
isar_models.TransactionSubType subType =
|
|
||||||
isar_models.TransactionSubType.none;
|
|
||||||
int amount;
|
|
||||||
if (foundInSenders) {
|
|
||||||
type = isar_models.TransactionType.outgoing;
|
|
||||||
amount = inputAmtSentFromWallet;
|
|
||||||
|
|
||||||
if (txObject["vout"][0]["scriptPubKey"]["type"] == "lelantusmint") {
|
|
||||||
subType = isar_models.TransactionSubType.mint;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
type = isar_models.TransactionType.incoming;
|
|
||||||
amount = outputAmtAddressedToWallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
final transactionAddress =
|
|
||||||
allAddresses.firstWhere((e) => e.value == outAddress,
|
|
||||||
orElse: () => isar_models.Address(
|
|
||||||
walletId: walletId,
|
|
||||||
value: outAddress,
|
|
||||||
derivationIndex: -1,
|
|
||||||
derivationPath: null,
|
|
||||||
type: isar_models.AddressType.nonWallet,
|
|
||||||
subType: isar_models.AddressSubType.nonWallet,
|
|
||||||
publicKey: [],
|
|
||||||
));
|
|
||||||
|
|
||||||
List<isar_models.Output> outs = [];
|
|
||||||
List<isar_models.Input> ins = [];
|
|
||||||
|
|
||||||
for (final json in txObject["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?,
|
|
||||||
);
|
|
||||||
ins.add(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final json in txObject["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(),
|
|
||||||
);
|
|
||||||
outs.add(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
final tx = isar_models.Transaction(
|
|
||||||
walletId: walletId,
|
|
||||||
txid: txObject["txid"] as String,
|
|
||||||
timestamp: txObject["blocktime"] as int? ??
|
|
||||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
|
||||||
type: type,
|
|
||||||
subType: subType,
|
|
||||||
amount: amount,
|
|
||||||
amountString: Amount(
|
|
||||||
rawValue: BigInt.from(amount),
|
|
||||||
fractionDigits: Coin.firo.decimals,
|
|
||||||
).toJsonString(),
|
|
||||||
fee: fees,
|
|
||||||
height: txObject["height"] as int? ?? 0,
|
|
||||||
isCancelled: false,
|
|
||||||
isLelantus: false,
|
|
||||||
slateId: null,
|
|
||||||
otherData: null,
|
|
||||||
nonce: null,
|
|
||||||
inputs: ins,
|
|
||||||
outputs: outs,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
txnsData.add(Tuple2(tx, transactionAddress));
|
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);
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Tuple2<isar_models.Transaction, isar_models.Address?>> txnsData =
|
||||||
|
[];
|
||||||
|
|
||||||
|
for (final txObject in allTransactions) {
|
||||||
|
final inputList = txObject["vin"] as List;
|
||||||
|
final outputList = txObject["vout"] as List;
|
||||||
|
|
||||||
|
bool isMint = false;
|
||||||
|
bool isJMint = false;
|
||||||
|
|
||||||
|
// check if tx is Mint or jMint
|
||||||
|
for (final output in outputList) {
|
||||||
|
if (output["scriptPubKey"]?["type"] == "lelantusmint") {
|
||||||
|
final asm = output["scriptPubKey"]?["asm"] as String?;
|
||||||
|
if (asm != null) {
|
||||||
|
if (asm.startsWith("OP_LELANTUSJMINT")) {
|
||||||
|
isJMint = true;
|
||||||
|
break;
|
||||||
|
} else if (asm.startsWith("OP_LELANTUSMINT")) {
|
||||||
|
isMint = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
Logging.instance.log(
|
||||||
|
"Unknown mint op code found for lelantusmint tx: ${txObject["txid"]}",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logging.instance.log(
|
||||||
|
"ASM for lelantusmint tx: ${txObject["txid"]} is null!",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> inputAddresses = {};
|
||||||
|
Set<String> outputAddresses = {};
|
||||||
|
|
||||||
|
Amount totalInputValue = Amount(
|
||||||
|
rawValue: BigInt.zero,
|
||||||
|
fractionDigits: coin.decimals,
|
||||||
|
);
|
||||||
|
Amount totalOutputValue = Amount(
|
||||||
|
rawValue: BigInt.zero,
|
||||||
|
fractionDigits: coin.decimals,
|
||||||
|
);
|
||||||
|
|
||||||
|
Amount amountSentFromWallet = Amount(
|
||||||
|
rawValue: BigInt.zero,
|
||||||
|
fractionDigits: coin.decimals,
|
||||||
|
);
|
||||||
|
Amount amountReceivedInWallet = Amount(
|
||||||
|
rawValue: BigInt.zero,
|
||||||
|
fractionDigits: coin.decimals,
|
||||||
|
);
|
||||||
|
Amount changeAmount = Amount(
|
||||||
|
rawValue: BigInt.zero,
|
||||||
|
fractionDigits: coin.decimals,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Parse mint transaction ================================================
|
||||||
|
// We should be able to assume this belongs to this wallet
|
||||||
|
if (isMint) {
|
||||||
|
List<isar_models.Input> ins = [];
|
||||||
|
|
||||||
|
// Parse inputs
|
||||||
|
for (final input in inputList) {
|
||||||
|
// Both value and address should not be null for a mint
|
||||||
|
final address = input["address"] as String?;
|
||||||
|
final value = input["valueSat"] as int?;
|
||||||
|
|
||||||
|
// We should not need to check whether the mint belongs to this
|
||||||
|
// wallet as any tx we look up will be looked up by one of this
|
||||||
|
// wallet's addresses
|
||||||
|
if (address != null && value != null) {
|
||||||
|
totalInputValue += value.toAmountAsRaw(
|
||||||
|
fractionDigits: coin.decimals,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ins.add(
|
||||||
|
isar_models.Input(
|
||||||
|
txid: input['txid'] as String? ?? "",
|
||||||
|
vout: input['vout'] as int? ?? -1,
|
||||||
|
scriptSig: input['scriptSig']?['hex'] as String?,
|
||||||
|
scriptSigAsm: input['scriptSig']?['asm'] as String?,
|
||||||
|
isCoinbase: input['is_coinbase'] as bool?,
|
||||||
|
sequence: input['sequence'] as int?,
|
||||||
|
innerRedeemScriptAsm: input['innerRedeemscriptAsm'] as String?,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse outputs
|
||||||
|
for (final output in outputList) {
|
||||||
|
// get value
|
||||||
|
final value = Amount.fromDecimal(
|
||||||
|
Decimal.parse(output["value"].toString()),
|
||||||
|
fractionDigits: coin.decimals,
|
||||||
|
);
|
||||||
|
|
||||||
|
// add value to total
|
||||||
|
totalOutputValue += value;
|
||||||
|
}
|
||||||
|
|
||||||
|
final fee = totalInputValue - totalOutputValue;
|
||||||
|
final tx = isar_models.Transaction(
|
||||||
|
walletId: walletId,
|
||||||
|
txid: txObject["txid"] as String,
|
||||||
|
timestamp: txObject["blocktime"] as int? ??
|
||||||
|
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
||||||
|
type: isar_models.TransactionType.sentToSelf,
|
||||||
|
subType: isar_models.TransactionSubType.mint,
|
||||||
|
amount: totalOutputValue.raw.toInt(),
|
||||||
|
amountString: totalOutputValue.toJsonString(),
|
||||||
|
fee: fee.raw.toInt(),
|
||||||
|
height: txObject["height"] as int?,
|
||||||
|
isCancelled: false,
|
||||||
|
isLelantus: true,
|
||||||
|
slateId: null,
|
||||||
|
otherData: null,
|
||||||
|
nonce: null,
|
||||||
|
inputs: ins,
|
||||||
|
outputs: [],
|
||||||
|
);
|
||||||
|
|
||||||
|
txnsData.add(Tuple2(tx, null));
|
||||||
|
|
||||||
|
// Otherwise parse JMint transaction ===================================
|
||||||
|
} else if (isJMint) {
|
||||||
|
Amount jMintFees = Amount(
|
||||||
|
rawValue: BigInt.zero,
|
||||||
|
fractionDigits: coin.decimals,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Parse inputs
|
||||||
|
List<isar_models.Input> ins = [];
|
||||||
|
for (final input in inputList) {
|
||||||
|
// JMint fee
|
||||||
|
final nFee = Decimal.tryParse(input["nFees"].toString());
|
||||||
|
if (nFee != null) {
|
||||||
|
final fees = Amount.fromDecimal(
|
||||||
|
nFee,
|
||||||
|
fractionDigits: coin.decimals,
|
||||||
|
);
|
||||||
|
|
||||||
|
jMintFees += fees;
|
||||||
|
}
|
||||||
|
|
||||||
|
ins.add(
|
||||||
|
isar_models.Input(
|
||||||
|
txid: input['txid'] as String? ?? "",
|
||||||
|
vout: input['vout'] as int? ?? -1,
|
||||||
|
scriptSig: input['scriptSig']?['hex'] as String?,
|
||||||
|
scriptSigAsm: input['scriptSig']?['asm'] as String?,
|
||||||
|
isCoinbase: input['is_coinbase'] as bool?,
|
||||||
|
sequence: input['sequence'] as int?,
|
||||||
|
innerRedeemScriptAsm: input['innerRedeemscriptAsm'] as String?,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nonWalletAddressFoundInOutputs = false;
|
||||||
|
|
||||||
|
// Parse outputs
|
||||||
|
List<isar_models.Output> outs = [];
|
||||||
|
for (final output in outputList) {
|
||||||
|
// get value
|
||||||
|
final value = Amount.fromDecimal(
|
||||||
|
Decimal.parse(output["value"].toString()),
|
||||||
|
fractionDigits: coin.decimals,
|
||||||
|
);
|
||||||
|
|
||||||
|
// add value to total
|
||||||
|
totalOutputValue += value;
|
||||||
|
|
||||||
|
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||||
|
output['scriptPubKey']?['address'] as String?;
|
||||||
|
|
||||||
|
if (address != null) {
|
||||||
|
outputAddresses.add(address);
|
||||||
|
if (receivingAddresses.contains(address) ||
|
||||||
|
changeAddresses.contains(address)) {
|
||||||
|
amountReceivedInWallet += value;
|
||||||
|
} else {
|
||||||
|
nonWalletAddressFoundInOutputs = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outs.add(
|
||||||
|
isar_models.Output(
|
||||||
|
scriptPubKey: output['scriptPubKey']?['hex'] as String?,
|
||||||
|
scriptPubKeyAsm: output['scriptPubKey']?['asm'] as String?,
|
||||||
|
scriptPubKeyType: output['scriptPubKey']?['type'] as String?,
|
||||||
|
scriptPubKeyAddress: address ?? "jmint",
|
||||||
|
value: value.raw.toInt(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const subType = isar_models.TransactionSubType.join;
|
||||||
|
final type = nonWalletAddressFoundInOutputs
|
||||||
|
? isar_models.TransactionType.outgoing
|
||||||
|
: isar_models.TransactionType.incoming;
|
||||||
|
|
||||||
|
final amount = nonWalletAddressFoundInOutputs
|
||||||
|
? totalOutputValue
|
||||||
|
: amountReceivedInWallet;
|
||||||
|
|
||||||
|
final possibleNonWalletAddresses =
|
||||||
|
receivingAddresses.difference(outputAddresses);
|
||||||
|
final possibleReceivingAddresses =
|
||||||
|
receivingAddresses.intersection(outputAddresses);
|
||||||
|
|
||||||
|
final transactionAddress = nonWalletAddressFoundInOutputs
|
||||||
|
? isar_models.Address(
|
||||||
|
walletId: walletId,
|
||||||
|
value: possibleNonWalletAddresses.first,
|
||||||
|
derivationIndex: -1,
|
||||||
|
derivationPath: null,
|
||||||
|
type: isar_models.AddressType.nonWallet,
|
||||||
|
subType: isar_models.AddressSubType.nonWallet,
|
||||||
|
publicKey: [],
|
||||||
|
)
|
||||||
|
: allAddresses.firstWhere(
|
||||||
|
(e) => e.value == possibleReceivingAddresses.first,
|
||||||
|
);
|
||||||
|
|
||||||
|
final tx = isar_models.Transaction(
|
||||||
|
walletId: walletId,
|
||||||
|
txid: txObject["txid"] as String,
|
||||||
|
timestamp: txObject["blocktime"] as int? ??
|
||||||
|
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
||||||
|
type: type,
|
||||||
|
subType: subType,
|
||||||
|
amount: amount.raw.toInt(),
|
||||||
|
amountString: amount.toJsonString(),
|
||||||
|
fee: jMintFees.raw.toInt(),
|
||||||
|
height: txObject["height"] as int?,
|
||||||
|
isCancelled: false,
|
||||||
|
isLelantus: true,
|
||||||
|
slateId: null,
|
||||||
|
otherData: null,
|
||||||
|
nonce: null,
|
||||||
|
inputs: ins,
|
||||||
|
outputs: outs,
|
||||||
|
);
|
||||||
|
|
||||||
|
txnsData.add(Tuple2(tx, transactionAddress));
|
||||||
|
|
||||||
|
// Assume non lelantus transaction =====================================
|
||||||
|
} else {
|
||||||
|
// parse inputs
|
||||||
|
List<isar_models.Input> ins = [];
|
||||||
|
for (final input in inputList) {
|
||||||
|
final valueSat = input["valueSat"] as int?;
|
||||||
|
final address = input["address"] as String? ??
|
||||||
|
input["scriptPubKey"]?["address"] as String? ??
|
||||||
|
input["scriptPubKey"]?["addresses"]?[0] as String?;
|
||||||
|
|
||||||
|
if (address != null && valueSat != null) {
|
||||||
|
final value = valueSat.toAmountAsRaw(
|
||||||
|
fractionDigits: coin.decimals,
|
||||||
|
);
|
||||||
|
|
||||||
|
// add value to total
|
||||||
|
totalInputValue += value;
|
||||||
|
inputAddresses.add(address);
|
||||||
|
|
||||||
|
// if input was from my wallet, add value to amount sent
|
||||||
|
if (receivingAddresses.contains(address) ||
|
||||||
|
changeAddresses.contains(address)) {
|
||||||
|
amountSentFromWallet += value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ins.add(
|
||||||
|
isar_models.Input(
|
||||||
|
txid: input['txid'] as String,
|
||||||
|
vout: input['vout'] as int? ?? -1,
|
||||||
|
scriptSig: input['scriptSig']?['hex'] as String?,
|
||||||
|
scriptSigAsm: input['scriptSig']?['asm'] as String?,
|
||||||
|
isCoinbase: input['is_coinbase'] as bool?,
|
||||||
|
sequence: input['sequence'] as int?,
|
||||||
|
innerRedeemScriptAsm: input['innerRedeemscriptAsm'] as String?,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse outputs
|
||||||
|
List<isar_models.Output> outs = [];
|
||||||
|
for (final output in outputList) {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outs.add(
|
||||||
|
isar_models.Output(
|
||||||
|
scriptPubKey: output['scriptPubKey']?['hex'] as String?,
|
||||||
|
scriptPubKeyAsm: output['scriptPubKey']?['asm'] as String?,
|
||||||
|
scriptPubKeyType: output['scriptPubKey']?['type'] as String?,
|
||||||
|
scriptPubKeyAddress: address ?? "",
|
||||||
|
value: value.raw.toInt(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 =
|
||||||
|
txObject["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;
|
||||||
|
|
||||||
|
// should be 0
|
||||||
|
amount = amountSentFromWallet -
|
||||||
|
amountReceivedInWallet -
|
||||||
|
fee -
|
||||||
|
changeAmount;
|
||||||
|
} else if (mySentFromAddresses.isNotEmpty) {
|
||||||
|
// outgoing tx
|
||||||
|
type = isar_models.TransactionType.outgoing;
|
||||||
|
amount = amountSentFromWallet - changeAmount - fee;
|
||||||
|
|
||||||
|
final possible =
|
||||||
|
outputAddresses.difference(myChangeReceivedOnAddresses).first;
|
||||||
|
|
||||||
|
if (transactionAddress.value != possible) {
|
||||||
|
transactionAddress = isar_models.Address(
|
||||||
|
walletId: walletId,
|
||||||
|
value: possible,
|
||||||
|
derivationIndex: -1,
|
||||||
|
derivationPath: null,
|
||||||
|
subType: isar_models.AddressSubType.nonWallet,
|
||||||
|
type: isar_models.AddressType.nonWallet,
|
||||||
|
publicKey: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// incoming tx
|
||||||
|
type = isar_models.TransactionType.incoming;
|
||||||
|
amount = amountReceivedInWallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
final tx = isar_models.Transaction(
|
||||||
|
walletId: walletId,
|
||||||
|
txid: txObject["txid"] as String,
|
||||||
|
timestamp: txObject["blocktime"] as int? ??
|
||||||
|
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
||||||
|
type: type,
|
||||||
|
subType: isar_models.TransactionSubType.none,
|
||||||
|
// amount may overflow. Deprecated. Use amountString
|
||||||
|
amount: amount.raw.toInt(),
|
||||||
|
amountString: amount.toJsonString(),
|
||||||
|
fee: fee.raw.toInt(),
|
||||||
|
height: txObject["height"] as int?,
|
||||||
|
isCancelled: false,
|
||||||
|
isLelantus: false,
|
||||||
|
slateId: null,
|
||||||
|
otherData: null,
|
||||||
|
nonce: null,
|
||||||
|
inputs: ins,
|
||||||
|
outputs: outs,
|
||||||
|
);
|
||||||
|
|
||||||
|
txnsData.add(Tuple2(tx, transactionAddress));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.addNewTransactionData(txnsData, walletId);
|
await db.addNewTransactionData(txnsData, walletId);
|
||||||
|
@ -4568,8 +4701,6 @@ class FiroWallet extends CoinServiceAPI
|
||||||
final response = await cachedElectrumXClient.getUsedCoinSerials(
|
final response = await cachedElectrumXClient.getUsedCoinSerials(
|
||||||
coin: coin,
|
coin: coin,
|
||||||
);
|
);
|
||||||
print("getUsedCoinSerials");
|
|
||||||
print(response);
|
|
||||||
return response;
|
return response;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance.log("Exception rethrown in firo_wallet.dart: $e\n$s",
|
Logging.instance.log("Exception rethrown in firo_wallet.dart: $e\n$s",
|
||||||
|
|
|
@ -1791,6 +1791,18 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
coin: coin,
|
coin: coin,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
bool shouldBlock = false;
|
||||||
|
String? blockReason;
|
||||||
|
String? label;
|
||||||
|
|
||||||
|
final utxoAmount = jsonUTXO["value"] as int;
|
||||||
|
|
||||||
|
if (utxoAmount <= 10000) {
|
||||||
|
shouldBlock = true;
|
||||||
|
blockReason = "May contain ordinal";
|
||||||
|
label = "Possible ordinal";
|
||||||
|
}
|
||||||
|
|
||||||
final vout = jsonUTXO["tx_pos"] as int;
|
final vout = jsonUTXO["tx_pos"] as int;
|
||||||
|
|
||||||
final outputs = txn["vout"] as List;
|
final outputs = txn["vout"] as List;
|
||||||
|
@ -1809,10 +1821,10 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
txid: txn["txid"] as String,
|
txid: txn["txid"] as String,
|
||||||
vout: vout,
|
vout: vout,
|
||||||
value: jsonUTXO["value"] as int,
|
value: utxoAmount,
|
||||||
name: "",
|
name: label ?? "",
|
||||||
isBlocked: false,
|
isBlocked: shouldBlock,
|
||||||
blockedReason: null,
|
blockedReason: blockReason,
|
||||||
isCoinbase: txn["is_coinbase"] as bool? ?? false,
|
isCoinbase: txn["is_coinbase"] as bool? ?? false,
|
||||||
blockHash: txn["blockhash"] as String?,
|
blockHash: txn["blockhash"] as String?,
|
||||||
blockHeight: jsonUTXO["height"] as int?,
|
blockHeight: jsonUTXO["height"] as int?,
|
||||||
|
@ -1824,16 +1836,20 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logging.instance
|
Logging.instance.log(
|
||||||
.log('Outputs fetched: $outputArray', level: LogLevel.Info);
|
'Outputs fetched: $outputArray',
|
||||||
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
|
|
||||||
await db.updateUTXOs(walletId, outputArray);
|
await db.updateUTXOs(walletId, outputArray);
|
||||||
|
|
||||||
// finally update balance
|
// finally update balance
|
||||||
await _updateBalance();
|
await _updateBalance();
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance
|
Logging.instance.log(
|
||||||
.log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error);
|
"Output fetch unsuccessful: $e\n$s",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -148,19 +148,31 @@ mixin ElectrumXParsing {
|
||||||
type = TransactionType.outgoing;
|
type = TransactionType.outgoing;
|
||||||
amount = amountSentFromWallet - changeAmount - fee;
|
amount = amountSentFromWallet - changeAmount - fee;
|
||||||
|
|
||||||
final possible =
|
// non wallet addresses found in tx outputs
|
||||||
outputAddresses.difference(myChangeReceivedOnAddresses).first;
|
final nonWalletOutAddresses = outputAddresses.difference(
|
||||||
|
myChangeReceivedOnAddresses,
|
||||||
|
);
|
||||||
|
|
||||||
if (transactionAddress.value != possible) {
|
if (nonWalletOutAddresses.isNotEmpty) {
|
||||||
transactionAddress = Address(
|
final possible = nonWalletOutAddresses.first;
|
||||||
walletId: walletId,
|
|
||||||
value: possible,
|
if (transactionAddress.value != possible) {
|
||||||
derivationIndex: -1,
|
transactionAddress = Address(
|
||||||
derivationPath: null,
|
walletId: walletId,
|
||||||
subType: AddressSubType.nonWallet,
|
value: possible,
|
||||||
type: AddressType.nonWallet,
|
derivationIndex: -1,
|
||||||
publicKey: [],
|
derivationPath: null,
|
||||||
);
|
subType: AddressSubType.nonWallet,
|
||||||
|
type: AddressType.nonWallet,
|
||||||
|
publicKey: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// some other type of tx where the receiving address is
|
||||||
|
// one of my change addresses
|
||||||
|
|
||||||
|
type = TransactionType.sentToSelf;
|
||||||
|
amount = changeAmount;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// incoming tx
|
// incoming tx
|
||||||
|
|
|
@ -35,9 +35,10 @@ mixin FiroHive {
|
||||||
}
|
}
|
||||||
|
|
||||||
// mintIndex
|
// mintIndex
|
||||||
int? firoGetMintIndex() {
|
int firoGetMintIndex() {
|
||||||
return DB.instance.get<dynamic>(boxName: _walletId, key: "mintIndex")
|
return DB.instance.get<dynamic>(boxName: _walletId, key: "mintIndex")
|
||||||
as int?;
|
as int? ??
|
||||||
|
0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> firoUpdateMintIndex(int mintIndex) async {
|
Future<void> firoUpdateMintIndex(int mintIndex) async {
|
||||||
|
|
|
@ -62,7 +62,7 @@ extension CoinExt on Coin {
|
||||||
case Coin.epicCash:
|
case Coin.epicCash:
|
||||||
return "Epic Cash";
|
return "Epic Cash";
|
||||||
case Coin.eCash:
|
case Coin.eCash:
|
||||||
return "E-Cash";
|
return "eCash";
|
||||||
case Coin.ethereum:
|
case Coin.ethereum:
|
||||||
return "Ethereum";
|
return "Ethereum";
|
||||||
case Coin.firo:
|
case Coin.firo:
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
@ -50,4 +52,15 @@ abstract class Util {
|
||||||
}
|
}
|
||||||
return MaterialColor(color.value, swatch);
|
return MaterialColor(color.value, swatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void printJson(dynamic json) {
|
||||||
|
if (json is Map || json is List) {
|
||||||
|
final spaces = ' ' * 4;
|
||||||
|
final encoder = JsonEncoder.withIndent(spaces);
|
||||||
|
final pretty = encoder.convert(json);
|
||||||
|
log(pretty);
|
||||||
|
} else {
|
||||||
|
log(dynamic.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,10 @@ import 'package:flutter/material.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/providers/providers.dart';
|
import 'package:stackwallet/providers/providers.dart';
|
||||||
|
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||||
import 'package:stackwallet/themes/coin_icon_provider.dart';
|
import 'package:stackwallet/themes/coin_icon_provider.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
@ -34,6 +36,28 @@ class _ManagedFavoriteCardState extends ConsumerState<ManagedFavorite> {
|
||||||
|
|
||||||
final isDesktop = Util.isDesktop;
|
final isDesktop = Util.isDesktop;
|
||||||
|
|
||||||
|
final balance = ref.watch(
|
||||||
|
walletsChangeNotifierProvider.select(
|
||||||
|
(value) => value.getManager(widget.walletId).balance,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Amount total = balance.total;
|
||||||
|
if (manager.coin == Coin.firo || manager.coin == Coin.firoTestNet) {
|
||||||
|
final balancePrivate = ref.watch(
|
||||||
|
walletsChangeNotifierProvider.select(
|
||||||
|
(value) => (value
|
||||||
|
.getManager(
|
||||||
|
widget.walletId,
|
||||||
|
)
|
||||||
|
.wallet as FiroWallet)
|
||||||
|
.balancePrivate,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
total += balancePrivate.total;
|
||||||
|
}
|
||||||
|
|
||||||
return RoundedWhiteContainer(
|
return RoundedWhiteContainer(
|
||||||
padding: EdgeInsets.all(isDesktop ? 0 : 4.0),
|
padding: EdgeInsets.all(isDesktop ? 0 : 4.0),
|
||||||
child: RawMaterialButton(
|
child: RawMaterialButton(
|
||||||
|
@ -107,7 +131,7 @@ class _ManagedFavoriteCardState extends ConsumerState<ManagedFavorite> {
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
"${manager.balance.total.localizedStringAsFixed(
|
"${total.localizedStringAsFixed(
|
||||||
locale: ref.watch(
|
locale: ref.watch(
|
||||||
localeServiceChangeNotifierProvider.select(
|
localeServiceChangeNotifierProvider.select(
|
||||||
(value) => value.locale,
|
(value) => value.locale,
|
||||||
|
@ -150,7 +174,7 @@ class _ManagedFavoriteCardState extends ConsumerState<ManagedFavorite> {
|
||||||
height: 2,
|
height: 2,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
"${manager.balance.total.localizedStringAsFixed(
|
"${total.localizedStringAsFixed(
|
||||||
locale: ref.watch(
|
locale: ref.watch(
|
||||||
localeServiceChangeNotifierProvider.select(
|
localeServiceChangeNotifierProvider.select(
|
||||||
(value) => value.locale,
|
(value) => value.locale,
|
||||||
|
|
|
@ -11,7 +11,7 @@ description: Stack Wallet
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.7.9+173
|
version: 1.7.10+174
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.17.0 <3.0.0"
|
sdk: ">=2.17.0 <3.0.0"
|
||||||
|
|
Loading…
Reference in a new issue