xpub refactor to include coin network type in generation

This commit is contained in:
julian 2023-04-07 18:44:43 -06:00
parent 7ced912de1
commit 839ee314a2
12 changed files with 235 additions and 104 deletions

View file

@ -5,6 +5,9 @@ import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/services/coins/manager.dart';
import 'package:stackwallet/services/mixins/xpubable.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/text_styles.dart';
@ -13,15 +16,17 @@ import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class DesktopShowXpubDialog extends ConsumerStatefulWidget {
const DesktopShowXpubDialog({
Key? key,
required this.xpub,
required this.walletId,
this.clipboardInterface = const ClipboardWrapper(),
}) : super(key: key);
final String xpub;
final String walletId;
final ClipboardInterface clipboardInterface;
@ -34,10 +39,15 @@ class DesktopShowXpubDialog extends ConsumerStatefulWidget {
class _DesktopShowXpubDialog extends ConsumerState<DesktopShowXpubDialog> {
late ClipboardInterface _clipboardInterface;
late final Manager manager;
String? xpub;
@override
void initState() {
_clipboardInterface = widget.clipboardInterface;
manager =
ref.read(walletsChangeNotifierProvider).getManager(widget.walletId);
super.initState();
}
@ -47,25 +57,36 @@ class _DesktopShowXpubDialog extends ConsumerState<DesktopShowXpubDialog> {
}
Future<void> _copy() async {
await _clipboardInterface.setData(ClipboardData(text: widget.xpub));
unawaited(showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
iconAsset: Assets.svg.copy,
context: context,
));
await _clipboardInterface.setData(ClipboardData(text: xpub!));
if (mounted) {
unawaited(showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
iconAsset: Assets.svg.copy,
context: context,
));
}
}
@override
Widget build(BuildContext context) {
return DesktopDialog(
maxWidth: 580,
maxWidth: 600,
maxHeight: double.infinity,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(
left: 32,
),
child: Text(
"${manager.walletName} xPub",
style: STextStyles.desktopH2(context),
),
),
DesktopDialogCloseButton(
onPressedOverride: Navigator.of(
context,
@ -74,48 +95,79 @@ class _DesktopShowXpubDialog extends ConsumerState<DesktopShowXpubDialog> {
),
],
),
Padding(
padding: const EdgeInsets.fromLTRB(32, 0, 32, 26),
child: Column(
children: [
const SizedBox(height: 16),
Text(
"Wallet Xpub",
style: STextStyles.desktopH2(context),
),
const SizedBox(height: 14),
QrImage(
data: widget.xpub,
size: 300,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
const SizedBox(height: 25),
Text(widget.xpub!, style: STextStyles.largeMedium14(context)),
const SizedBox(height: 25),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SecondaryButton(
width: 250,
buttonHeight: ButtonHeight.xl,
label: "Copy",
onPressed: () async {
await _copy();
}),
const SizedBox(width: 16),
PrimaryButton(
width: 250,
buttonHeight: ButtonHeight.xl,
label: "Continue",
onPressed: Navigator.of(
context,
rootNavigator: true,
).pop),
],
)
],
AnimatedSize(
duration: const Duration(milliseconds: 150),
child: Padding(
padding: const EdgeInsets.fromLTRB(32, 0, 32, 32),
child: Column(
children: [
const SizedBox(height: 44),
FutureBuilder(
future: (manager.wallet as XPubAble).xpub,
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
xpub = snapshot.data!;
}
return Column(
children: [
xpub == null
? const SizedBox(
height: 300,
child: LoadingIndicator(),
)
: QrImage(
data: xpub!,
size: 280,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
const SizedBox(height: 25),
RoundedWhiteContainer(
padding: const EdgeInsets.all(16),
borderColor: xpub == null
? null
: Theme.of(context)
.extension<StackColors>()!
.backgroundAppBar,
child: SelectableText(
xpub ?? "",
style: STextStyles.largeMedium14(context),
),
),
const SizedBox(height: 32),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: SecondaryButton(
buttonHeight: ButtonHeight.xl,
label: "Cancel",
onPressed: Navigator.of(
context,
rootNavigator: true,
).pop,
),
),
const SizedBox(width: 16),
Expanded(
child: PrimaryButton(
buttonHeight: ButtonHeight.xl,
label: "Copy",
enabled: xpub != null,
onPressed: _copy,
),
),
],
),
],
);
},
),
],
),
),
),
],

View file

@ -1,7 +1,5 @@
import 'dart:async';
import 'package:bip32/bip32.dart' as bip32;
import 'package:bip39/bip39.dart' as bip39;
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
@ -12,7 +10,6 @@ import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
@ -33,29 +30,14 @@ enum _WalletOptions {
}
}
class WalletOptionsButton extends ConsumerStatefulWidget {
const WalletOptionsButton({Key? key, required this.walletId})
: super(key: key);
class WalletOptionsButton extends StatelessWidget {
const WalletOptionsButton({
Key? key,
required this.walletId,
}) : super(key: key);
final String walletId;
@override
ConsumerState<WalletOptionsButton> createState() =>
_WalletOptionsButtonState();
}
class _WalletOptionsButtonState extends ConsumerState<WalletOptionsButton> {
late final String walletId;
late final Coin coin;
@override
void initState() {
walletId = widget.walletId;
coin = ref.read(walletsChangeNotifierProvider).getManager(walletId).coin;
super.initState();
}
@override
Widget build(BuildContext context) {
return RawMaterialButton(
@ -81,12 +63,12 @@ class _WalletOptionsButtonState extends ConsumerState<WalletOptionsButton> {
onShowXpubPressed: () async {
Navigator.of(context).pop(_WalletOptions.showXpub);
},
coin: coin,
walletId: walletId,
);
},
);
if (mounted && func != null) {
if (context.mounted && func != null) {
switch (func) {
case _WalletOptions.addressList:
unawaited(
@ -117,21 +99,12 @@ class _WalletOptionsButtonState extends ConsumerState<WalletOptionsButton> {
);
if (result == true) {
if (mounted) {
if (context.mounted) {
Navigator.of(context).pop();
}
}
break;
case _WalletOptions.showXpub:
final List<String> mnemonic = await ref
.read(walletsChangeNotifierProvider)
.getManager(widget.walletId)
.mnemonic;
final seed = bip39.mnemonicToSeed(mnemonic.join(' '));
final node = bip32.BIP32.fromSeed(seed);
final xpub = node.neutered().toBase58();
final result = await showDialog<bool?>(
context: context,
barrierDismissible: false,
@ -143,7 +116,7 @@ class _WalletOptionsButtonState extends ConsumerState<WalletOptionsButton> {
RouteGenerator.generateRoute(
RouteSettings(
name: DesktopShowXpubDialog.routeName,
arguments: xpub,
arguments: walletId,
),
),
];
@ -152,7 +125,7 @@ class _WalletOptionsButtonState extends ConsumerState<WalletOptionsButton> {
);
if (result == true) {
if (mounted) {
if (context.mounted) {
Navigator.of(context).pop();
}
}
@ -182,24 +155,24 @@ class _WalletOptionsButtonState extends ConsumerState<WalletOptionsButton> {
}
}
class WalletOptionsPopupMenu extends StatelessWidget {
class WalletOptionsPopupMenu extends ConsumerWidget {
const WalletOptionsPopupMenu({
Key? key,
required this.onDeletePressed,
required this.onAddressListPressed,
required this.onShowXpubPressed,
required this.coin,
required this.walletId,
}) : super(key: key);
final VoidCallback onDeletePressed;
final VoidCallback onAddressListPressed;
final VoidCallback onShowXpubPressed;
final Coin coin;
final String walletId;
@override
Widget build(BuildContext context) {
final bool xpubEnabled =
coin != Coin.monero && coin != Coin.epicCash && coin != Coin.wownero;
Widget build(BuildContext context, WidgetRef ref) {
final bool xpubEnabled = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletId).hasXPub));
return Stack(
children: [

View file

@ -1546,7 +1546,7 @@ class RouteGenerator {
if (args is String) {
return FadePageRoute(
DesktopShowXpubDialog(
xpub: args,
walletId: args,
),
RouteSettings(
name: settings.name,

View file

@ -30,6 +30,7 @@ import 'package:stackwallet/services/mixins/electrum_x_parsing.dart';
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
import 'package:stackwallet/services/mixins/wallet_cache.dart';
import 'package:stackwallet/services/mixins/wallet_db.dart';
import 'package:stackwallet/services/mixins/xpubable.dart';
import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/notifications_api.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
@ -101,7 +102,8 @@ class BitcoinWallet extends CoinServiceAPI
WalletDB,
ElectrumXParsing,
PaynymWalletInterface,
CoinControlInterface {
CoinControlInterface
implements XPubAble {
BitcoinWallet({
required String walletId,
required String walletName,
@ -3144,4 +3146,15 @@ class BitcoinWallet extends CoinServiceAPI
return false;
}
}
@override
Future<String> get xpub async {
final node = await Bip32Utils.getBip32Root(
(await mnemonic).join(" "),
await mnemonicPassphrase ?? "",
_network,
);
return node.neutered().toBase58();
}
}

View file

@ -27,6 +27,7 @@ import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/services/mixins/coin_control_interface.dart';
import 'package:stackwallet/services/mixins/wallet_cache.dart';
import 'package:stackwallet/services/mixins/wallet_db.dart';
import 'package:stackwallet/services/mixins/xpubable.dart';
import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/notifications_api.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
@ -100,7 +101,8 @@ String constructDerivePath({
}
class BitcoinCashWallet extends CoinServiceAPI
with WalletCache, WalletDB, CoinControlInterface {
with WalletCache, WalletDB, CoinControlInterface
implements XPubAble {
BitcoinCashWallet({
required String walletId,
required String walletName,
@ -3191,6 +3193,17 @@ class BitcoinCashWallet extends CoinServiceAPI
return false;
}
}
@override
Future<String> get xpub async {
final node = await Bip32Utils.getBip32Root(
(await mnemonic).join(" "),
await mnemonicPassphrase ?? "",
_network,
);
return node.neutered().toBase58();
}
}
// Bitcoincash Network

View file

@ -29,6 +29,7 @@ import 'package:stackwallet/services/mixins/coin_control_interface.dart';
import 'package:stackwallet/services/mixins/electrum_x_parsing.dart';
import 'package:stackwallet/services/mixins/wallet_cache.dart';
import 'package:stackwallet/services/mixins/wallet_db.dart';
import 'package:stackwallet/services/mixins/xpubable.dart';
import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/notifications_api.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
@ -87,7 +88,8 @@ String constructDerivePath({
}
class DogecoinWallet extends CoinServiceAPI
with WalletCache, WalletDB, ElectrumXParsing, CoinControlInterface {
with WalletCache, WalletDB, ElectrumXParsing, CoinControlInterface
implements XPubAble {
DogecoinWallet({
required String walletId,
required String walletName,
@ -2912,6 +2914,17 @@ class DogecoinWallet extends CoinServiceAPI
return false;
}
}
@override
Future<String> get xpub async {
final node = await Bip32Utils.getBip32Root(
(await mnemonic).join(" "),
await mnemonicPassphrase ?? "",
network,
);
return node.neutered().toBase58();
}
}
// Dogecoin Network

View file

@ -28,6 +28,7 @@ import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/services/mixins/firo_hive.dart';
import 'package:stackwallet/services/mixins/wallet_cache.dart';
import 'package:stackwallet/services/mixins/wallet_db.dart';
import 'package:stackwallet/services/mixins/xpubable.dart';
import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/notifications_api.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
@ -773,7 +774,9 @@ Future<void> _setTestnetWrapper(bool isTestnet) async {
}
/// Handles a single instance of a firo wallet
class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
class FiroWallet extends CoinServiceAPI
with WalletCache, WalletDB, FiroHive
implements XPubAble {
// Constructor
FiroWallet({
required String walletId,
@ -5052,4 +5055,15 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
@override
Future<List<isar_models.Transaction>> get transactions =>
db.getTransactions(walletId).findAll();
@override
Future<String> get xpub async {
final node = await Bip32Utils.getBip32Root(
(await mnemonic).join(" "),
await mnemonicPassphrase ?? "",
_network,
);
return node.neutered().toBase58();
}
}

View file

@ -28,6 +28,7 @@ import 'package:stackwallet/services/mixins/coin_control_interface.dart';
import 'package:stackwallet/services/mixins/electrum_x_parsing.dart';
import 'package:stackwallet/services/mixins/wallet_cache.dart';
import 'package:stackwallet/services/mixins/wallet_db.dart';
import 'package:stackwallet/services/mixins/xpubable.dart';
import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/notifications_api.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
@ -92,7 +93,8 @@ String constructDerivePath({
}
class LitecoinWallet extends CoinServiceAPI
with WalletCache, WalletDB, ElectrumXParsing, CoinControlInterface {
with WalletCache, WalletDB, ElectrumXParsing, CoinControlInterface
implements XPubAble {
LitecoinWallet({
required String walletId,
required String walletName,
@ -3310,6 +3312,17 @@ class LitecoinWallet extends CoinServiceAPI
return false;
}
}
@override
Future<String> get xpub async {
final node = await Bip32Utils.getBip32Root(
(await mnemonic).join(" "),
await mnemonicPassphrase ?? "",
_network,
);
return node.neutered().toBase58();
}
}
final litecoin = NetworkType(

View file

@ -12,6 +12,7 @@ import 'package:stackwallet/services/event_bus/events/global/updated_in_backgrou
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/services/mixins/coin_control_interface.dart';
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
import 'package:stackwallet/services/mixins/xpubable.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
@ -247,4 +248,14 @@ class Manager with ChangeNotifier {
boxName: DB.boxNameDBInfo,
);
}
bool get hasXPub => _currentWallet is XPubAble;
Future<String> get xpub async {
if (!hasXPub) {
throw Exception(
"Tried to read xpub from wallet that does not support it");
}
return (_currentWallet as XPubAble).xpub;
}
}

View file

@ -28,6 +28,7 @@ import 'package:stackwallet/services/mixins/coin_control_interface.dart';
import 'package:stackwallet/services/mixins/electrum_x_parsing.dart';
import 'package:stackwallet/services/mixins/wallet_cache.dart';
import 'package:stackwallet/services/mixins/wallet_db.dart';
import 'package:stackwallet/services/mixins/xpubable.dart';
import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/notifications_api.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
@ -89,7 +90,8 @@ String constructDerivePath({
}
class NamecoinWallet extends CoinServiceAPI
with WalletCache, WalletDB, ElectrumXParsing, CoinControlInterface {
with WalletCache, WalletDB, ElectrumXParsing, CoinControlInterface
implements XPubAble {
NamecoinWallet({
required String walletId,
required String walletName,
@ -3303,6 +3305,17 @@ class NamecoinWallet extends CoinServiceAPI
return false;
}
}
@override
Future<String> get xpub async {
final node = await Bip32Utils.getBip32Root(
(await mnemonic).join(" "),
await mnemonicPassphrase ?? "",
_network,
);
return node.neutered().toBase58();
}
}
// Namecoin Network

View file

@ -27,6 +27,7 @@ import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/services/mixins/coin_control_interface.dart';
import 'package:stackwallet/services/mixins/wallet_cache.dart';
import 'package:stackwallet/services/mixins/wallet_db.dart';
import 'package:stackwallet/services/mixins/xpubable.dart';
import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/notifications_api.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
@ -84,7 +85,8 @@ String constructDerivePath({
}
class ParticlWallet extends CoinServiceAPI
with WalletCache, WalletDB, CoinControlInterface {
with WalletCache, WalletDB, CoinControlInterface
implements XPubAble {
ParticlWallet({
required String walletId,
required String walletName,
@ -3356,6 +3358,17 @@ class ParticlWallet extends CoinServiceAPI
return false;
}
}
@override
Future<String> get xpub async {
final node = await Bip32Utils.getBip32Root(
(await mnemonic).join(" "),
await mnemonicPassphrase ?? "",
_network,
);
return node.neutered().toBase58();
}
}
// Particl Network

View file

@ -0,0 +1,3 @@
mixin XPubAble {
Future<String> get xpub;
}