diff --git a/android/app/build.gradle b/android/app/build.gradle
index 5e27aeb9e..70e9f299e 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -61,17 +61,17 @@ android {
}
}
- signingConfigs {
- release {
- keyAlias keystoreProperties['keyAlias']
- keyPassword keystoreProperties['keyPassword']
- storeFile file(keystoreProperties['storeFile'])
- storePassword keystoreProperties['storePassword']
- }
- }
+// signingConfigs {
+// release {
+// keyAlias keystoreProperties['keyAlias']
+// keyPassword keystoreProperties['keyPassword']
+// storeFile file(keystoreProperties['storeFile'])
+// storePassword keystoreProperties['storePassword']
+// }
+// }
buildTypes {
release {
- signingConfig signingConfigs.release
+// signingConfig signingConfigs.release
shrinkResources false
minifyEnabled false
diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml
index 0c0c74578..b03c8a925 100644
--- a/android/app/src/main/AndroidManifestBase.xml
+++ b/android/app/src/main/AndroidManifestBase.xml
@@ -91,6 +91,9 @@
+
+
+
diff --git a/assets/bitcoin_electrum_server_list.yml b/assets/bitcoin_electrum_server_list.yml
index 2b6649271..8b734a7bb 100644
--- a/assets/bitcoin_electrum_server_list.yml
+++ b/assets/bitcoin_electrum_server_list.yml
@@ -1,2 +1,8 @@
-
- uri: electrum.cakewallet.com:50002
\ No newline at end of file
+ uri: electrum.cakewallet.com:50002
+ useSSL: true
+-
+ uri: btc-electrum.cakewallet.com:50002
+ isDefault: true
+-
+ uri: electrs.cakewallet.com:50001
diff --git a/how_to_add_new_wallet_type.md b/how_to_add_new_wallet_type.md
index f51428d44..917e87cf4 100644
--- a/how_to_add_new_wallet_type.md
+++ b/how_to_add_new_wallet_type.md
@@ -214,7 +214,7 @@ Now you can run the codebase and successfully create a wallet for type walletX s
**Restore Wallet**
- 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.
-- 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
- 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`
diff --git a/ios/Runner/InfoBase.plist b/ios/Runner/InfoBase.plist
index 83e60b542..aec00022b 100644
--- a/ios/Runner/InfoBase.plist
+++ b/ios/Runner/InfoBase.plist
@@ -200,7 +200,7 @@
solana-wallet
-
+
CFBundleTypeRole
Viewer
CFBundleURLName
@@ -220,6 +220,26 @@
tron-wallet
+
+ CFBundleTypeRole
+ Viewer
+ CFBundleURLName
+ wownero
+ CFBundleURLSchemes
+
+ wownero
+
+
+
+ CFBundleTypeRole
+ Viewer
+ CFBundleURLName
+ wownero-wallet
+ CFBundleURLSchemes
+
+ wownero-wallet
+
+
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart
index fe6629f51..ee7e7e54a 100644
--- a/lib/core/address_validator.dart
+++ b/lib/core/address_validator.dart
@@ -139,6 +139,7 @@ class AddressValidator extends TextValidator {
switch (type) {
case CryptoCurrency.xmr:
+ case CryptoCurrency.wow:
return null;
case CryptoCurrency.ada:
return null;
@@ -266,6 +267,7 @@ class AddressValidator extends TextValidator {
static String? getAddressFromStringPattern(CryptoCurrency type) {
switch (type) {
case CryptoCurrency.xmr:
+ case CryptoCurrency.wow:
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]|^)[0-9a-zA-Z]{106}([^0-9a-zA-Z]|\$)';
diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart
index f3680084b..a8ec55c12 100644
--- a/lib/entities/default_settings_migration.dart
+++ b/lib/entities/default_settings_migration.dart
@@ -229,6 +229,10 @@ Future defaultSettingsMigration(
case 34:
await _addElectRsNode(nodes, sharedPreferences);
break;
+ case 36:
+ await addWowneroNodeList(nodes: nodes);
+ await changeWowneroCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
+ break;
default:
break;
}
@@ -488,9 +492,23 @@ Node? getTronDefaultNode({required Box nodes}) {
nodes.values.firstWhereOrNull((node) => node.type == WalletType.tron);
}
-Node? getWowneroDefaultNode({required Box nodes}) {
- return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == wowneroDefaultNodeUri) ??
- nodes.values.firstWhereOrNull((node) => node.type == WalletType.wownero);
+Node getWowneroDefaultNode({required Box nodes}) {
+ final timeZone = DateTime.now().timeZoneOffset.inHours;
+ 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 insecureStorageMigration({
@@ -1021,6 +1039,23 @@ Future changeEthereumCurrentNodeToDefault(
await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, nodeId);
}
+Future addWowneroNodeList({required Box 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 changeWowneroCurrentNodeToDefault(
+ {required SharedPreferences sharedPreferences, required Box nodes}) async {
+ final node = getWowneroDefaultNode(nodes: nodes);
+ final nodeId = node?.key as int? ?? 0;
+
+ await sharedPreferences.setInt(PreferencesKey.currentWowneroNodeIdKey, nodeId);
+}
+
Future addNanoNodeList({required Box nodes}) async {
final nodeList = await loadDefaultNanoNodes();
for (var node in nodeList) {
diff --git a/lib/entities/node_list.dart b/lib/entities/node_list.dart
index c1211d2fe..85e37a7bc 100644
--- a/lib/entities/node_list.dart
+++ b/lib/entities/node_list.dart
@@ -183,6 +183,23 @@ Future> loadDefaultTronNodes() async {
return nodes;
}
+Future> loadDefaultWowneroNodes() async {
+ final nodesRaw = await rootBundle.loadString('assets/wownero_node_list.yml');
+ final loadedNodes = loadYaml(nodesRaw) as YamlList;
+ final nodes = [];
+
+ for (final raw in loadedNodes) {
+ if (raw is Map) {
+ final node = Node.fromMap(Map.from(raw));
+
+ node.type = WalletType.wownero;
+ nodes.add(node);
+ }
+ }
+
+ return nodes;
+}
+
Future resetToDefault(Box nodeSource) async {
final moneroNodes = await loadDefaultNodes();
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
diff --git a/lib/main.dart b/lib/main.dart
index 3618e40d3..03ca018d3 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,5 +1,4 @@
import 'dart:async';
-import 'dart:io' show Platform;
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/buy/order.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:shared_preferences/shared_preferences.dart';
import 'package:cw_core/window_size.dart';
-import 'package:monero/monero.dart' as monero_dart;
final navigatorKey = GlobalKey();
final rootKey = GlobalKey();
@@ -205,7 +203,7 @@ Future initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
- initialMigrationVersion: 34,
+ initialMigrationVersion: 36,
);
}
diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart
index 5ae532bb6..c8acb9c2c 100644
--- a/lib/view_model/dashboard/balance_view_model.dart
+++ b/lib/view_model/dashboard/balance_view_model.dart
@@ -125,6 +125,7 @@ abstract class BalanceViewModelBase with Store {
String get availableBalanceLabel {
switch (wallet.type) {
case WalletType.monero:
+ case WalletType.wownero:
case WalletType.haven:
case WalletType.ethereum:
case WalletType.polygon:
@@ -142,6 +143,7 @@ abstract class BalanceViewModelBase with Store {
String get additionalBalanceLabel {
switch (wallet.type) {
case WalletType.monero:
+ case WalletType.wownero:
case WalletType.haven:
case WalletType.ethereum:
case WalletType.polygon:
diff --git a/lib/view_model/restore/restore_from_qr_vm.dart b/lib/view_model/restore/restore_from_qr_vm.dart
index c92612580..0ddfb56ce 100644
--- a/lib/view_model/restore/restore_from_qr_vm.dart
+++ b/lib/view_model/restore/restore_from_qr_vm.dart
@@ -75,6 +75,15 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
viewKey: restoreWallet.viewKey ?? '',
spendKey: restoreWallet.spendKey ?? '',
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.litecoin:
return bitcoin!.createBitcoinRestoreWalletFromWIFCredentials(
diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart
index 09b5c9d96..37a0ad162 100644
--- a/lib/view_model/restore/wallet_restore_from_qr_code.dart
+++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart
@@ -36,6 +36,9 @@ class WalletRestoreFromQRCode {
'tron': 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;
@@ -57,7 +60,9 @@ class WalletRestoreFromQRCode {
RegExp _getPattern(int wordCount) =>
RegExp(r'(?<=\W|^)((?:\w+\s+){' + (wordCount - 1).toString() + r'}\w+)(?=\W|$)');
- List patternCounts = walletType == WalletType.monero ? [25, 16, 14, 13] : [24, 18, 12];
+ List patternCounts = walletType == WalletType.monero || walletType == WalletType.wownero
+ ? [25, 16, 14, 13]
+ : [24, 18, 12];
for (final count in patternCounts) {
final pattern = _getPattern(count);
diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart
index a8ff6afa9..5b7a1a8db 100644
--- a/lib/view_model/transaction_details_view_model.dart
+++ b/lib/view_model/transaction_details_view_model.dart
@@ -1,4 +1,5 @@
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/transaction_info.dart';
import 'package:cw_core/wallet_type.dart';
@@ -76,7 +77,7 @@ abstract class TransactionDetailsViewModelBase with Store {
_addTronListItems(tx, dateFormat);
break;
case WalletType.wownero:
- // _addWowneroListItems(tx, dateFormat);
+ _addWowneroListItems(tx, dateFormat);
break;
default:
break;
@@ -169,7 +170,9 @@ abstract class TransactionDetailsViewModelBase with Store {
return 'https://solscan.io/tx/${txId}';
case WalletType.tron:
return 'https://tronscan.org/#/transaction/${txId}';
- default:
+ case WalletType.wownero:
+ return 'https://explore.wownero.com/tx/${txId}';
+ case WalletType.none:
return '';
}
}
@@ -197,7 +200,9 @@ abstract class TransactionDetailsViewModelBase with Store {
return S.current.view_transaction_on + 'solscan.io';
case WalletType.tron:
return S.current.view_transaction_on + 'tronscan.org';
- default:
+ case WalletType.wownero:
+ return S.current.view_transaction_on + 'Wownero.com';
+ case WalletType.none:
return '';
}
}
@@ -442,4 +447,44 @@ abstract class TransactionDetailsViewModelBase with Store {
String get pendingTransactionFeeFiatAmountFormatted => sendViewModel.isFiatDisabled
? ''
: 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);
+ }
}
diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
index 6b59c9033..dd7f02407 100644
--- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
@@ -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_address_list_header.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/currency.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 {
WalletAddressListViewModelBase({
required AppStore appStore,
@@ -293,6 +310,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
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()}');
}
@@ -409,6 +430,20 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
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) {
return ObservableList.of(addressList.where((item) {
if (item is WalletAddressListItem) {
diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart
index 22e86ed95..41d17e99d 100644
--- a/lib/view_model/wallet_restore_view_model.dart
+++ b/lib/view_model/wallet_restore_view_model.dart
@@ -29,8 +29,10 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService,
Box walletInfoSource,
{required WalletType type})
- : hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven,
- hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven,
+ : hasSeedLanguageSelector =
+ type == WalletType.monero || type == WalletType.haven || type == WalletType.wownero,
+ hasBlockchainHeightLanguageSelector =
+ type == WalletType.monero || type == WalletType.haven || type == WalletType.wownero,
hasRestoreFromPrivateKey = type == WalletType.ethereum ||
type == WalletType.polygon ||
type == WalletType.nano ||
@@ -112,6 +114,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials(
name: name, mnemonic: seed, password: password);
case WalletType.nano:
+ case WalletType.banano:
return nano!.createNanoRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
@@ -143,7 +146,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
password: password,
height: height,
);
- default:
+ case WalletType.none:
break;
}
}