finalizing wownero

This commit is contained in:
OmarHatem 2024-06-10 00:28:52 +02:00
parent 2ad76de4be
commit f48023b60c
15 changed files with 205 additions and 25 deletions

View file

@ -61,17 +61,17 @@ android {
} }
} }
signingConfigs { // signingConfigs {
release { // release {
keyAlias keystoreProperties['keyAlias'] // keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword'] // keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile']) // storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword'] // storePassword keystoreProperties['storePassword']
} // }
} // }
buildTypes { buildTypes {
release { release {
signingConfig signingConfigs.release // signingConfig signingConfigs.release
shrinkResources false shrinkResources false
minifyEnabled false minifyEnabled false

View file

@ -91,6 +91,9 @@
<data android:scheme="tron" /> <data android:scheme="tron" />
<data android:scheme="tron-wallet" /> <data android:scheme="tron-wallet" />
<data android:scheme="tron_wallet" /> <data android:scheme="tron_wallet" />
<data android:scheme="wownero" />
<data android:scheme="wownero-wallet" />
<data android:scheme="wownero_wallet" />
</intent-filter> </intent-filter>
<!-- nano-gpt link scheme --> <!-- nano-gpt link scheme -->
<intent-filter android:autoVerify="true"> <intent-filter android:autoVerify="true">

View file

@ -1,2 +1,8 @@
- -
uri: electrum.cakewallet.com:50002 uri: electrum.cakewallet.com:50002
useSSL: true
-
uri: btc-electrum.cakewallet.com:50002
isDefault: true
-
uri: electrs.cakewallet.com:50001

View file

@ -214,7 +214,7 @@ Now you can run the codebase and successfully create a wallet for type walletX s
**Restore Wallet** **Restore Wallet**
- Go to `lib/core/seed_validator.dart` - Go to `lib/core/seed_validator.dart`
- In the `getWordList` method, add a case to handle `WalletType.walletx` which would return the word list to be used to validate the passed in seeds. - In the `getWordList` method, add a case to handle `WalletType.walletx` which would return the word list to be used to validate the passed in seeds.
- Next, go to `lib/restore_view_model.dart` - Next, go to `lib/wallet_restore_view_model.dart`
- Modify the `hasRestoreFromPrivateKey` to reflect if walletx supports restore from Key - Modify the `hasRestoreFromPrivateKey` to reflect if walletx supports restore from Key
- Add a switch case to handle the various restore modes that walletX supports - Add a switch case to handle the various restore modes that walletX supports
- Modify the `getCredential` method to handle the restore flows for `WalletType.walletx` - Modify the `getCredential` method to handle the restore flows for `WalletType.walletx`

View file

@ -220,6 +220,26 @@
<string>tron-wallet</string> <string>tron-wallet</string>
</array> </array>
</dict> </dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>wownero</string>
<key>CFBundleURLSchemes</key>
<array>
<string>wownero</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>wownero-wallet</string>
<key>CFBundleURLSchemes</key>
<array>
<string>wownero-wallet</string>
</array>
</dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string> <string>$(CURRENT_PROJECT_VERSION)</string>

View file

@ -139,6 +139,7 @@ class AddressValidator extends TextValidator {
switch (type) { switch (type) {
case CryptoCurrency.xmr: case CryptoCurrency.xmr:
case CryptoCurrency.wow:
return null; return null;
case CryptoCurrency.ada: case CryptoCurrency.ada:
return null; return null;
@ -266,6 +267,7 @@ class AddressValidator extends TextValidator {
static String? getAddressFromStringPattern(CryptoCurrency type) { static String? getAddressFromStringPattern(CryptoCurrency type) {
switch (type) { switch (type) {
case CryptoCurrency.xmr: case CryptoCurrency.xmr:
case CryptoCurrency.wow:
return '([^0-9a-zA-Z]|^)4[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)' return '([^0-9a-zA-Z]|^)4[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)'
'|([^0-9a-zA-Z]|^)8[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)' '|([^0-9a-zA-Z]|^)8[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)'
'|([^0-9a-zA-Z]|^)[0-9a-zA-Z]{106}([^0-9a-zA-Z]|\$)'; '|([^0-9a-zA-Z]|^)[0-9a-zA-Z]{106}([^0-9a-zA-Z]|\$)';

View file

@ -229,6 +229,10 @@ Future<void> defaultSettingsMigration(
case 34: case 34:
await _addElectRsNode(nodes, sharedPreferences); await _addElectRsNode(nodes, sharedPreferences);
break; break;
case 36:
await addWowneroNodeList(nodes: nodes);
await changeWowneroCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
break;
default: default:
break; break;
} }
@ -488,9 +492,23 @@ Node? getTronDefaultNode({required Box<Node> nodes}) {
nodes.values.firstWhereOrNull((node) => node.type == WalletType.tron); nodes.values.firstWhereOrNull((node) => node.type == WalletType.tron);
} }
Node? getWowneroDefaultNode({required Box<Node> nodes}) { Node getWowneroDefaultNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == wowneroDefaultNodeUri) ?? final timeZone = DateTime.now().timeZoneOffset.inHours;
nodes.values.firstWhereOrNull((node) => node.type == WalletType.wownero); var nodeUri = '';
if (timeZone >= 1) {
// Eurasia
nodeUri = 'node2.monerodevs.org.lol:34568';
} else if (timeZone <= -4) {
// America
nodeUri = 'node3.monerodevs.org:34568';
}
try {
return nodes.values.firstWhere((Node node) => node.uriRaw == nodeUri);
} catch (_) {
return nodes.values.first;
}
} }
Future<void> insecureStorageMigration({ Future<void> insecureStorageMigration({
@ -1021,6 +1039,23 @@ Future<void> changeEthereumCurrentNodeToDefault(
await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, nodeId); await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, nodeId);
} }
Future<void> addWowneroNodeList({required Box<Node> nodes}) async {
final nodeList = await loadDefaultWowneroNodes();
for (var node in nodeList) {
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
await nodes.add(node);
}
}
}
Future<void> changeWowneroCurrentNodeToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final node = getWowneroDefaultNode(nodes: nodes);
final nodeId = node?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentWowneroNodeIdKey, nodeId);
}
Future<void> addNanoNodeList({required Box<Node> nodes}) async { Future<void> addNanoNodeList({required Box<Node> nodes}) async {
final nodeList = await loadDefaultNanoNodes(); final nodeList = await loadDefaultNanoNodes();
for (var node in nodeList) { for (var node in nodeList) {

View file

@ -183,6 +183,23 @@ Future<List<Node>> loadDefaultTronNodes() async {
return nodes; return nodes;
} }
Future<List<Node>> loadDefaultWowneroNodes() async {
final nodesRaw = await rootBundle.loadString('assets/wownero_node_list.yml');
final loadedNodes = loadYaml(nodesRaw) as YamlList;
final nodes = <Node>[];
for (final raw in loadedNodes) {
if (raw is Map) {
final node = Node.fromMap(Map<String, Object>.from(raw));
node.type = WalletType.wownero;
nodes.add(node);
}
}
return nodes;
}
Future<void> resetToDefault(Box<Node> nodeSource) async { Future<void> resetToDefault(Box<Node> nodeSource) async {
final moneroNodes = await loadDefaultNodes(); final moneroNodes = await loadDefaultNodes();
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList(); final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();

View file

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:io' show Platform;
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/buy/order.dart';
import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/auth_service.dart';
@ -42,7 +41,6 @@ import 'package:hive/hive.dart';
import 'package:cw_core/root_dir.dart'; import 'package:cw_core/root_dir.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:cw_core/window_size.dart'; import 'package:cw_core/window_size.dart';
import 'package:monero/monero.dart' as monero_dart;
final navigatorKey = GlobalKey<NavigatorState>(); final navigatorKey = GlobalKey<NavigatorState>();
final rootKey = GlobalKey<RootState>(); final rootKey = GlobalKey<RootState>();
@ -205,7 +203,7 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions, transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage, secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo, anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 34, initialMigrationVersion: 36,
); );
} }

View file

@ -125,6 +125,7 @@ abstract class BalanceViewModelBase with Store {
String get availableBalanceLabel { String get availableBalanceLabel {
switch (wallet.type) { switch (wallet.type) {
case WalletType.monero: case WalletType.monero:
case WalletType.wownero:
case WalletType.haven: case WalletType.haven:
case WalletType.ethereum: case WalletType.ethereum:
case WalletType.polygon: case WalletType.polygon:
@ -142,6 +143,7 @@ abstract class BalanceViewModelBase with Store {
String get additionalBalanceLabel { String get additionalBalanceLabel {
switch (wallet.type) { switch (wallet.type) {
case WalletType.monero: case WalletType.monero:
case WalletType.wownero:
case WalletType.haven: case WalletType.haven:
case WalletType.ethereum: case WalletType.ethereum:
case WalletType.polygon: case WalletType.polygon:

View file

@ -75,6 +75,15 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
viewKey: restoreWallet.viewKey ?? '', viewKey: restoreWallet.viewKey ?? '',
spendKey: restoreWallet.spendKey ?? '', spendKey: restoreWallet.spendKey ?? '',
height: restoreWallet.height ?? 0); height: restoreWallet.height ?? 0);
case WalletType.wownero:
return wownero!.createWowneroRestoreWalletFromKeysCredentials(
name: name,
password: password,
language: 'English',
address: restoreWallet.address ?? '',
viewKey: restoreWallet.viewKey ?? '',
spendKey: restoreWallet.spendKey ?? '',
height: restoreWallet.height ?? 0);
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
return bitcoin!.createBitcoinRestoreWalletFromWIFCredentials( return bitcoin!.createBitcoinRestoreWalletFromWIFCredentials(

View file

@ -36,6 +36,9 @@ class WalletRestoreFromQRCode {
'tron': WalletType.tron, 'tron': WalletType.tron,
'tron-wallet': WalletType.tron, 'tron-wallet': WalletType.tron,
'tron_wallet': WalletType.tron, 'tron_wallet': WalletType.tron,
'wownero': WalletType.wownero,
'wownero-wallet': WalletType.wownero,
'wownero_wallet': WalletType.wownero,
}; };
static bool _containsAssetSpecifier(String code) => _extractWalletType(code) != null; static bool _containsAssetSpecifier(String code) => _extractWalletType(code) != null;
@ -57,7 +60,9 @@ class WalletRestoreFromQRCode {
RegExp _getPattern(int wordCount) => RegExp _getPattern(int wordCount) =>
RegExp(r'(?<=\W|^)((?:\w+\s+){' + (wordCount - 1).toString() + r'}\w+)(?=\W|$)'); RegExp(r'(?<=\W|^)((?:\w+\s+){' + (wordCount - 1).toString() + r'}\w+)(?=\W|$)');
List<int> patternCounts = walletType == WalletType.monero ? [25, 16, 14, 13] : [24, 18, 12]; List<int> patternCounts = walletType == WalletType.monero || walletType == WalletType.wownero
? [25, 16, 14, 13]
: [24, 18, 12];
for (final count in patternCounts) { for (final count in patternCounts) {
final pattern = _getPattern(count); final pattern = _getPattern(count);

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
@ -76,7 +77,7 @@ abstract class TransactionDetailsViewModelBase with Store {
_addTronListItems(tx, dateFormat); _addTronListItems(tx, dateFormat);
break; break;
case WalletType.wownero: case WalletType.wownero:
// _addWowneroListItems(tx, dateFormat); _addWowneroListItems(tx, dateFormat);
break; break;
default: default:
break; break;
@ -169,7 +170,9 @@ abstract class TransactionDetailsViewModelBase with Store {
return 'https://solscan.io/tx/${txId}'; return 'https://solscan.io/tx/${txId}';
case WalletType.tron: case WalletType.tron:
return 'https://tronscan.org/#/transaction/${txId}'; return 'https://tronscan.org/#/transaction/${txId}';
default: case WalletType.wownero:
return 'https://explore.wownero.com/tx/${txId}';
case WalletType.none:
return ''; return '';
} }
} }
@ -197,7 +200,9 @@ abstract class TransactionDetailsViewModelBase with Store {
return S.current.view_transaction_on + 'solscan.io'; return S.current.view_transaction_on + 'solscan.io';
case WalletType.tron: case WalletType.tron:
return S.current.view_transaction_on + 'tronscan.org'; return S.current.view_transaction_on + 'tronscan.org';
default: case WalletType.wownero:
return S.current.view_transaction_on + 'Wownero.com';
case WalletType.none:
return ''; return '';
} }
} }
@ -442,4 +447,44 @@ abstract class TransactionDetailsViewModelBase with Store {
String get pendingTransactionFeeFiatAmountFormatted => sendViewModel.isFiatDisabled String get pendingTransactionFeeFiatAmountFormatted => sendViewModel.isFiatDisabled
? '' ? ''
: sendViewModel.pendingTransactionFeeFiatAmount + ' ' + sendViewModel.fiat.title; : sendViewModel.pendingTransactionFeeFiatAmount + ' ' + sendViewModel.fiat.title;
void _addWowneroListItems(TransactionInfo tx, DateFormat dateFormat) {
final key = tx.additionalInfo['key'] as String?;
final accountIndex = tx.additionalInfo['accountIndex'] as int;
final addressIndex = tx.additionalInfo['addressIndex'] as int;
final feeFormatted = tx.feeFormatted();
final _items = [
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
if (feeFormatted != null)
StandartListItem(title: S.current.transaction_details_fee, value: feeFormatted),
if (key?.isNotEmpty ?? false) StandartListItem(title: S.current.transaction_key, value: key!),
];
if (tx.direction == TransactionDirection.incoming) {
try {
final address = wownero!.getTransactionAddress(wallet, accountIndex, addressIndex);
final label = wownero!.getSubaddressLabel(wallet, accountIndex, addressIndex);
if (address.isNotEmpty) {
isRecipientAddressShown = true;
_items.add(StandartListItem(
title: S.current.transaction_details_recipient_address,
value: address,
));
}
if (label.isNotEmpty) {
_items.add(StandartListItem(title: S.current.address_label, value: label));
}
} catch (e) {
print(e.toString());
}
}
items.addAll(_items);
}
} }

View file

@ -17,6 +17,7 @@ import 'package:cake_wallet/utils/list_item.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/amount_converter.dart'; import 'package:cw_core/amount_converter.dart';
import 'package:cw_core/currency.dart'; import 'package:cw_core/currency.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
@ -191,6 +192,22 @@ class TronURI extends PaymentURI {
} }
} }
class WowneroURI extends PaymentURI {
WowneroURI({required String amount, required String address})
: super(amount: amount, address: address);
@override
String toString() {
var base = 'wownero:' + address;
if (amount.isNotEmpty) {
base += '?tx_amount=${amount.replaceAll(',', '.')}';
}
return base;
}
}
abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store { abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
WalletAddressListViewModelBase({ WalletAddressListViewModelBase({
required AppStore appStore, required AppStore appStore,
@ -293,6 +310,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
return TronURI(amount: amount, address: address.address); return TronURI(amount: amount, address: address.address);
} }
if (wallet.type == WalletType.wownero) {
return WowneroURI(amount: amount, address: address.address);
}
throw Exception('Unexpected type: ${type.toString()}'); throw Exception('Unexpected type: ${type.toString()}');
} }
@ -409,6 +430,20 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress)); addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
} }
if (wallet.type == WalletType.wownero) {
final primaryAddress = wownero!.getSubaddressList(wallet).subaddresses.first;
final addressItems = wownero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final isPrimary = subaddress == primaryAddress;
return WalletAddressListItem(
id: subaddress.id,
isPrimary: isPrimary,
name: subaddress.label,
address: subaddress.address);
});
addressList.addAll(addressItems);
}
if (searchText.isNotEmpty) { if (searchText.isNotEmpty) {
return ObservableList.of(addressList.where((item) { return ObservableList.of(addressList.where((item) {
if (item is WalletAddressListItem) { if (item is WalletAddressListItem) {

View file

@ -29,8 +29,10 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService, WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService,
Box<WalletInfo> walletInfoSource, Box<WalletInfo> walletInfoSource,
{required WalletType type}) {required WalletType type})
: hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven, : hasSeedLanguageSelector =
hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven, type == WalletType.monero || type == WalletType.haven || type == WalletType.wownero,
hasBlockchainHeightLanguageSelector =
type == WalletType.monero || type == WalletType.haven || type == WalletType.wownero,
hasRestoreFromPrivateKey = type == WalletType.ethereum || hasRestoreFromPrivateKey = type == WalletType.ethereum ||
type == WalletType.polygon || type == WalletType.polygon ||
type == WalletType.nano || type == WalletType.nano ||
@ -112,6 +114,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials( return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials(
name: name, mnemonic: seed, password: password); name: name, mnemonic: seed, password: password);
case WalletType.nano: case WalletType.nano:
case WalletType.banano:
return nano!.createNanoRestoreWalletFromSeedCredentials( return nano!.createNanoRestoreWalletFromSeedCredentials(
name: name, name: name,
mnemonic: seed, mnemonic: seed,
@ -143,7 +146,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
password: password, password: password,
height: height, height: height,
); );
default: case WalletType.none:
break; break;
} }
} }