cake_wallet/lib/nano/cw_nano.dart
David Adegoke 4e2e5e708c
CW-580: BIP39 Wallets Shared Seed Implementation: "One Seed - Multiple Wallets" (#1307)
* feat: Implement creating new BIP39 wallet with same seed used for other owned BIP39 wallets

* feat: Use same seed for BIP39 Wallets

* Update pre_existing_seeds_page.dart

* Feat: BIP39 Same seed wallet creation using the Common Parent Wallet Strategy

* feat: Finalize implementing preexisting seeds

* feat: Implement shared bip39 wallet seed for Bitcoin wallet type

* feat: Implement shared bip39 wallet seed for Litecoin wallet type

* feat: Implement shared bip39 wallet seed for BitcoinCash wallet type

* feat: Implement shared bip39 wallet seed for Nano wallet type, although disabled entry for now

* fix: Remove non bip39 seed wallet type from listing

* feat: Implement grouped and single wallets lists in wallets listing page and implement editing and saving group names

* fix: Issue where the ontap always references the leadwallet, also make shared seed wallets section header only display when the multi wallet groups list is not empty

* fix: Add translation and adjust the way the groups display

* feat: Activate bip39 as an option for creating Nano wallet types

* fix: Handle edgecase with creating new wallet with group address, handle case where only bip39 derivation type is allowed with child wallets, activate nano wallet type for shared seed

* chore: Modify the UI to fit adjustment made on figma

* fix: Disposed box triggering error in hive and causing wallet list view to display error

* fix: Switch wallet groups title in wallets list page and also fix issue with renaming groups

* Update lib/reactions/bip39_wallet_utils.dart [skip ci]

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* Update lib/router.dart [skip ci]

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* fix: Review fixes: Combine New Wallet Page Type arguments into a single model

* fix: Review fixes: Add failure guard when fetching mnemonic for selected wallet in pre-existing wallets page

* fix: Review fixes - Add loading indicator when mnemonic is being selected for wallet

* fix: Review fixes - Modify variable name to avoid clashes

* fix: Review fixes - Access WalletManager through dependency injection instead of service location

* fix: Review fixes - Add testnet to convertWalletInfoToWalletlistItem function, and adjust according where used

* fix: Review fixes - Add walletPassword to nano, tron and wownero wallets and confirm it is properly handled as it should be

* fix: Remove leadWallet, modify filtering flow to reflect this and not depend on leadWallet, and adjust privacy settings

* fix: Review Fixes - Modify restore flow to reflect current nature of bip39 as default for majority of wallet types

* fix: QA Fixes - Modify preexisting page to display wallet group names if set, and display them in incremental order if not set

* fix: Add wallet group description page and rename pre-existingseeds page to wallet group display page

* fix: Product Fix - Rename pre-existing seeds file name to wallet group display filename

* fix: Product fix - Separate multiwallets groups from single wallets and display separately

* fix - Product Fix - Add empty state for wallet group listing when creating a new wallet, adjust CTAs across buttons relating to the flow also

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
2024-09-20 21:25:08 +03:00

413 lines
12 KiB
Dart

part of 'nano.dart';
class CWNanoAccountList extends NanoAccountList {
CWNanoAccountList(this._wallet);
final Object _wallet;
@override
@computed
ObservableList<NanoAccount> get accounts {
final nanoWallet = _wallet as NanoWallet;
final accounts = nanoWallet.walletAddresses.accountList.accounts
.map((acc) => NanoAccount(id: acc.id, label: acc.label, balance: acc.balance))
.toList();
return ObservableList<NanoAccount>.of(accounts);
}
@override
void update(Object wallet) {
final nanoWallet = wallet as NanoWallet;
nanoWallet.walletAddresses.accountList.update(null);
}
@override
void refresh(Object wallet) {
final nanoWallet = wallet as NanoWallet;
nanoWallet.walletAddresses.accountList.refresh();
}
@override
Future<List<NanoAccount>> getAll(Object wallet) async {
final nanoWallet = wallet as NanoWallet;
return (await nanoWallet.walletAddresses.accountList.getAll())
.map((acc) => NanoAccount(id: acc.id, label: acc.label, balance: acc.balance))
.toList();
}
@override
Future<void> addAccount(Object wallet, {required String label}) async {
final nanoWallet = wallet as NanoWallet;
await nanoWallet.walletAddresses.accountList.addAccount(label: label);
}
@override
Future<void> setLabelAccount(Object wallet,
{required int accountIndex, required String label}) async {
final nanoWallet = wallet as NanoWallet;
await nanoWallet.walletAddresses.accountList
.setLabelAccount(accountIndex: accountIndex, label: label);
}
}
class CWNano extends Nano {
@override
NanoAccountList getAccountList(Object wallet) {
return CWNanoAccountList(wallet);
}
@override
Account getCurrentAccount(Object wallet) {
final nanoWallet = wallet as NanoWallet;
final acc = nanoWallet.walletAddresses.account;
return Account(id: acc!.id, label: acc.label, balance: acc.balance);
}
@override
void setCurrentAccount(Object wallet, int id, String label, String? balance) {
final nanoWallet = wallet as NanoWallet;
nanoWallet.walletAddresses.account = NanoAccount(id: id, label: label, balance: balance);
nanoWallet.regenerateAddress();
}
@override
List<String> getNanoWordList(String language) {
return NanoMnemomics.WORDLIST;
}
@override
WalletService createNanoWalletService(Box<WalletInfo> walletInfoSource, bool isDirect) {
return NanoWalletService(walletInfoSource, isDirect);
}
@override
Map<String, String> getKeys(Object wallet) {
final nanoWallet = wallet as NanoWallet;
final keys = nanoWallet.keys;
return <String, String>{
"seedKey": keys.seedKey,
};
}
@override
WalletCredentials createNanoNewWalletCredentials({
required String name,
WalletInfo? walletInfo,
String? password,
String? mnemonic,
String? parentAddress,
}) =>
NanoNewWalletCredentials(
name: name,
password: password,
mnemonic: mnemonic,
parentAddress: parentAddress,
walletInfo: walletInfo,
);
@override
WalletCredentials createNanoRestoreWalletFromSeedCredentials({
required String name,
required String password,
required String mnemonic,
required DerivationType derivationType,
}) {
if (mnemonic.split(" ").length == 12 && derivationType != DerivationType.bip39) {
throw Exception("Invalid mnemonic for derivation type!");
}
return NanoRestoreWalletFromSeedCredentials(
name: name,
password: password,
mnemonic: mnemonic,
derivationType: derivationType,
);
}
@override
WalletCredentials createNanoRestoreWalletFromKeysCredentials({
required String name,
required String password,
required String seedKey,
required DerivationType derivationType,
}) {
if (seedKey.length == 128 && derivationType != DerivationType.bip39) {
throw Exception("Invalid seed key length for derivation type!");
}
return NanoRestoreWalletFromKeysCredentials(
name: name,
password: password,
seedKey: seedKey,
derivationType: derivationType,
);
}
@override
Object createNanoTransactionCredentials(List<Output> outputs) {
return NanoTransactionCredentials(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
))
.toList(),
);
}
@override
Future<void> changeRep(Object wallet, String address) async {
if ((wallet as NanoWallet).transactionHistory.transactions.isEmpty) {
throw Exception("Can't change representative without an existing transaction history");
}
return wallet.changeRep(address);
}
@override
Future<bool> updateTransactions(Object wallet) async {
return (wallet as NanoWallet).updateTransactions();
}
@override
BigInt getTransactionAmountRaw(TransactionInfo transactionInfo) {
return (transactionInfo as NanoTransactionInfo).amountRaw;
}
@override
String getRepresentative(Object wallet) {
return (wallet as NanoWallet).representative;
}
@override
Future<List<N2Node>> getN2Reps(Object wallet) async {
return (wallet as NanoWallet).getN2Reps();
}
@override
bool isRepOk(Object wallet) {
return (wallet as NanoWallet).isRepOk;
}
}
class CWNanoUtil extends NanoUtil {
@override
bool isValidBip39Seed(String seed) {
return NanoDerivations.isValidBip39Seed(seed);
}
// number util:
static const int maxDecimalDigits = 6; // Max digits after decimal
BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000");
BigInt rawPerNyano = BigInt.parse("1000000000000000000000000");
BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
BigInt rawPerXMR = BigInt.parse("1000000000000");
BigInt convertXMRtoNano = BigInt.parse("1000000000000000000");
@override
String getRawAsUsableString(String? raw, BigInt rawPerCur) {
return NanoAmounts.getRawAsUsableString(raw, rawPerCur);
}
@override
String getRawAccuracy(String? raw, BigInt rawPerCur) {
return NanoAmounts.getRawAccuracy(raw, rawPerCur);
}
@override
String getAmountAsRaw(String amount, BigInt rawPerCur) {
return NanoAmounts.getAmountAsRaw(amount, rawPerCur);
}
@override
Future<AccountInfoResponse?> getInfoFromSeedOrMnemonic(
DerivationType derivationType, {
String? seedKey,
String? mnemonic,
required Node node,
}) async {
NanoClient nanoClient = NanoClient();
nanoClient.connect(node);
String? publicAddress;
if (seedKey == null && mnemonic == null) {
throw Exception("One of seed key OR mnemonic must be provided!");
}
try {
if (seedKey != null) {
if (seedKey.length == 64) {
try {
mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey);
} catch (e) {
print("not a valid 'nano' seed key");
}
}
if (derivationType == DerivationType.bip39) {
publicAddress = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
} else if (derivationType == DerivationType.nano) {
publicAddress = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
}
}
if (derivationType == DerivationType.bip39) {
if (mnemonic != null) {
seedKey = await NanoDerivations.hdMnemonicListToSeed(mnemonic.split(' '));
publicAddress = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
}
}
if (derivationType == DerivationType.nano) {
if (mnemonic != null) {
seedKey = await NanoDerivations.standardMnemonicToSeed(mnemonic);
publicAddress = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
}
}
} catch (_) {}
if (publicAddress == null) {
// we couldn't derive a public address for the derivation type provided
// i.e. a bip39 seed was provided and we were instructed to derive a "nano" type address
return null;
}
AccountInfoResponse? accountInfo = await nanoClient.getAccountInfo(publicAddress);
if (accountInfo == null) {
accountInfo = AccountInfoResponse(
frontier: "", balance: "0", representative: "", confirmationHeight: 0);
}
accountInfo.address = publicAddress;
return accountInfo;
}
@override
Future<List<DerivationType>> compareDerivationMethods({
String? mnemonic,
String? privateKey,
required Node node,
}) async {
String? seedKey = privateKey;
if (mnemonic?.split(' ').length == 12) {
return [DerivationType.bip39];
}
if (seedKey?.length == 128) {
return [DerivationType.bip39];
} else if (seedKey?.length == 64) {
try {
mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey!);
} catch (e) {
print("not a valid 'nano' seed key");
}
}
late String publicAddressStandard;
late String publicAddressBip39;
try {
NanoClient nanoClient = NanoClient();
nanoClient.connect(node);
if (mnemonic != null) {
seedKey = await NanoDerivations.hdMnemonicListToSeed(mnemonic.split(' '));
publicAddressBip39 = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
seedKey = await NanoDerivations.standardMnemonicToSeed(mnemonic);
publicAddressStandard = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
} else if (seedKey != null) {
try {
publicAddressBip39 = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
} catch (e) {
return [DerivationType.nano];
}
try {
publicAddressStandard = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
} catch (e) {
return [DerivationType.bip39];
}
}
// check if account has a history:
AccountInfoResponse? bip39Info;
AccountInfoResponse? standardInfo;
try {
bip39Info = await nanoClient.getAccountInfo(publicAddressBip39);
} catch (e) {
bip39Info = null;
}
try {
standardInfo = await nanoClient.getAccountInfo(publicAddressStandard);
} catch (e) {
standardInfo = null;
}
// one of these is *probably* null:
if (bip39Info == null && standardInfo != null) {
return [DerivationType.nano];
} else if (standardInfo == null && bip39Info != null) {
return [DerivationType.bip39];
}
// we don't know for sure:
return [DerivationType.nano, DerivationType.bip39];
} catch (e) {
return [DerivationType.nano, DerivationType.bip39];
}
}
@override
Future<List<DerivationInfo>> getDerivationsFromMnemonic({
String? mnemonic,
String? seedKey,
required Node node,
}) async {
List<DerivationInfo> list = [];
List<DerivationType> possibleDerivationTypes = await compareDerivationMethods(
mnemonic: mnemonic,
privateKey: seedKey,
node: node,
);
if (possibleDerivationTypes.length == 1) {
return [DerivationInfo(derivationType: possibleDerivationTypes.first)];
}
AccountInfoResponse? bip39Info = await nanoUtil!.getInfoFromSeedOrMnemonic(
DerivationType.bip39,
mnemonic: mnemonic,
seedKey: seedKey,
node: node,
);
AccountInfoResponse? standardInfo = await nanoUtil!.getInfoFromSeedOrMnemonic(
DerivationType.nano,
mnemonic: mnemonic,
seedKey: seedKey,
node: node,
);
if (standardInfo?.confirmationHeight != null && standardInfo!.confirmationHeight > 0) {
list.add(DerivationInfo(
derivationType: DerivationType.nano,
balance: nanoUtil!.getRawAsUsableString(standardInfo.balance, nanoUtil!.rawPerNano),
address: standardInfo.address!,
transactionsCount: standardInfo.confirmationHeight,
));
}
if (bip39Info?.confirmationHeight != null && bip39Info!.confirmationHeight > 0) {
list.add(DerivationInfo(
derivationType: DerivationType.bip39,
balance: nanoUtil!.getRawAsUsableString(bip39Info.balance, nanoUtil!.rawPerNano),
address: bip39Info.address!,
transactionsCount: bip39Info.confirmationHeight,
));
}
return list;
}
}