Generic fixes and enhancements (#1083)

* Add exception handler to fiat APIs Increase send card size for coin control Fix Monero.com unspent coins hive box issue minor bug fix * Remove EIP-1559 parameters from Eth transaction Enhance error reporting * Throw error if not enough monero utx outputs are selected * Fix Search text color * Fix Ethereum sending EIP-1559 transactions * Add transaction data to ERC20 transactions * Add input check in single output transactions as well * Fix Node deletion issue Handle user input error in anonpay * Remove exception handler from fiat conversion since it's not working with isolates * Require enough utxo for amount and fees; More insightful Error messages * Add cakewallet to applinks [skip ci] * Add cakewallet app link for iOS [skip ci] * Add applink depending on app scheme variable * Add applink in iOS custom to the app getting built [skip ci] * Handle normal app links without considering them as Payment URIs * Minor fix [skip ci] * Fixate encrypt package version as the recent update they made has some issues [skip ci] --------- Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>

Omar Hatem committed Sep 14, 2023 at 22:14 UTC ce4d375abfa90f0a69527ae94fcb87ceb6914f71
18 files changed +94 -16
android/app/src/main/AndroidManifestBase.xml
+1
@@ -46,6 +46,7 @@
46 <action android:name="android.intent.action.VIEW" />
47 <category android:name="android.intent.category.DEFAULT" />
48 <category android:name="android.intent.category.BROWSABLE" />
49 + <data android:scheme="__APP_SCHEME__" />
50 <data android:scheme="bitcoin" />
51 <data android:scheme="bitcoin-wallet" />
52 <data android:scheme="bitcoin_wallet" />
cw_ethereum/lib/ethereum_client.dart
+2 -1
@@ -93,6 +93,7 @@ class EthereumClient {
93 EthereumAddress.fromHex(toAddress),
94 BigInt.parse(amount),
95 credentials: privateKey,
96 + transaction: transaction,
97 );
98 };
99 }
@@ -107,7 +108,7 @@ class EthereumClient {
108 }
109
110 Future<String> sendTransaction(Uint8List signedTransaction) async =>
110 - await _client!.sendRawTransaction(signedTransaction);
111 + await _client!.sendRawTransaction(prependTransactionType(0x02, signedTransaction));
112
113 Future getTransactionDetails(String transactionHash) async {
114 // Wait for the transaction receipt to become available
cw_monero/lib/exceptions/monero_transaction_no_inputs_exception.dart
+5 -1
@@ -1,4 +1,8 @@
1 class MoneroTransactionNoInputsException implements Exception {
2 + MoneroTransactionNoInputsException(this.inputsSize);
3 +
4 + int inputsSize;
5 +
6 @override
3 - String toString() => 'Not enough inputs available. Please select more under Coin Control';
7 + String toString() => 'Not enough inputs ($inputsSize) selected. Please select more under Coin Control';
8 }
cw_monero/lib/monero_wallet.dart
+12 -1
@@ -210,7 +210,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
210 }
211
212 if (inputs.isEmpty) {
213 - throw MoneroTransactionNoInputsException();
213 + throw MoneroTransactionNoInputsException(0);
214 }
215
216 if (hasMultiDestination) {
@@ -222,10 +222,15 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
222 final int totalAmount = outputs.fold(0, (acc, value) =>
223 acc + (value.formattedCryptoAmount ?? 0));
224
225 + final estimatedFee = calculateEstimatedFee(_credentials.priority, totalAmount);
226 if (unlockedBalance < totalAmount) {
227 throw MoneroTransactionCreationException('You do not have enough XMR to send this amount.');
228 }
229
230 + if (allInputsAmount < totalAmount + estimatedFee) {
231 + throw MoneroTransactionNoInputsException(inputs.length);
232 + }
233 +
234 final moneroOutputs = outputs.map((output) {
235 final outputAddress = output.isParsedAddress
236 ? output.extractedAddress
@@ -262,6 +267,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
267 'You do not have enough unlocked balance. Unlocked: $formattedBalance. Transaction amount: ${output.cryptoAmount}.');
268 }
269
270 + final estimatedFee = calculateEstimatedFee(_credentials.priority, formattedAmount);
271 + if ((formattedAmount != null && allInputsAmount < (formattedAmount + estimatedFee)) ||
272 + (formattedAmount == null && allInputsAmount != unlockedBalance)) {
273 + throw MoneroTransactionNoInputsException(inputs.length);
274 + }
275 +
276 pendingTransactionDescription = await transaction_history.createTransaction(
277 address: address!,
278 amount: amount,
ios/Podfile.lock
+23
@@ -8,6 +8,25 @@ PODS:
8 - Flutter
9 - ReachabilitySwift
10 - CryptoSwift (1.7.1)
11 + - cw_haven (0.0.1):
12 + - cw_haven/Boost (= 0.0.1)
13 + - cw_haven/Haven (= 0.0.1)
14 + - cw_haven/OpenSSL (= 0.0.1)
15 + - cw_haven/Sodium (= 0.0.1)
16 + - cw_shared_external
17 + - Flutter
18 + - cw_haven/Boost (0.0.1):
19 + - cw_shared_external
20 + - Flutter
21 + - cw_haven/Haven (0.0.1):
22 + - cw_shared_external
23 + - Flutter
24 + - cw_haven/OpenSSL (0.0.1):
25 + - cw_shared_external
26 + - Flutter
27 + - cw_haven/Sodium (0.0.1):
28 + - cw_shared_external
29 + - Flutter
30 - cw_monero (0.0.2):
31 - cw_monero/Boost (= 0.0.2)
32 - cw_monero/Monero (= 0.0.2)
@@ -140,6 +159,7 @@ DEPENDENCIES:
159 - barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`)
160 - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
161 - CryptoSwift
162 + - cw_haven (from `.symlinks/plugins/cw_haven/ios`)
163 - cw_monero (from `.symlinks/plugins/cw_monero/ios`)
164 - cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`)
165 - device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`)
@@ -185,6 +205,8 @@ EXTERNAL SOURCES:
205 :path: ".symlinks/plugins/barcode_scan2/ios"
206 connectivity_plus:
207 :path: ".symlinks/plugins/connectivity_plus/ios"
208 + cw_haven:
209 + :path: ".symlinks/plugins/cw_haven/ios"
210 cw_monero:
211 :path: ".symlinks/plugins/cw_monero/ios"
212 cw_shared_external:
@@ -239,6 +261,7 @@ SPEC CHECKSUMS:
261 BigInt: f668a80089607f521586bbe29513d708491ef2f7
262 connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e
263 CryptoSwift: d3d18dc357932f7e6d580689e065cf1f176007c1
264 + cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a
265 cw_monero: 4cf3b96f2da8e95e2ef7d6703dd4d2c509127b7d
266 cw_shared_external: 2972d872b8917603478117c9957dfca611845a92
267 device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7
ios/Runner/InfoBase.plist
+4
@@ -36,6 +36,10 @@
36 <string>cakewallet</string>
37 </array>
38 </dict>
39 + <dict>
40 + <key>CFBundleTypeRole</key>
41 + <string>Editor</string>
42 + </dict>
43 <dict>
44 <key>CFBundleTypeRole</key>
45 <string>Editor</string>
lib/main.dart
+1 -1
@@ -114,7 +114,7 @@ Future<void> initializeAppConfigs() async {
114 CakeHive.registerAdapter(OrderAdapter());
115 }
116
117 - if (!isMoneroOnly && !CakeHive.isAdapterRegistered(UnspentCoinsInfo.typeId)) {
117 + if (!CakeHive.isAdapterRegistered(UnspentCoinsInfo.typeId)) {
118 CakeHive.registerAdapter(UnspentCoinsInfoAdapter());
119 }
120
lib/src/screens/dashboard/home_settings_page.dart
+3 -1
@@ -9,6 +9,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.da
9 import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
10 import 'package:cake_wallet/themes/extensions/address_theme.dart';
11 import 'package:cake_wallet/themes/extensions/menu_theme.dart';
12 +import 'package:cake_wallet/themes/extensions/picker_theme.dart';
13 import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
14 import 'package:flutter/material.dart';
15 import 'package:flutter_mobx/flutter_mobx.dart';
@@ -55,7 +56,8 @@ class HomeSettingsPage extends BasePage {
56 padding: const EdgeInsetsDirectional.only(start: 16),
57 child: TextFormField(
58 controller: _searchController,
58 - style: TextStyle(color: Theme.of(context).dialogTheme.backgroundColor),
59 + style: TextStyle(
60 + color: Theme.of(context).extension<PickerTheme>()!.searchHintColor),
61 decoration: InputDecoration(
62 hintText: S.of(context).search_add_token,
63 prefixIcon: Image.asset("assets/images/search_icon.png"),
lib/src/screens/root/root.dart
+5 -3
@@ -140,7 +140,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
140 }
141 _reset();
142 totpAuth.close(
143 - route: launchUri != null ? Routes.send : null,
143 + route: _isValidPaymentUri() ? Routes.send : null,
144 arguments: PaymentRequest.fromUri(launchUri),
145 );
146 launchUri = null;
@@ -152,7 +152,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
152 } else {
153 _reset();
154 auth.close(
155 - route: launchUri != null ? Routes.send : null,
155 + route: _isValidPaymentUri() ? Routes.send : null,
156 arguments: PaymentRequest.fromUri(launchUri),
157 );
158 launchUri = null;
@@ -161,7 +161,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
161 },
162 );
163 });
164 - } else if (launchUri != null) {
164 + } else if (_isValidPaymentUri()) {
165 widget.navigatorKey.currentState?.pushNamed(
166 Routes.send,
167 arguments: PaymentRequest.fromUri(launchUri),
@@ -183,4 +183,6 @@ class RootState extends State<Root> with WidgetsBindingObserver {
183 _isInactive = value;
184 _isInactiveController.add(value);
185 }
186 +
187 + bool _isValidPaymentUri() => launchUri?.path.isNotEmpty ?? false;
188 }
lib/src/screens/send/send_page.dart
+1 -1
@@ -100,7 +100,7 @@ class SendPage extends BasePage {
100 AppBarStyle get appBarStyle => AppBarStyle.transparent;
101
102 double _sendCardHeight(BuildContext context) {
103 - final double initialHeight = sendViewModel.hasCoinControl ? 490 : 465;
103 + final double initialHeight = sendViewModel.hasCoinControl ? 500 : 465;
104
105 if (!ResponsiveLayoutUtil.instance.isMobile) {
106 return initialHeight - 66;
lib/src/screens/settings/manage_nodes_page.dart
+2 -3
@@ -34,13 +34,12 @@ class ManageNodesPage extends BasePage {
34 SizedBox(height: 20),
35 Observer(
36 builder: (BuildContext context) {
37 + int itemsCount = nodeListViewModel.nodes.length;
38 return Flexible(
39 child: SectionStandardList(
40 sectionCount: 1,
41 dividerPadding: EdgeInsets.symmetric(horizontal: 24),
41 - itemCounter: (int sectionIndex) {
42 - return nodeListViewModel.nodes.length;
43 - },
42 + itemCounter: (int sectionIndex) => itemsCount,
43 itemBuilder: (_, index) {
44 return Observer(
45 builder: (context) {
lib/utils/exception_handler.dart
+12
@@ -32,6 +32,14 @@ class ExceptionHandler {
32 const String separator = '''\n\n==========================================================
33 ==========================================================\n\n''';
34
35 + /// don't save existing errors
36 + if (file.existsSync()) {
37 + final String fileContent = await file.readAsString();
38 + if (fileContent.contains("${exception.values.first}")) {
39 + return;
40 + }
41 + }
42 +
43 file.writeAsStringSync(
44 "$exception $separator",
45 mode: FileMode.append,
@@ -83,6 +91,10 @@ class ExceptionHandler {
91 library: errorDetails.library,
92 );
93
94 + if (errorDetails.silent) {
95 + return;
96 + }
97 +
98 final sharedPrefs = await SharedPreferences.getInstance();
99
100 final lastPopupDate =
lib/view_model/anon_invoice_page_view_model.dart
+5 -1
@@ -93,7 +93,11 @@ abstract class AnonInvoicePageViewModelBase with Store {
93 Future<void> createInvoice() async {
94 state = IsExecutingState();
95 if (amount.isNotEmpty) {
96 - final amountInCrypto = double.parse(amount);
96 + final amountInCrypto = double.tryParse(amount);
97 + if (amountInCrypto == null) {
98 + state = FailureState('Amount is invalid');
99 + return;
100 + }
101 if (minimum != null && amountInCrypto < minimum!) {
102 state = FailureState('Amount is too small');
103 return;
lib/view_model/exchange/exchange_view_model.dart
+4 -1
@@ -225,7 +225,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
225
226 @computed
227 List<WalletContact> get walletContactsToShow => contactListViewModel.walletContacts
228 - .where((element) => receiveCurrency == null || element.type == receiveCurrency)
228 + .where((element) => element.type == receiveCurrency)
229 .toList();
230
231 @action
@@ -550,6 +550,9 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
550 amount = amount.replaceAll(',', '.');
551
552 if (limitsState is LimitsLoadedSuccessfully) {
553 + if (double.tryParse(amount) == null) {
554 + continue;
555 + }
556 if (limits.max != null && double.parse(amount) < limits.min!) {
557 continue;
558 } else if (limits.max != null && double.parse(amount) > limits.max!) {
pubspec_base.yaml
+1 -1
@@ -49,7 +49,7 @@ dependencies:
49 lottie: ^1.3.0
50 animate_do: ^2.1.0
51 cupertino_icons: ^1.0.5
52 - encrypt: ^5.0.1
52 + encrypt: 5.0.1
53 crypto: ^3.0.2
54 # password: ^1.0.0
55 basic_utils: ^5.6.1
scripts/android/app_env.sh
+7 -1
@@ -5,6 +5,7 @@ APP_ANDROID_VERSION=""
5 APP_ANDROID_BUILD_VERSION=""
6 APP_ANDROID_ID=""
7 APP_ANDROID_PACKAGE=""
8 +APP_ANDROID_SCHEME=""
9
10 MONERO_COM="monero.com"
11 CAKEWALLET="cakewallet"
@@ -18,12 +19,14 @@ MONERO_COM_VERSION="1.6.0"
19 MONERO_COM_BUILD_NUMBER=56
20 MONERO_COM_BUNDLE_ID="com.monero.app"
21 MONERO_COM_PACKAGE="com.monero.app"
22 +MONERO_COM_SCHEME="monero.com"
23
24 CAKEWALLET_NAME="Cake Wallet"
25 CAKEWALLET_VERSION="4.9.0"
26 CAKEWALLET_BUILD_NUMBER=169
27 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
28 CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
29 +CAKEWALLET_SCHEME="cakewallet"
30
31 HAVEN_NAME="Haven"
32 HAVEN_VERSION="1.0.0"
@@ -44,6 +47,7 @@ case $APP_ANDROID_TYPE in
47 APP_ANDROID_BUILD_NUMBER=$MONERO_COM_BUILD_NUMBER
48 APP_ANDROID_BUNDLE_ID=$MONERO_COM_BUNDLE_ID
49 APP_ANDROID_PACKAGE=$MONERO_COM_PACKAGE
50 + APP_ANDROID_SCHEME=$MONERO_COM_SCHEME
51 ;;
52 $CAKEWALLET)
53 APP_ANDROID_NAME=$CAKEWALLET_NAME
@@ -51,6 +55,7 @@ case $APP_ANDROID_TYPE in
55 APP_ANDROID_BUILD_NUMBER=$CAKEWALLET_BUILD_NUMBER
56 APP_ANDROID_BUNDLE_ID=$CAKEWALLET_BUNDLE_ID
57 APP_ANDROID_PACKAGE=$CAKEWALLET_PACKAGE
58 + APP_ANDROID_SCHEME=$CAKEWALLET_SCHEME
59 ;;
60 $HAVEN)
61 APP_ANDROID_NAME=$HAVEN_NAME
@@ -66,4 +71,5 @@ export APP_ANDROID_NAME
71 export APP_ANDROID_VERSION
72 export APP_ANDROID_BUILD_NUMBER
73 export APP_ANDROID_BUNDLE_ID
69 -export APP_ANDROID_PACKAGE
\ No newline at end of file
74 +export APP_ANDROID_PACKAGE
75 +export APP_ANDROID_SCHEME
\ No newline at end of file
scripts/android/inject_app_details.sh
+1
@@ -8,6 +8,7 @@ fi
8 cd ../..
9 sed -i "0,/version:/{s/version:.*/version: ${APP_ANDROID_VERSION}+${APP_ANDROID_BUILD_NUMBER}/}" ./pubspec.yaml
10 sed -i "0,/version:/{s/__APP_PACKAGE__/${APP_ANDROID_PACKAGE}/}" ./android/app/src/main/AndroidManifest.xml
11 +sed -i "0,/__APP_SCHEME__/s/__APP_SCHEME__/${APP_ANDROID_SCHEME}/" ./android/app/src/main/AndroidManifest.xml
12 sed -i "0,/version:/{s/__versionCode__/${APP_ANDROID_BUILD_NUMBER}/}" ./android/app/src/main/AndroidManifest.xml
13 sed -i "0,/version:/{s/__versionName__/${APP_ANDROID_VERSION}/}" ./android/app/src/main/AndroidManifest.xml
14 cd scripts/android
scripts/ios/app_config.sh
+5
@@ -16,6 +16,11 @@ cp -rf ./ios/Runner/InfoBase.plist ./ios/Runner/Info.plist
16 /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier ${APP_IOS_BUNDLE_ID}" ./ios/Runner/Info.plist
17 /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${APP_IOS_VERSION}" ./ios/Runner/Info.plist
18 /usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${APP_IOS_BUILD_NUMBER}" ./ios/Runner/Info.plist
19 +
20 +/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:1:CFBundleURLName string ${APP_IOS_TYPE}" ./ios/Runner/Info.plist
21 +/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:1:CFBundleURLSchemes array" ./ios/Runner/Info.plist
22 +/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:1:CFBundleURLSchemes: string ${APP_IOS_TYPE}" ./ios/Runner/Info.plist
23 +
24 CONFIG_ARGS=""
25
26 case $APP_IOS_TYPE in