This commit is contained in:
fosse 2023-08-15 11:59:31 -04:00
parent a4e7acf875
commit cbe72198ee
8 changed files with 290 additions and 52 deletions

View file

@ -105,7 +105,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
final currentWalletInfo = walletInfoSource.values
.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
currentWalletInfo.derivationType = DerivationType.nano;// doesn't matter for the rename action
currentWalletInfo.derivationType = DerivationType.nano; // doesn't matter for the rename action
String randomWords =
(List<String>.from(nm.NanoMnemomics.WORDLIST)..shuffle()).take(24).join(' ');
@ -121,14 +121,52 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
}
static Future<DerivationType> compareDerivationMethods({String? mnemonic, String? seedKey}) async {
static Future<dynamic> getInfoFromSeedOrMnemonic(DerivationType derivationType,
{String? seedKey, String? mnemonic}) async {
NanoClient nanoClient = NanoClient();
// TODO: figure out how to load the current node uri in this context:
nanoClient.connect(Node(
uri: NanoClient.BACKUP_NODE_URI,
type: WalletType.nano,
));
late String publicAddress;
if (seedKey != null) {
if (derivationType == DerivationType.bip39) {
publicAddress = await NanoUtil.hdSeedToAddress(seedKey, 0);
} else if (derivationType == DerivationType.nano) {
publicAddress = await NanoUtil.seedToAddress(seedKey, 0);
}
}
if (derivationType == DerivationType.bip39) {
if (mnemonic != null) {
seedKey = await NanoUtil.hdMnemonicListToSeed(mnemonic.split(' '));
publicAddress = await NanoUtil.hdSeedToAddress(seedKey, 0);
}
}
if (derivationType == DerivationType.nano) {
if (mnemonic != null) {
seedKey = await NanoUtil.mnemonicToSeed(mnemonic);
publicAddress = await NanoUtil.seedToAddress(seedKey, 0);
}
}
var accountInfo = await nanoClient.getAccountInfo(publicAddress);
accountInfo["address"] = publicAddress;
return accountInfo;
}
static Future<List<DerivationType>> compareDerivationMethods(
{String? mnemonic, String? seedKey}) async {
if (mnemonic?.split(' ').length == 12) {
return DerivationType.bip39;
return [DerivationType.bip39];
}
if (seedKey?.length == 128) {
return DerivationType.bip39;
return [DerivationType.bip39];
} else if (seedKey?.length == 64) {
return DerivationType.nano;
return [DerivationType.nano];
}
late String publicAddressStandard;
@ -152,32 +190,19 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
try {
publicAddressBip39 = await NanoUtil.hdSeedToAddress(seedKey, 0);
} catch (e) {
return DerivationType.nano;
return [DerivationType.nano];
}
try {
publicAddressStandard = await NanoUtil.seedToAddress(seedKey, 0);
} catch (e) {
return DerivationType.bip39;
return [DerivationType.bip39];
}
}
// check if either has a balance:
// NanoBalance bip39Balance = await nanoClient.getBalance(publicAddressBip39);
// NanoBalance standardBalance = await nanoClient.getBalance(publicAddressStandard);
// // TODO: this is a super basic implementation, and if both addresses have balances
// // it might not be the one that the user wants, though it is unlikely
// if (bip39Balance.currentBalance > standardBalance.currentBalance) {
// return DerivationType.bip39;
// } else {
// return DerivationType.nano;
// }
// check if account has a history:
var bip39Info;
var standardInfo;
print(publicAddressBip39);
print(publicAddressStandard);
try {
bip39Info = await nanoClient.getAccountInfo(publicAddressBip39);
} catch (e) {
@ -190,26 +215,18 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
}
// one of these is *probably* null:
if (bip39Info == null || bip39Info["error"] != null) {
return DerivationType.nano;
} else if (standardInfo == null || standardInfo["error"] != null) {
return DerivationType.bip39;
if ((bip39Info == null || bip39Info["error"] != null) &&
(standardInfo != null && standardInfo["error"] == null)) {
return [DerivationType.nano];
} else if ((standardInfo == null || standardInfo["error"] != null) &&
(bip39Info != null && bip39Info["error"] == null)) {
return [DerivationType.bip39];
}
// both are non-null:
var bip39Height = int.parse(bip39Info['confirmation_height'] as String);
var standardHeight = int.parse(standardInfo['confirmation_height'] as String);
print(bip39Height);
print(standardHeight);
if (bip39Height > standardHeight) {
return DerivationType.bip39;
} else {
return DerivationType.nano;
}
// we don't know for sure:
return [DerivationType.nano, DerivationType.bip39];
} catch (e) {
return DerivationType.nano;
return [DerivationType.unknown];
}
}
@ -246,8 +263,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
throw nm.NanoMnemonicIsIncorrectException();
}
DerivationType derivationType = credentials.derivationType ??
await compareDerivationMethods(mnemonic: credentials.mnemonic);
DerivationType derivationType = credentials.derivationType ?? DerivationType.nano;
credentials.walletInfo!.derivationType = derivationType;

View file

@ -27,6 +27,7 @@ import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_creat
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_pow_nodes_page.dart';
@ -85,6 +86,7 @@ import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_core/unspent_coins_info.dart';
@ -846,6 +848,17 @@ Future setup({
getIt.registerFactoryParam<WalletRestorePage, WalletType, void>(
(type, _) => WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type)));
getIt.registerFactoryParam<WalletRestoreChooseDerivationViewModel, WalletType, dynamic>(
(type, credentials) =>
WalletRestoreChooseDerivationViewModel(type: type, credentials: credentials));
getIt.registerFactoryParam<WalletRestoreChooseDerivationPage, WalletType, dynamic>(
(type, credentials) =>
WalletRestoreChooseDerivationPage(getIt.get<WalletRestoreChooseDerivationViewModel>(
param1: type,
param2: credentials,
)));
getIt.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>(
(TransactionInfo transactionInfo, _) {
final wallet = getIt.get<AppStore>().wallet!;

View file

@ -19,6 +19,7 @@ import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
@ -197,6 +198,12 @@ Route<dynamic> createRoute(RouteSettings settings) {
return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletRestorePage>(param1: settings.arguments as WalletType));
case Routes.restoreWalletChooseDerivation:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletRestoreChooseDerivationPage>(
param1: (settings.arguments as dynamic)!["walletType"] as WalletType,
param2: (settings.arguments as dynamic)!["credentials"]));
case Routes.sweepingWalletPage:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<SweepingWalletPage>());
@ -315,17 +322,13 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.otherSettingsPage:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<OtherSettingsPage>());
fullscreenDialog: true, builder: (_) => getIt.get<OtherSettingsPage>());
case Routes.newNode:
final args = settings.arguments as Map<String, dynamic>?;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NodeCreateOrEditPage>(
param1: args?['editingNode'] as Node?,
param2: args?['isSelected'] as bool?));
param1: args?['editingNode'] as Node?, param2: args?['isSelected'] as bool?));
case Routes.accountCreation:
return CupertinoPageRoute<String>(
@ -334,8 +337,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.nanoAccountCreation:
return CupertinoPageRoute<String>(
builder: (_) => getIt.get<NanoAccountEditOrCreatePage>(
param1: settings.arguments as NanoAccount?));
builder: (_) =>
getIt.get<NanoAccountEditOrCreatePage>(param1: settings.arguments as NanoAccount?));
case Routes.addressBook:
return MaterialPageRoute<void>(

View file

@ -6,6 +6,7 @@ class Routes {
static const seed = '/seed';
static const restoreOptions = '/restore_options';
static const restoreWalletFromSeedKeys = '/restore_wallet_from_seeds_keys';
static const restoreWalletChooseDerivation = '/restore_wallet_choose_derivation';
static const dashboard = '/dashboard';
static const send = '/send';
static const transactionDetails = '/transaction_info';

View file

@ -0,0 +1,98 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
class WalletRestoreChooseDerivationPage extends BasePage {
WalletRestoreChooseDerivationPage(this.walletRestoreChooseDerivationViewModel) {}
@override
Widget middle(BuildContext context) => Observer(
builder: (_) => Text(
"change me",
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
fontFamily: 'Lato',
color: titleColor ?? Theme.of(context).primaryTextTheme!.titleLarge!.color!),
));
final WalletRestoreChooseDerivationViewModel walletRestoreChooseDerivationViewModel;
DerivationType derivationType = DerivationType.unknown;
@override
Widget body(BuildContext context) {
return Observer(
builder: (_) => FutureBuilder<List<Derivation>>(
future: walletRestoreChooseDerivationViewModel.derivations,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator(); // Show loading spinner while waiting
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}'); // Show error if any
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Text('No derivations available'); // Show message if no derivations are available
} else {
return ListView.separated(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
separatorBuilder: (_, __) => Container(padding: EdgeInsets.only(bottom: 8)),
itemCount: snapshot.data!.length,
itemBuilder: (__, index) {
final derivation = snapshot.data![index];
return Container(
margin: const EdgeInsets.only(left: 16, right: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30.0),
border: Border.all(
color: getIt.get<SettingsStore>().currentTheme.type == ThemeType.bright
? Color.fromRGBO(255, 255, 255, 0.2)
: Colors.transparent,
width: 1,
),
color: Theme.of(context).textTheme.titleLarge!.backgroundColor!,
),
child: Container(
margin: const EdgeInsets.only(top: 16, left: 24, right: 24, bottom: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
derivation.address,
style: TextStyle(
fontSize: 16,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color:
Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
height: 1,
),
),
Text(
"${S.current.confirmed}: ${derivation.balance}",
style: TextStyle(
fontSize: 18,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color:
Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
height: 2,
),
),
],
),
),
);
},
);
}
},
),
);
}
}

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
@ -101,6 +102,7 @@ class WalletRestorePage extends BasePage {
final GlobalKey<WalletRestoreFromSeedFormState> walletRestoreFromSeedFormKey;
final GlobalKey<WalletRestoreFromKeysFromState> walletRestoreFromKeysFormKey;
final FocusNode _blockHeightFocusNode;
DerivationType derivationType = DerivationType.unknown;
@override
Widget body(BuildContext context) {
@ -256,10 +258,12 @@ class WalletRestorePage extends BasePage {
walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
}
credentials['derivationType'] = this.derivationType;
return credentials;
}
void _confirmForm(BuildContext context) {
void _confirmForm(BuildContext context) async {
// Dismissing all visible keyboard to provide context for navigation
FocusManager.instance.primaryFocus?.unfocus();
final formContext = walletRestoreViewModel.mode == WalletRestoreMode.seed
@ -283,12 +287,25 @@ class WalletRestorePage extends BasePage {
return;
}
var credentials = walletRestoreViewModel.getCredentials(_credentials());
if (credentials.walletInfo?.derivationType == DerivationType.unknown) {
List<DerivationType> derivationTypes =
await walletRestoreViewModel.getDerivationType(_credentials());
if (derivationTypes[0] == DerivationType.unknown || derivationTypes.length > 0) {
// push screen to choose the derivation type:
var derivationType =
await Navigator.of(context).pushNamed(Routes.restoreWalletChooseDerivation, arguments: {
"walletType": walletRestoreViewModel.type,
"credentials": _credentials(),
}) as DerivationType?;
if (derivationType == null) {
return;
}
this.derivationType = derivationType;
}
walletRestoreViewModel.create(options: _credentials());
print(this.derivationType);
// todo: re-enable
// walletRestoreViewModel.create(options: _credentials());
}
Future<void> showNameExistsAlert(BuildContext context) {

View file

@ -0,0 +1,68 @@
import 'package:cw_core/wallet_info.dart';
import 'package:cw_nano/nano_balance.dart';
import 'package:cw_nano/nano_util.dart';
import 'package:cw_nano/nano_wallet_service.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
part 'wallet_restore_choose_derivation_view_model.g.dart';
class WalletRestoreChooseDerivationViewModel = WalletRestoreChooseDerivationViewModelBase
with _$WalletRestoreChooseDerivationViewModel;
class Derivation {
Derivation(this.balance, this.address, this.derivationType, this.height);
final String balance;
final String address;
final DerivationType derivationType;
final int height;
}
abstract class WalletRestoreChooseDerivationViewModelBase with Store {
WalletRestoreChooseDerivationViewModelBase({required this.credentials, required this.type})
: mode = WalletRestoreMode.seed {}
WalletType type;
dynamic credentials;
@observable
WalletRestoreMode mode;
@observable
Future<List<Derivation>> get derivations async {
var list = <Derivation>[];
switch (type) {
case WalletType.nano:
var seed = credentials['seed'] as String;
var bip39Info =
await NanoWalletService.getInfoFromSeedOrMnemonic(DerivationType.bip39, mnemonic: seed);
var standardInfo =
await NanoWalletService.getInfoFromSeedOrMnemonic(DerivationType.nano, mnemonic: seed);
list.add(Derivation(
NanoUtil.getRawAsUsableString(bip39Info["balance"] as String, NanoUtil.rawPerNano),
bip39Info["address"] as String,
DerivationType.bip39,
int.parse(
bip39Info["confirmation_height"] as String,
),
));
list.add(Derivation(
NanoUtil.getRawAsUsableString(standardInfo["balance"] as String, NanoUtil.rawPerNano),
standardInfo["address"] as String,
DerivationType.nano,
int.parse(
standardInfo["confirmation_height"] as String,
),
));
break;
default:
break;
}
return list;
}
}

View file

@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cw_nano/nano_wallet.dart';
import 'package:cw_nano/nano_wallet_service.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/store/app_store.dart';
@ -118,6 +119,27 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
throw Exception('Unexpected type: ${type.toString()}');
}
@override
Future<List<DerivationType>> getDerivationType(dynamic options) async {
final seed = options['seed'] as String?;
switch (type) {
// case WalletType.bitcoin:
// return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
// name: name, mnemonic: seed, password: password);
// case WalletType.litecoin:
// return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
// name: name, mnemonic: seed, password: password);
case WalletType.nano:
return await NanoWalletService.compareDerivationMethods(mnemonic: seed, seedKey: null);
default:
break;
}
// throw Exception('Unexpected type: ${type.toString()}');
return [DerivationType.def];
}
@override
Future<WalletBase> process(WalletCredentials credentials) async {
if (mode == WalletRestoreMode.keys) {