Skip to content
Merged

Various #1244

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e5bbd32
firo restore and refresh optimizations
julian-CStack Jan 8, 2026
964d2dd
add tooltip option to app bar icon button
julian-CStack Jan 12, 2026
fd0ea21
paginate desktop recent activity transactions
julian-CStack Jan 12, 2026
6209011
Merge branch 'staging' into various
julian-CStack Jan 12, 2026
4b9606e
wrap xelis lib model to fix conditional import
julian-CStack Jan 12, 2026
6875b7a
https://github.com/cypherstack/stack_wallet/pull/1240/commits/22a1015…
julian-CStack Jan 12, 2026
2857755
update coinlib with firo related change
julian-CStack Jan 12, 2026
65a8596
Add support for creating Firo masternodes.
cassandras-lies Jan 11, 2026
9f92bcb
satisfy linter and some clean up
julian-CStack Jan 12, 2026
0891597
add optional content padding to primary button
julian-CStack Jan 13, 2026
a738c22
refactor create masternode dialog to match app wide look and feel and…
julian-CStack Jan 13, 2026
81a5ca8
show page number (paginated list view)
julian-CStack Jan 13, 2026
73b5a89
extract master node info into separate widgets and improve look and feel
julian-CStack Jan 13, 2026
efd1731
extract master nodes home view widget builder functions into separate…
julian-CStack Jan 13, 2026
16ea6f7
WIP mobile masternodes list
julian-CStack Jan 13, 2026
c898a70
Merge branch 'staging' into various
julian-CStack Jan 13, 2026
fdd617a
hack in quick fix alt to ensure future doesn't start executing before…
julian-CStack Jan 13, 2026
c52cdfb
fix message bug I introduced earlier
julian-CStack Jan 13, 2026
a088e2c
various masternode related fixes and improvements
julian-CStack Jan 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 55 additions & 8 deletions lib/electrumx_rpc/cached_electrumx_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,13 @@ class CachedElectrumXClient {
required ElectrumXClient electrumXClient,
}) => CachedElectrumXClient(electrumXClient: electrumXClient);

String base64ToHex(String source) =>
base64Decode(
LineSplitter.split(source).join(),
).map((e) => e.toRadixString(16).padLeft(2, '0')).join();
String base64ToHex(String source) => base64Decode(
LineSplitter.split(source).join(),
).map((e) => e.toRadixString(16).padLeft(2, '0')).join();

String base64ToReverseHex(String source) =>
base64Decode(
LineSplitter.split(source).join(),
).reversed.map((e) => e.toRadixString(16).padLeft(2, '0')).join();
String base64ToReverseHex(String source) => base64Decode(
LineSplitter.split(source).join(),
).reversed.map((e) => e.toRadixString(16).padLeft(2, '0')).join();

/// Call electrumx getTransaction on a per coin basis, storing the result in local db if not already there.
///
Expand Down Expand Up @@ -77,6 +75,55 @@ class CachedElectrumXClient {
}
}

Future<List<Map<String, dynamic>>> getBatchTransactions({
required List<String> txHashes,
required CryptoCurrency cryptoCurrency,
}) async {
try {
final box = await DB.instance.getTxCacheBox(currency: cryptoCurrency);

final List<Map<String, dynamic>> result = [];
final List<String> needsFetching = [];

for (final txHash in txHashes) {
final cachedTx = box.get(txHash) as Map?;
if (cachedTx == null) {
needsFetching.add(txHash);
} else {
result.add(Map<String, dynamic>.from(cachedTx));
}
}

if (needsFetching.isNotEmpty) {
final txns = await electrumXClient.getBatchTransactions(
txHashes: needsFetching,
);

for (final tx in txns) {
tx.remove("hex");
tx.remove("lelantusData");
tx.remove("sparkData");

if (tx["confirmations"] != null &&
tx["confirmations"] as int > minCacheConfirms) {
await box.put(tx["txid"] as String, tx);
}

result.add(tx);
}
}

return result;
} catch (e, s) {
Logging.instance.e(
"Failed to process CachedElectrumX.getTransaction(): ",
error: e,
stackTrace: s,
);
rethrow;
}
}

/// Clear all cached transactions for the specified coin
Future<void> clearSharedTransactionCache({
required CryptoCurrency cryptoCurrency,
Expand Down
22 changes: 22 additions & 0 deletions lib/electrumx_rpc/electrumx_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,28 @@ class ElectrumXClient {
return Map<String, dynamic>.from(response as Map);
}

Future<List<Map<String, dynamic>>> getBatchTransactions({
required List<String> txHashes,
String? requestID,
}) async {
Logging.instance.d(
"attempting to fetch BATCHED blockchain.transaction.get...",
);

final response = await batchRequest(
command: 'blockchain.transaction.get',
args: txHashes.map((e) => [e, true]).toList(),
);
final List<Map<String, dynamic>> result = [];
for (int i = 0; i < response.length; i++) {
result.add(Map<String, dynamic>.from(response[i] as Map));
}

Logging.instance.d("Fetching blockchain.transaction.get BATCHED finished");

return result;
}

/// Returns the whole Lelantus anonymity set for denomination in the groupId.
///
/// ex:
Expand Down
5 changes: 1 addition & 4 deletions lib/pages/address_book_views/address_book_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,7 @@ class _AddressBookViewState extends ConsumerState<AddressBookView> {
} else {
ref
.read(addressBookFilterProvider)
.addAll(
coins.where((e) => e.network != CryptoCurrencyNetwork.test),
false,
);
.addAll(coins.where((e) => !e.network.isTestNet), false);
}
} else {
ref.read(addressBookFilterProvider).add(widget.coin!, false);
Expand Down
119 changes: 119 additions & 0 deletions lib/pages/masternodes/create_masternode_view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../themes/stack_colors.dart';
import '../../utilities/text_styles.dart';
import '../../utilities/util.dart';
import '../../widgets/background.dart';
import '../../widgets/conditional_parent.dart';
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../widgets/desktop/desktop_dialog_close_button.dart';
import 'sub_widgets/register_masternode_form.dart';

class CreateMasternodeView extends ConsumerStatefulWidget {
const CreateMasternodeView({
super.key,
required this.firoWalletId,
this.popTxidOnSuccess = true,
});

static const routeName = "/createMasternodeView";

final String firoWalletId;
final bool popTxidOnSuccess;

@override
ConsumerState<CreateMasternodeView> createState() =>
_CreateMasternodeDialogState();
}

class _CreateMasternodeDialogState extends ConsumerState<CreateMasternodeView> {
@override
Widget build(BuildContext context) {
return ConditionalParent(
condition: Util.isDesktop,
builder: (child) => SizedBox(
width: 660,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: .spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left: 32),
child: Text(
"Create masternode",
style: STextStyles.desktopH3(context),
),
),
const DesktopDialogCloseButton(),
],
),
Flexible(
child: Padding(
padding: const EdgeInsets.only(left: 32, bottom: 32, right: 32),
child: child,
),
),
],
),
),
child: ConditionalParent(
condition: !Util.isDesktop,
builder: (child) => Background(
child: Scaffold(
backgroundColor: Theme.of(
context,
).extension<StackColors>()!.background,
appBar: AppBar(
backgroundColor: Theme.of(
context,
).extension<StackColors>()!.background,
leading: AppBarBackButton(
onPressed: () async {
Navigator.of(context).pop();
},
),
title: Text(
"Create masternode",
style: STextStyles.navBarTitle(context),
),
),
body: SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.only(bottom: 16),
child: child,
),
),
),
),
);
},
),
),
),
),
child: RegisterMasternodeForm(
firoWalletId: widget.firoWalletId,
onRegistrationSuccess: (txid) {
if (widget.popTxidOnSuccess && mounted) {
Navigator.of(context, rootNavigator: Util.isDesktop).pop(txid);
}
},
),
),
);
}
}
57 changes: 57 additions & 0 deletions lib/pages/masternodes/masternode_details_view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';

import '../../themes/stack_colors.dart';
import '../../utilities/text_styles.dart';
import '../../wallets/wallet/impl/firo_wallet.dart';
import '../../widgets/background.dart';
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
import 'sub_widgets/masternode_info_widget.dart';

class MasternodeDetailsView extends StatelessWidget {
const MasternodeDetailsView({super.key, required this.node});

static const String routeName = "/masternodeDetailsView";

final MasternodeInfo node;

@override
Widget build(BuildContext context) {
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: const AppBarBackButton(),
title: Text(
"Masternode details",
style: STextStyles.navBarTitle(context),
),
),
body: SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
mainAxisSize: .min,
children: [
MasternodeInfoWidget(info: node),
const SizedBox(height: 16),
],
),
),
),
),
);
},
),
),
),
);
}
}
Loading
Loading