From d1c45a5326317b0b78675d34f4d827a88a5bba5a Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Sun, 5 Jan 2025 03:11:44 +0200 Subject: [PATCH 01/16] 4.22.1 RC (#1932) * 4.22.1 RC * minor cleanup [skip ci] * Fix frozen balance not displaying at startup issue * Monero balance tx display issue (#1934) * minor cleanup [skip ci] * Fix frozen balance not displaying at startup issue * fix transactions not updating (stupid mobx reactions :3) * [skip ci] --- assets/text/Monerocom_Release_Notes.txt | 5 ++- assets/text/Release_Notes.txt | 9 +++-- cw_monero/lib/monero_wallet.dart | 34 ++++--------------- ios/Podfile.lock | 2 +- .../dashboard/dashboard_view_model.dart | 6 ++-- scripts/android/app_env.sh | 8 ++--- scripts/ios/app_env.sh | 8 ++--- scripts/linux/app_env.sh | 4 +-- scripts/macos/app_env.sh | 8 ++--- scripts/windows/build_exe_installer.iss | 2 +- 10 files changed, 32 insertions(+), 54 deletions(-) diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt index 3a6706a26..09092a8df 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1,3 +1,2 @@ -Support Monero Ledger -Bug fixes -New designs and better user experience \ No newline at end of file +UI enhancements +Bug fixes \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index f7d5e4d2c..8abed72a2 100644 --- a/assets/text/Release_Notes.txt +++ b/assets/text/Release_Notes.txt @@ -1,5 +1,4 @@ -Support Monero Ledger -Prepare for Haven removal -Improve Ethereum and Polygon sending process -Bug fixes -New designs and better user experience \ No newline at end of file +Bitcoin and Litecoin enhancements +Solana and Nano fixes/improvements +UI enhancements +Bug fixes \ No newline at end of file diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 54c36a828..b46e8dd10 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -7,7 +7,6 @@ import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/account.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/monero_amount_format.dart'; import 'package:cw_core/monero_balance.dart'; import 'package:cw_core/monero_transaction_priority.dart'; import 'package:cw_core/monero_wallet_keys.dart'; @@ -28,7 +27,6 @@ import 'package:cw_monero/api/transaction_history.dart' as transaction_history; import 'package:cw_monero/api/wallet.dart' as monero_wallet; import 'package:cw_monero/api/wallet_manager.dart'; import 'package:cw_monero/exceptions/monero_transaction_creation_exception.dart'; -import 'package:cw_monero/exceptions/monero_transaction_no_inputs_exception.dart'; import 'package:cw_monero/ledger.dart'; import 'package:cw_monero/monero_transaction_creation_credentials.dart'; import 'package:cw_monero/monero_transaction_history.dart'; @@ -58,8 +56,9 @@ abstract class MoneroWalletBase extends WalletBase.of({ CryptoCurrency.xmr: MoneroBalance( - fullBalance: monero_wallet.getFullBalance(accountIndex: 0), - unlockedBalance: monero_wallet.getFullBalance(accountIndex: 0)) + fullBalance: monero_wallet.getFullBalance(accountIndex: 0), + unlockedBalance: monero_wallet.getUnlockedBalance(accountIndex: 0), + ) }), _isTransactionUpdating = false, _hasSyncAfterStartup = false, @@ -281,7 +280,6 @@ abstract class MoneroWalletBase extends WalletBase 1; final unlockedBalance = monero_wallet.getUnlockedBalance( accountIndex: walletAddresses.account!.id); - var allInputsAmount = 0; PendingTransactionDescription pendingTransactionDescription; @@ -295,11 +293,9 @@ abstract class MoneroWalletBase extends WalletBase acc + (value.formattedCryptoAmount ?? 0)); - final estimatedFee = - calculateEstimatedFee(_credentials.priority, totalAmount); if (unlockedBalance < totalAmount) { throw MoneroTransactionCreationException( 'You do not have enough XMR to send this amount.'); @@ -342,8 +336,6 @@ abstract class MoneroWalletBase extends WalletBase _askForUpdateTransactionHistory() async => await updateTransactions(); - int _getFullBalance() => - monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id); - int _getUnlockedBalance() => monero_wallet.getUnlockedBalance( accountIndex: walletAddresses.account!.id); int _getFrozenBalance() { var frozenBalance = 0; - unspentCoinsInfo.values.forEach((info) { - unspentCoins.forEach((element) { - if (element.hash == info.hash && - element.vout == info.vout && - info.isFrozen && - element.value == info.value && info.walletId == id && - info.accountIndex == walletAddresses.account!.id) { - if (element.isFrozen && !element.isSending) frozenBalance+= element.value; - } - }); - }); + for (var coin in unspentCoinsInfo.values.where((element) => + element.walletId == id && element.accountIndex == walletAddresses.account!.id)) { + if (coin.isFrozen && !coin.isSending) frozenBalance += coin.value; + } return frozenBalance; } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 8046ba307..9e2a8507a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -272,7 +272,7 @@ SPEC CHECKSUMS: uni_links: d97da20c7701486ba192624d99bffaaffcfc298a universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1 + wakelock_plus: 373cfe59b235a6dd5837d0fb88791d2f13a90d56 workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6 PODFILE CHECKSUM: e448f662d4c41f0c0b1ccbb78afd57dbf895a597 diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 387c66511..4ab171a15 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -641,7 +641,7 @@ abstract class DashboardViewModelBase with Store { transactions.clear(); - transactions = ObservableList.of( + transactions.addAll( wallet.transactionHistory.transactions.values.map( (transaction) => TransactionListItem( transaction: transaction, @@ -703,7 +703,7 @@ abstract class DashboardViewModelBase with Store { monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id) .toList(); - transactions = ObservableList.of( + transactions.addAll( _accountTransactions.map( (transaction) => TransactionListItem( transaction: transaction, @@ -723,7 +723,7 @@ abstract class DashboardViewModelBase with Store { wow.wownero!.getCurrentAccount(wallet).id) .toList(); - transactions = ObservableList.of( + transactions.addAll( _accountTransactions.map( (transaction) => TransactionListItem( transaction: transaction, diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 385414f24..6b1907451 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -15,15 +15,15 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.19.0" -MONERO_COM_BUILD_NUMBER=109 +MONERO_COM_VERSION="1.19.1" +MONERO_COM_BUILD_NUMBER=110 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_SCHEME="monero.com" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.22.0" -CAKEWALLET_BUILD_NUMBER=240 +CAKEWALLET_VERSION="4.22.1" +CAKEWALLET_BUILD_NUMBER=241 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_SCHEME="cakewallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 580adad8e..c1747c502 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.19.0" -MONERO_COM_BUILD_NUMBER=106 +MONERO_COM_VERSION="1.19.1" +MONERO_COM_BUILD_NUMBER=107 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.22.0" -CAKEWALLET_BUILD_NUMBER=287 +CAKEWALLET_VERSION="4.22.1" +CAKEWALLET_BUILD_NUMBER=288 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" diff --git a/scripts/linux/app_env.sh b/scripts/linux/app_env.sh index 6d8557d6c..f0ec8e9e6 100755 --- a/scripts/linux/app_env.sh +++ b/scripts/linux/app_env.sh @@ -14,8 +14,8 @@ if [ -n "$1" ]; then fi CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="1.12.0" -CAKEWALLET_BUILD_NUMBER=41 +CAKEWALLET_VERSION="1.12.1" +CAKEWALLET_BUILD_NUMBER=42 if ! [[ " ${TYPES[*]} " =~ " ${APP_LINUX_TYPE} " ]]; then echo "Wrong app type." diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index 37e7890c4..fe3d806d8 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -16,13 +16,13 @@ if [ -n "$1" ]; then fi MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.9.0" -MONERO_COM_BUILD_NUMBER=39 +MONERO_COM_VERSION="1.9.1" +MONERO_COM_BUILD_NUMBER=40 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="1.15.0" -CAKEWALLET_BUILD_NUMBER=99 +CAKEWALLET_VERSION="1.15.1" +CAKEWALLET_BUILD_NUMBER=100 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then diff --git a/scripts/windows/build_exe_installer.iss b/scripts/windows/build_exe_installer.iss index 155e65005..950800896 100644 --- a/scripts/windows/build_exe_installer.iss +++ b/scripts/windows/build_exe_installer.iss @@ -1,5 +1,5 @@ #define MyAppName "Cake Wallet" -#define MyAppVersion "0.3.0" +#define MyAppVersion "0.3.1" #define MyAppPublisher "Cake Labs LLC" #define MyAppURL "https://cakewallet.com/" #define MyAppExeName "CakeWallet.exe" From 55005f8b39b2b904507253227d1ff1cb7da5d86d Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Wed, 8 Jan 2025 05:09:45 +0200 Subject: [PATCH 02/16] await settings migration --- lib/entities/default_settings_migration.dart | 30 +++++++++++++------- scripts/android/app_env.sh | 2 +- scripts/ios/app_env.sh | 2 +- scripts/linux/app_env.sh | 2 +- scripts/macos/app_env.sh | 2 +- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 25140f106..90e32d982 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -70,8 +70,13 @@ Future defaultSettingsMigration( await sharedPreferences.setBool(PreferencesKey.isNewInstall, isNewInstall); - final currentVersion = + int currentVersion = sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) ?? 0; + // TODO: remove after v4.22.1 is live + /// for beta testers who didn't correctly get the 46 step configured + if (currentVersion == 46) { + currentVersion--; + } if (currentVersion >= version) { return; @@ -334,20 +339,20 @@ Future defaultSettingsMigration( ); break; case 46: - _fixNodesUseSSLFlag(nodes); - updateWalletTypeNodesWithNewNode( + await _fixNodesUseSSLFlag(nodes); + await updateWalletTypeNodesWithNewNode( newNodeUri: 'litecoin.stackwallet.com:20063', nodes: nodes, type: WalletType.litecoin, useSSL: true, ); - updateWalletTypeNodesWithNewNode( + await updateWalletTypeNodesWithNewNode( newNodeUri: 'electrum-ltc.bysh.me:50002', nodes: nodes, type: WalletType.litecoin, useSSL: true, ); - _changeDefaultNode( + await _changeDefaultNode( nodes: nodes, sharedPreferences: sharedPreferences, type: WalletType.solana, @@ -360,13 +365,13 @@ Future defaultSettingsMigration( 'solana-rpc.publicnode.com:443', ], ); - _updateNode( + await _updateNode( nodes: nodes, currentUri: "ethereum.publicnode.com", newUri: "ethereum-rpc.publicnode.com", useSSL: true, ); - _updateNode( + await _updateNode( nodes: nodes, currentUri: "polygon-bor.publicnode.com", newUri: "polygon-bor-rpc.publicnode.com", @@ -387,13 +392,16 @@ Future defaultSettingsMigration( await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version); } -void _updateNode({ +Future _updateNode({ required Box nodes, required String currentUri, String? newUri, bool? useSSL, -}) { +}) async { for (Node node in nodes.values) { + print("@@@@@@@@@@@@@"); + print(node.uriRaw); + print(node.uri); if (node.uriRaw == currentUri) { if (newUri != null) { node.uriRaw = newUri; @@ -401,6 +409,7 @@ void _updateNode({ if (useSSL != null) { node.useSSL = useSSL; } + await node.save(); } } } @@ -481,7 +490,7 @@ void _deselectExchangeProvider(SharedPreferences sharedPreferences, String provi ); } -void _fixNodesUseSSLFlag(Box nodes) { +Future _fixNodesUseSSLFlag(Box nodes) async { for (Node node in nodes.values) { switch (node.uriRaw) { case cakeWalletLitecoinElectrumUri: @@ -490,6 +499,7 @@ void _fixNodesUseSSLFlag(Box nodes) { case newCakeWalletMoneroUri: node.useSSL = true; node.trusted = true; + await node.save(); } } } diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 6b1907451..45e28379d 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -23,7 +23,7 @@ MONERO_COM_SCHEME="monero.com" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.22.1" -CAKEWALLET_BUILD_NUMBER=241 +CAKEWALLET_BUILD_NUMBER=242 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_SCHEME="cakewallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index c1747c502..079b12391 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -19,7 +19,7 @@ MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.22.1" -CAKEWALLET_BUILD_NUMBER=288 +CAKEWALLET_BUILD_NUMBER=289 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" diff --git a/scripts/linux/app_env.sh b/scripts/linux/app_env.sh index f0ec8e9e6..5103eeccc 100755 --- a/scripts/linux/app_env.sh +++ b/scripts/linux/app_env.sh @@ -15,7 +15,7 @@ fi CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="1.12.1" -CAKEWALLET_BUILD_NUMBER=42 +CAKEWALLET_BUILD_NUMBER=43 if ! [[ " ${TYPES[*]} " =~ " ${APP_LINUX_TYPE} " ]]; then echo "Wrong app type." diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index fe3d806d8..af2b6ab1e 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -22,7 +22,7 @@ MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="1.15.1" -CAKEWALLET_BUILD_NUMBER=100 +CAKEWALLET_BUILD_NUMBER=101 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then From eb2827402a1bccf1b938306099770b17e6483496 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Wed, 8 Jan 2025 05:12:57 +0200 Subject: [PATCH 03/16] revert beta testers change --- lib/entities/default_settings_migration.dart | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 90e32d982..abcdda4c0 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -70,13 +70,8 @@ Future defaultSettingsMigration( await sharedPreferences.setBool(PreferencesKey.isNewInstall, isNewInstall); - int currentVersion = + final currentVersion = sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) ?? 0; - // TODO: remove after v4.22.1 is live - /// for beta testers who didn't correctly get the 46 step configured - if (currentVersion == 46) { - currentVersion--; - } if (currentVersion >= version) { return; From b865e5e38bc5c154588147a4057f216899168c03 Mon Sep 17 00:00:00 2001 From: Kainoa Kanter Date: Sat, 11 Jan 2025 06:05:25 -0800 Subject: [PATCH 04/16] Closes #873 (#1928) --- .../mipmap-anydpi-v26/ic_launcher.xml | 1 + .../mipmap-hdpi/ic_launcher_adaptive_mono.png | Bin 0 -> 2359 bytes .../mipmap-mdpi/ic_launcher_adaptive_mono.png | Bin 0 -> 1544 bytes .../mipmap-xhdpi/ic_launcher_adaptive_mono.png | Bin 0 -> 3285 bytes .../mipmap-xxhdpi/ic_launcher_adaptive_mono.png | Bin 0 -> 5258 bytes .../mipmap-xxxhdpi/ic_launcher_adaptive_mono.png | Bin 0 -> 7640 bytes .../mipmap-anydpi-v26/ic_launcher.xml | 1 + .../mipmap-hdpi/ic_launcher_adaptive_mono.png | Bin 0 -> 2316 bytes .../mipmap-mdpi/ic_launcher_adaptive_mono.png | Bin 0 -> 1518 bytes .../mipmap-xhdpi/ic_launcher_adaptive_mono.png | Bin 0 -> 3110 bytes .../mipmap-xxhdpi/ic_launcher_adaptive_mono.png | Bin 0 -> 4880 bytes .../mipmap-xxxhdpi/ic_launcher_adaptive_mono.png | Bin 0 -> 6978 bytes 12 files changed, 2 insertions(+) create mode 100644 assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png create mode 100644 assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_adaptive_mono.png create mode 100644 assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_adaptive_mono.png create mode 100644 assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png create mode 100644 assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_mono.png create mode 100644 assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png create mode 100644 assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher_adaptive_mono.png create mode 100644 assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher_adaptive_mono.png create mode 100644 assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png create mode 100644 assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_mono.png diff --git a/assets/images/cakewallet_android_icon/mipmap-anydpi-v26/ic_launcher.xml b/assets/images/cakewallet_android_icon/mipmap-anydpi-v26/ic_launcher.xml index 00d924171..c8bd4b26c 100644 --- a/assets/images/cakewallet_android_icon/mipmap-anydpi-v26/ic_launcher.xml +++ b/assets/images/cakewallet_android_icon/mipmap-anydpi-v26/ic_launcher.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png b/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png new file mode 100644 index 0000000000000000000000000000000000000000..27f939b416b899a04ebf6312ab9e99a322e3af8a GIT binary patch literal 2359 zcmai$c{J1u8^?bld#>zzB1<7-8)L@OVjmQxi<*hC%M5BpqlA)ViL%ZZGp=n$3q~}^ zQVL^V%hI@1_A=<&#wf1$yyv{<+&|uP&v~Bb`z+^ko^zhRo}}|Gc87!&gaH6J9_cwba~?cJOK;Fcx;#N7t~?tUw70RV6y0Qlw&08jz|$cE>?b~WE0 z96-VCtbx76^qx2Sl>pYk*+yWIhnH7?XTdS15dZ`g9js4Z#D8Lw(c#MO^2}IMQ)egA zkQ8-bNLIC~(ONjIM9S+z%4gBy?A(!Npigx{+fAwYkJUox!!txzx=ge$!Ih?dLhoMi zab;!Y!8Gcp!c=rs4cIqSfnRYaqlOxcS^n|f;%4=2Oh&`)m1PQPcAK5JWHE1kduw}s zX}Nyhd|p@Zp6b7wjs3Xg-NW`qWZOJKi>Rh6Tw1;_{2oSZxaBDKJmcITTqDVYiHjy& zs`xTt;CNhYu&;olF3|DGa@MhklKnH;x9k*)6`gp^o@Kf1=&dp(qC2&k5W*57 z)x6Ah>O3G8#>C%SWRR;`+(QGk`FPe?4$Yl@ESEM1UPU=k*S9|-@4GV)DJkh|D}sDI zVdyJCtd=rjM|B~xRUK_t-^L)hUN?ZL%YbSR{own^AU}GLfMu%rU~VVe2!$zG!p)?M z?+drNP{$*oS1^I90YcJeU(~Q82@|N(??ek;?Pt1ku~l#gL04%0uZn|0Z@gJ-5V40; z1;^*~w|)1kDSs-}ACBuBvA{+UVx)qph9tAb{=|>s*YK8Zm8bxdafuK&St~y?=MVU_ z>>`SciZ_dJbIfVt6)1!{EXhGKlG0_^0qD;$ySqrd8t+ft$8qxD>rYpFX&&vAd+O1X zjBcOmV*{l>HAN1@zUNaZ8Qp+XT@oo)D03WkN$G!i_C=sxVCshx0TCM2kX+;V^?^>6 zG*sFeqR-YVPV}nN$TXifape|14|a$Zg83|&8%K;@hoa)bx*)BM9ofCkrH0 z0+X08AKV_ZqPB^{>c-Xm^1kmM=%DF#)~ZyBMPy1exVJiV{uhIsnx|)}XxxneOX770 z>bf14E6N7q9~0Tt{d2Cl&K77MR+eyEZ3YaxN7v_x1hrHVTEpOv&lirJ%0}NTR&AS# zhD`O(rrDV2=V}+8`GI4J*|pd-qZBCDJzwaZJ=1nxg1Nc;;|sp>+In&65_08c1~}({ z;+?vt$>U^dlar8i@_Z;v`Q-84+35rM-?Oe34!^vmqYLX$jaQ$k(=#+?MVdSHZMlYh zB5xwHV+7eHNwx~XehsdX%EN?qV~W9$>7cdbqbpQ_BNFbixg7ByM``Pc>nqbdS}}9T zVihEHxYFd?SK;X$E&;{bSgq?%eE7Zz_C3F52iKKA!6Y`hbV`vc)_D}LTz;)yl)1;4 z^T)4mg3i(V$KMQ&caOfn?0!A5Q&deGTXNh})&k0Wh?$#J53?2G={EW9HLWq+4J7aH z(5E6~kz1taOFG3%^WD1M)XYe9mSJ1>cN_h=M=s;rly&{Z4*^>K1+@6WuB4D2kR9z; zhktArGCy?8q^db6=_c7Cth{$>cysNyrY(v3n5FzIPvp>ISh!rASI+Attq7#(tw;7_ zYBERUmvNg4=8J3ff^oVmoFlTPVPh(HV+!pxp<(M3in7*?X-uu2KcrL^p>pyK|7X2T zAlx8*F}Am0k?#-Z+?}k586gj~(8DSH5ehjKQyuk)t97xZ0ztz}ueQ)%Gre@_g%4jx zBA)xiLN$AZ1uwT6*}z4ocGKm}M6kS13(#*hRZW}CugrsaFU#?Lwk>8Ig`YFeu<1W? zz?`hF*Rhk_=h?}F@FjH?K`KfsOGzXgu|SSt_lTYu(W9T744EKQ%$7Z=ghu`~X zh((-|>5`ac@T4Ps*P8S6NiGgoNrfzQjeq0jNFQWI4=|>EBx(X1h}E{sqxp>qly#m_ z{fq)d@ShGO&U9evY(vFg0R{bN@IZmA^a_cV#81DyHAL?)2#NR?J>o~L{ zF)!Jn7qcX!`JeOlpJ6)&ga=aGzDj;M5h?IET*N4}L0)fH-T$&hq)9Y!k4yjl*`=QY zKamuXd>X~DLXA+gs z#5@gN6Yo8Ev|TcaC*kYIl3yWrYbqbOV7gb?uKaawPCihh`nb;G*1)S@xRlY%+6i~M zyn6#&E_$o4%uPE|L*LDSGtpe%oK8kXwM%lGf3BKomO;1i){M)TXzukn zm}welD$4R2zI-8B0@0H)+8ab0F5y&3z7fw?5gWF#@_a_@QVNiFCJk=?RvT5+-EIaR zi94_D^7`GTMBJl~_EO0gI`i^;d=W|ssJMpE1{F>4r64zLLt9xUWFD+zxa{CHy(3vw z=`~6*7UHQ$$Ng$$%;4;2>W(q8%}2*Ywo@tik*uayKPAhtMaVz#0oyc)%EJ&c8*_;j zsfVl(yVJJC%;ajzlU64!=qDF_V||U%S8_LGQtz09=5{Vm8hxmKRtWQ57!Bf zqChJGpQM}e;ucJKn-Ist_@5m+yjDVN(J(g1Tzg7Olal9}bsl~Vu@-2$I0k#hcUt`4 z%pSTYpk3QFa{OI}+WzODfwl3#`uSk}p}yDs_XRKn8Jg&U4D>)o7eR(lBV(umSO)}x zfGkL5Kf?F@l0jp&*0*E(wr1#o1>7 N2OAgbiZkB#{s!nGQz8HW literal 0 HcmV?d00001 diff --git a/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_adaptive_mono.png b/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_adaptive_mono.png new file mode 100644 index 0000000000000000000000000000000000000000..7bdb298c127e13a9b405704250496ca40f8c2eeb GIT binary patch literal 1544 zcmZ{kdpHw%7{`Az5$5vL!`yReJ5HK5*R0%TVhqDLxyHr_i?Kw~Rx0<1XGJI{HZbDqxgoIl?8`@HY_eBSRr@AIaSTX9C`a96vhCiz0e@f)P=G$-kU2j6cw1?7ad>_0aW<79?u zwLZI~%~QOVz1iQC)bL(&F8SDelHZ;p{e8_}Ct~`{zt67k{a(CTIuK^8nsuQ3U*Y}k z9DJT-l}ypBII~Z0Czpt9AX+_SG74m>rvJFmgSAAQn@~W0e3P{6DapTogyaZ?YvkwJ z%17ngX>9pOYI8yU#<|;u7^juZoNlUN^D~9+UDEI4<9eIpOE|H@N{-^AtJZ>cVsYq- zv;YQksr3`27H#zMYUA zkmq%3d%L-vS##67C^;v(f2Wk|RZGi?b1$ciF72K+f23up8|rbeu5{eCst9|V5pP<8 zDwLNsFk(&~)|6g!~A*Vt1wM7i($<-9G6h==7=fHVn;RG)X8-o>(U>k-g8Z zxQBS$*g*G|2gB?x2YE#uTG2ByeiM!k@uv2D5a_yJNuMYm`Ls>Ug#;c#XJW& zwyr!Mn#XqD(QJE-%g|W4b*uz~(yr_fF2!>48B@_Zkf{xuA5!AG$p_>#+5}Q7*$U4y=?>5T28YZ&ZqaJ>w}B75ep%G0 zOjU=x!GJ6KWp}Q)RG6lJGMj8vgI?dF)izVwvCh~TlB5B@@r-m+k*zvFeWm9P&)_aH zw2}=j+_Mqn5@wz{UUsn9y>=a^m0|&M1gs?5N4YJqf<7qP!8%@m6lp6-1MlLV@C#!Ny%x z>4=rOuKJe2c5Bhr1y%iLBa`(YryIdwxldj4vDn~Bi*7HVd~K`MuLZVXKegKff;ZQn zw{qiCD?4n2yllm<+*AVEVa#;NL>X~pt+OykMyjkOQR0tODu48G!o?~t0?kb|LQ_7r z*Y;*clV^soou1z{>KZ^Sf7{i$b4P4)cpABZudi91WS0M#7U{F9P}02t>v}=+wU$lQ zAL$*p6;OSCavea;f4NbSsdHDkj2m|{m~@RlZS-Y@mzB8%^`2{(Em-5yYT}}*xqibq zkD)|kx!ED?T>X^uOFAY@#`ST@_{Iy#p=!J(buN3>vBrwFAd^AX_ZA+pukEJV50)nD z3_B{d&e^CHUcn1;JNBJQS6Pa^6OPC$?Mui^2`#4-7Yi{`GrNbShjomkCYOA}cx%D$ zJ|@`ZB_y;BQR+iaLl2enoP-`@N#}8SOn0kel?|JBV!wBc;SAiKqz$^tzfy1X>3bFN zpAVzj=H6}zPzz}U{fv?#45CN>z!+iCW>_@lZxfo5^@@lA N1Unb}ZCv2#KLH{Bp9lZ| literal 0 HcmV?d00001 diff --git a/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_adaptive_mono.png b/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_adaptive_mono.png new file mode 100644 index 0000000000000000000000000000000000000000..79e9df08d83daea66bec94b669062a82b6603e4f GIT binary patch literal 3285 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 B0Pp|+ literal 0 HcmV?d00001 diff --git a/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png b/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png new file mode 100644 index 0000000000000000000000000000000000000000..0bb1c74308757dc2319eb2a63bbcc81cab2fd95c GIT binary patch literal 5258 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 literal 0 HcmV?d00001 diff --git a/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_mono.png b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_mono.png new file mode 100644 index 0000000000000000000000000000000000000000..205f2ad2a5ab1a7a704a5b3bbab36249aeb2be4f GIT binary patch literal 7640 zcmdUU^-~-{xAp=F1b4UKu8V6D2of|9T!JkN1Yg`kg0nyt!XgO-C+OlVu8X@%LSS)+ z;Fin#eO0&W{&4?*+f`HDb7r1CPoL>i)AgJfU2RoDJUTo806?g&rmP15U?Bc;J$v$K zu~EIsdF-Css_AI~00Eo;K-gyh;O5a3whaJy3j+YVRseuZ8UR4;{;lb)+~WgG8x2)u zz`v-(;r7Qn94~dP*El;ESXek1+iyZz004#-b>)}u{1y*>`G2F>_}+J-bIBmeb{R|@ z9Y*lXk9|T}_nmT{U+&xYBFzhPACk+ORq|A9HLE!@e>sf)f=<{;#vsK8N}|^&p`^V5plq=EPtxI)gP^xH~+xgd+7jb+JXikrIHoTX|Tu%h)(Rva7?`)ezYS z&bIVs3rYf+3!1G5%+LGu!WN8ro@v_Pw9@T3@^paD6AIdGI=yNwA)iQtIdzbs__m=eZ?`+GE=Pz zI4m$Mxf0UOi#kx^AXAQSGw*?jnGv%n{){L>rYQtudVO~=hA)`;0qV)1>-Y`YKaA(wxx{3+c^nCr4 z7?J(?9MNz029b?JV}CpTCTDL*Yg&cBF#&r)!Zinepu`_h5b*MPSJ z?i?o)6II;61Xxgux}ro=+QH=IugR9m-d`_@!Rz$%IH?Bx@a)nKCB3OcxYy+=sg2Im*u_uS9WY&z(o$hc54E6R0efFq zwRi){)N$k}npXh9glx?FP50o=Z3T71h~PDL#czF&L_a7L5(en*YnSIWH+_CDJn6%E zs>{}1IP1g7-q0(R4V^uDa-BwpG(uy=!8NA8N%kH$C8M3Q=*+yRAKD(xJ5>Rh#dfHU;YzN!)suVZK;a4U?L2GuP&Hjd0PWBEf75SB_N%rjeZ; zf{fyVNO@;FVCMV?#$#GximdhQ3_Rzh(RzwS(|KGbS6g>{w5*zUpp>i=>jLh8tp>4i zb(el5p4XzR)Em+E(LK6`(rggeKeJ8iw8IvWsFl@}zx*p70r%q@K~Ch7IfF%fg+IM? zysu^%UN7ah0i*bsGp;jYC3>=lq^;!hiHang{9?x-p4gegR6yw13A>r&(0ub;!}N;A z9qq`QJ?ojQU6WC2TI$J6_xO|L;S~op*=&Jj3xp6knXbq`TX_VG{U@5(92U|1iWdG? z*xPZvHvOnrDfUX73x|pL@-MoKh=kl$?%(&dBiGfadY$z3M1>Y7=|-YsAaD8Ig~wgE z(#Ipq6{BR!lcP+-%nOOXfhe>4ds{!b7q7kfRvLo4*t0dQ6MK(Md$^pL0f14vFO_!^ zrv|@T*`wc^;8zeMd7@&GLyZeH^LY_&d@bzJahUBN?Re#T*BP3P(VDBcaE3WZL&n(~ z5{?!SN8^RZ2W^SL@T{Ou_b53@NFv@QFwdRt)^dc6idVzv7-U%}P;f*g67Laj^(2pW z&?-QG)`Qu3^?BfYCsjS-JMO3>2_Z#`A}I zuo|7Lv<#>a`K1QbBR=tFt=o_0`r-xBPl5WgsSby78eR{Ohr-X-t_|B%SHh6{y(U4> zb_7p>#Ah7i0VVUH`LLtF;44xs#wCY8ObHFa)$A*0{Ws%)KqPP+%aJD;nJ!s8*3nja z&A0{R?m&l%GrCWSFwZev)zex{+RaGG4J$Q$9@ki!8$Sk@;-GQ7Z zzH*gd@s@f|gRqtndu!~)=FF*pLz~(o9Mk}>bjrKoAt%2X_pGs?ukILQ%>K8yd@g_X z2&#hd*qa9Z6yjFvz7GX$@S@b1h8KUxmZq2b^5OPP^CvLYWoiU({7j)=$m&JtOrjv~ zD0ItniX4>3Z@^2rIgOCX{-Xb*8SjR_%{2KE3m1hP%b{71xnj9_)M( z!QU#Z&w~x{@S@?(Nxpj8(TbF+{ zhzxvA1n-2r9r7A$(OiRmOXqe}Y$$$XGD*2rWP&uO529ew981qxThwP;nH0{2WlaPn zeT_R08KWZM+A{X5`)6|1bZ7%hpszkvtB=fKx=zdZv^o<)-E0QUV>jTC`yLT@Mp7e> zN5F~b3gDJr={=l`ZiQu#FQi0hoP{JdRATVgGpV@#%+gs>cnixG9DDWU$~CfiGbdhg zUAeQxY!Su3RP^RlC;oj@h-1A+vMSSIwPpD}mjQ6{Vh0ukOu2m3E^HK7tE(u)cAwm> zgUh7dswJ0f;Q^tQXZN{xnI8o7JgCf0z^n&DyUs$e z%R6XWR)EtP4b>mKJr;5?feG8(D{+Z4)*Dd8x$1q*bR>Jtj)XYIXp4hSN2#50@siFl zNqNEt^m0FLJ@HFC*Qt5Nbm|d+EZaKg3;HWXc%A+g>;NkXX1$!_!@R^y02reN>_vQ9A`5oHO%icpP`gEZ=NSPa0{glDumr3p8VmGY!CM6F zCp4XBWR*)}g;rw35+1-LOZcE;V+rS=zh($je+7?gj^3i#hGgTm^^!BLI8$S}t`n^w zEm73l3|0qb(oxJ_rjzioQ%d*>SJ&7pf#KgO1?VjF^4!5+0Zqu&nNZMC4NA)UMl)*X zZ&T3|Upk0xkJ2xNW)>lh7Z5{=mCnM*rt|iyS&v`3n?7cS`%cW+K&qntR)u=C^XSFkL09ptc)^mp9If*Qt6+`#nKwu=Q68WmL^ z5QW&OK&`g!YVs0j2#rkexc${T!`%O@%ayb=u$Qp678J?aeX7sEM0Qy-Jf~3VKVm%s z4BGyHA1aFNP}SY)M8^f%b&tX1PjmY1Yc3g?NDHU_%M%sVL)}avlh&#P?augGc5-rW zpGpHy{3C`wMN2E{M``3zTjjkHP+HbZMmJ5-q7j<}QKwysw*hKFgC1JsLf2Ohf)6(4 z3eYN|l{ETdyqD(6LLI!5E1%;Oo6BV9JhTdw29}*90uDWz_dc!1Dd4@a6p8eR|G?NT z5~IGu$SKVqfMJ;az{xOE)8%17tgzNtk+K&*lq|Q89FsI3!dqEoOgw7vn*QPK%?g$d zDXO%c34cP-3c#;HTh*bLNHjfNeu&3)qBi2@(N$xLO(~{b3R#y|4?aiF!C z2@)m|+tqGMTXmbRoam7HN+NlA3afOIIsiUIZ<-1&LHU5`OaZ2cw>vrr7`r*I%Z3LM zZt-{KMbb4np|Nbf?FD^5M^E$dlRrAKo()GnO>0-3VlsC$w*>k11P&PY2GuF~_HRTc z@D0UN0=_B)22n>X^5s}PhPCTX-bL_0>W=E!H#dfpk_la+tN7KAwV&~&M(52wFpqhg zNvzB-?FVJ&NuUh+zu>;bM+kESc_(^Fro_b$z27F&)*6-!a-yNegwvIoPFw^LV*9LFG4M`w{J6{Ar3 z!fE&PD(lk$jGI8orrH*YmB&aEK7P)(NDVIKf4;(Lpl_{Z7ioiQV8SEC*7V@@6^@`u zM1%^H;^`4Q{xX{n^IeZ5PlxCu(anDr+>Hu?br#In++^F`pvVFj)h;&2GMlV%$Kgcj z$fzFnP?_rT2$cMLx*tzG67i(}gPKkGnCrzAZ2s`zMtpp!;2MPgX=5MT>g(!>;OP1H z-2!`&M|Zu9m*wIp-c6B@_Ww?SYq@^0reOyVMpjPw$Hfh7(KH{7O4Os~J4F}cWSjnd zp_wkoaTgK zMo>GB&BS2&v3d6^57Up=k%~Z%3jE{8VBQQfHol^&1C-)d{ZQBxO?Vx?fl*Jf<%I5S z3o}fm$KFa1X;|9Q5#QW=eIY|&AT#Sr3G5KG6|Z~p9}r<3=e`)+yYflEZF9#wt2=(B zZwaxiGa`pFJP?P!QMvKtSPzq9iwe-Uq;;}1eYH{jEgpj1T9ViS_@CmS<(R(oruqpUG_Tv1 z=Q-`#SAUbEBVYJ0f%i*zc%m)C%HM>NCk#sUf({^U4OrvJI@fpWvT(TzQq3?|Uwr&Y(MwUR&Fa{-pqtp+ z@uw5=`>kmjMio}2-0W{nk1zlMG6Z#KyRSoHNHX0s%(9G6EUv*M%)Tbg{8spFadN7i zylYbxTMpG*`Q*Z#eJ>XJZWQbC#=dPoHD;@0ah11cAVnh>F_P}X6g9U|x4P{o z?ypPDWz^v_6lxzi*FQS&xo0%}n0BzGnp3}#q85D;5Pd+J%zjNgcW+e9)lvVjJ`b&l z2DR0XcbzBrr>jH4T9&ET_MOOB{xNxm1zRxyZV{|)Ojc%rjIyUXpoTFQxgA{?4VnwX zv-&G(y^i^r@(C%aCGqu3aD4aphl9l{kFb(Og@l42U~mE}F|&umXSsM2jF6d2=%?No zFJLN^Wu-gSe_Fsowd=Cp5|}Vfy8f~u;sX@fDEvb3_qa3f+DdK`R#{A``Q`hbu^_k7 ziFujzcNb6l$IvMAyXDJEdUFjR2k7nh+|7-!gQIZXjZ(P-(FAw;28bZn&El*_Q2Of^ z@iYf{dwtO!+COMH!4S!)Xzw6dbuw;ICm=IkeuEUrP1# z1!;fHXu{aH`MJlHDiV?G8PwG*8_=l0i z;LVy@?f&uikx8;2=8-WmThVlf5$i1h8w=mYPT4Ysec*2lZ4mNYS2Su)^4%rJ&s)vd z0*}r~I(T7yl-8ET-s-e`%b|gvx(sje=wLxWd!mVk_NKrd%ZzkOTj4S6tOu*MP>oC6 zvmjC8d$Msxgbj7*4xk}00h@JghuowjY7yndjHDgxmVPT_I5fk%n54OVRHJHev*Ukr z-9L7+teQOY=M7>~RmhPvyX|Y8#${0(Cru(mdZ{eJh`#+qdcHndi0kUGHZ7!S34GM_ zMb436P`Yb=;Osk{-+ooMOv=A|Vuqk!q*$!$Wwm zF%4Wy%pP&{tSmQ7nvr#8zjcvJKXTlG}+!~Aqt>zO)FWq~G$ zN%7B9!RoUQRkc#c5)D?p-`4T(ahW-t>)dstCLA;-`q zf?fRqB3fv3hW`8y4w^XGzFRJ%c&EzqOQd1jme z6Mf8)<*y4Vf5uQE+cihS9c-7Osz)|!WAY|K6^at#f1}4vcDqF*uXzZ~kJ}YcjHEfl zl_ms_^oWiDTqf5C=xflRTMllPp)d7`t+q-ATLJvy?|YN)g4`&-2jIcVeaaCoMnM=Q z*s+YKvNkgcDTV)(dBc40D45!@u|R@K1a+~KyeygVb+)@ye&(-)q=;btg_sBm0(6^7 z>SSmyAt85zZ*|zNP29dB#J^H4v_s@qe0B^&fGJDNuO~ zvKQutu29+vgFUF@=4ovlcg!?vf-(Q9=7?P!I7uaLs+GS369&CtEH!>5-aN04WjW$v zn*xkxiQjPM6iFfvB4mbV@jk@#))O0qa8Cn1Nz0|?xjb1C*BdgtK*T_ut*LQj3L_$D zJYu&8o~r;O@os?MR!x}j0_7!7!DD~mn1;gwCJb)8z{D>jViU>`?RbxNpnK&(_`61z z0`fAuSC*}aKJHyLq`Niqn6pH+im!-o1ijUGm)_`{?j#SNt)Q%N1qnpI9|QZ=cyva3 zdQg6a0)ktICn6caXodLdw7Jp4W)u?Zfg5O|(&eM#X^?|M)1nH4ID=U$E!0RgC~WNU z5bPSocGU;Y>vgmQe?X=#U^Z%=j^_h$!#4Sk#wwa3U`G46oh%!y6uYJ?U_>vZ=ur|z z989GJdf|NVjh2))vcQ`n0b}a>r(@LvVUL=7l*^X)s6M<)tVil&;wfIB3z!|w-H}kT zfi?Q5NB?I|$}`Z%#{n5(zjqn6G=l zffj3Tia$YbG^9HpRgt?XI5mY$7Efo*Sxs;WyW-caG+U?D zYy(&Bofr|b-V4;30%ThErKmNgh8Oc5ELHgTh2sED3C`cVgoqSTEjN0`-D?&}eOp<2 zG7gTQSh~GSsJR+F(sJrVeX}=h6kTiF-r-VcPbI4Ll%6cra(O`FQ7A8eH*~N&%C;H{ zkr~@u__36(RxUTd699%gZ}*lz^~T)+ELlwrOF2eJEj zfUzC#%-`qaF3fsgMtQH_6B!dH{HKo4sa{W95Uu|0?Q$Q}-b?G!NGJylF}T;Y;(4eESJr7AaClQfIKs>Zdr$mYHwhaIMgt`8qKLoWroOr9=E>N zPNiYv8iBpcoTugJh+=(&(G_9od=8SQJ0CC<$%+zA=KGo76#!Woc1VmEZG&MwI16QZ3=+>2h~eR}$+>MbxBssNF<_V8iyU`9AnT z8~ow-^`SvIZ^IV!wK>Ss2X+W@LQ(j1bo1TXzin_0$~KY?n9YhGYeJMFu`%)1tmL#> z%!52zjUD28xEaMoS)ZJ(7!91Q3C+$@L7ABRb7wYUY_wg^RfIEM666#2_Qa6Ode+0( z)suce@AvI-%;vjXeRR83ewBL0Ve&Imyw#@VpdiN~tDV$jJ8x2qiT*#+)~o;FY+;hD z4V_mPJeD>&yj~l7*;#vmWNbY_j~zf%SX5jo_ + \ No newline at end of file diff --git a/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png b/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png new file mode 100644 index 0000000000000000000000000000000000000000..af8126ea9fd3293c46c1c46dd153d14a251b55e9 GIT binary patch literal 2316 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 literal 0 HcmV?d00001 diff --git a/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png b/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png new file mode 100644 index 0000000000000000000000000000000000000000..b1165abe85ed60d39f44a52b3bccb09a7c8f50ea GIT binary patch literal 4880 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 X|Jw=OGV%JI3_wFgN4ZYXI{g0tJY&B* literal 0 HcmV?d00001 From 82b65e8934f6e31ef945a13b9295c42a3c207218 Mon Sep 17 00:00:00 2001 From: cyan Date: Sat, 11 Jan 2025 21:35:45 +0100 Subject: [PATCH 05/16] Fix normal wallets showing export outputs QR code (#1952) --- cw_monero/lib/monero_wallet.dart | 3 + ios/Podfile.lock | 100 +++++------------- linux/flutter/generated_plugins.cmake | 1 - macos/Flutter/GeneratedPluginRegistrant.swift | 2 - windows/flutter/generated_plugins.cmake | 1 - 5 files changed, 28 insertions(+), 79 deletions(-) diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index b46e8dd10..af943b9e1 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -265,6 +265,9 @@ abstract class MoneroWalletBase extends WalletBase Date: Sat, 11 Jan 2025 21:36:41 +0100 Subject: [PATCH 06/16] fix print in default_settings_migration (#1953) * fix print in default_settings_migration * Update lib/entities/default_settings_migration.dart [skip ci] --------- Co-authored-by: Omar Hatem --- lib/entities/default_settings_migration.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index abcdda4c0..049bfa15c 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -394,9 +394,6 @@ Future _updateNode({ bool? useSSL, }) async { for (Node node in nodes.values) { - print("@@@@@@@@@@@@@"); - print(node.uriRaw); - print(node.uri); if (node.uriRaw == currentUri) { if (newUri != null) { node.uriRaw = newUri; From 206c7159c21e63c67430f8063f58a5fb5b6a3650 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Mon, 13 Jan 2025 13:06:43 +0200 Subject: [PATCH 07/16] minor fix [skip ci] --- ios/Podfile.lock | 100 +++++++++++++----- lib/src/screens/send/send_page.dart | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + windows/flutter/generated_plugins.cmake | 1 + 5 files changed, 83 insertions(+), 25 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 5b80a86e8..9e2a8507a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3,6 +3,38 @@ PODS: - Flutter - ReachabilitySwift - CryptoSwift (1.8.3) + - cw_haven (0.0.1): + - cw_haven/Boost (= 0.0.1) + - cw_haven/Haven (= 0.0.1) + - cw_haven/OpenSSL (= 0.0.1) + - cw_haven/Sodium (= 0.0.1) + - cw_shared_external + - Flutter + - cw_haven/Boost (0.0.1): + - cw_shared_external + - Flutter + - cw_haven/Haven (0.0.1): + - cw_shared_external + - Flutter + - cw_haven/OpenSSL (0.0.1): + - cw_shared_external + - Flutter + - cw_haven/Sodium (0.0.1): + - cw_shared_external + - Flutter + - cw_mweb (0.0.1): + - Flutter + - cw_shared_external (0.0.1): + - cw_shared_external/Boost (= 0.0.1) + - cw_shared_external/OpenSSL (= 0.0.1) + - cw_shared_external/Sodium (= 0.0.1) + - Flutter + - cw_shared_external/Boost (0.0.1): + - Flutter + - cw_shared_external/OpenSSL (0.0.1): + - Flutter + - cw_shared_external/Sodium (0.0.1): + - Flutter - device_display_brightness (0.0.1): - Flutter - device_info_plus (0.0.1): @@ -85,6 +117,8 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS + - sp_scanner (0.0.1): + - Flutter - SwiftyGif (5.4.5) - Toast (4.1.1) - uni_links (0.0.1): @@ -102,6 +136,9 @@ PODS: DEPENDENCIES: - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - CryptoSwift + - cw_haven (from `.symlinks/plugins/cw_haven/ios`) + - cw_mweb (from `.symlinks/plugins/cw_mweb/ios`) + - cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`) - device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - devicelocale (from `.symlinks/plugins/devicelocale/ios`) @@ -121,6 +158,7 @@ DEPENDENCIES: - sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - sp_scanner (from `.symlinks/plugins/sp_scanner/ios`) - uni_links (from `.symlinks/plugins/uni_links/ios`) - universal_ble (from `.symlinks/plugins/universal_ble/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) @@ -141,6 +179,12 @@ SPEC REPOS: EXTERNAL SOURCES: connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/ios" + cw_haven: + :path: ".symlinks/plugins/cw_haven/ios" + cw_mweb: + :path: ".symlinks/plugins/cw_mweb/ios" + cw_shared_external: + :path: ".symlinks/plugins/cw_shared_external/ios" device_display_brightness: :path: ".symlinks/plugins/device_display_brightness/ios" device_info_plus: @@ -179,6 +223,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sp_scanner: + :path: ".symlinks/plugins/sp_scanner/ios" uni_links: :path: ".symlinks/plugins/uni_links/ios" universal_ble: @@ -191,40 +237,44 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/workmanager/ios" SPEC CHECKSUMS: - connectivity_plus: 481668c94744c30c53b8895afb39159d1e619bdf + connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483 - device_display_brightness: 04374ebd653619292c1d996f00f42877ea19f17f - device_info_plus: 335f3ce08d2e174b9fdc3db3db0f4e3b1f66bd89 - devicelocale: bd64aa714485a8afdaded0892c1e7d5b7f680cf8 + cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a + cw_mweb: 87af74f9659fed0c1a2cbfb44413f1070e79e3ae + cw_shared_external: 2972d872b8917603478117c9957dfca611845a92 + device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7 + device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 + devicelocale: 35ba84dc7f45f527c3001535d8c8d104edd5d926 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 - fast_scanner: 2cb1ad3e69e645e9980fb4961396ce5804caa3e3 - file_picker: 07c75322ede1d47ec9bb4ac82b27c94d3598251a + fast_scanner: 44c00940355a51258cd6c2085734193cd23d95bc + file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 - flutter_local_authentication: 989278c681612f1ee0e36019e149137f114b9d7f - flutter_mailer: 3a8cd4f36c960fb04528d5471097270c19fec1c4 - flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418 - fluttertoast: 76fea30fcf04176325f6864c87306927bd7d2038 - in_app_review: 5596fe56fab799e8edb3561c03d053363ab13457 - integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e + flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 + flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb + flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83 + flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be + fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c + in_app_review: a31b5257259646ea78e0e35fc914979b0031d011 + integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 - package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - permission_handler_apple: 3787117e48f80715ff04a3830ca039283d6a4f29 + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3 - sensitive_clipboard: 161e9abc3d56b3131309d8a321eb4690a803c16b - share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986 + share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e - uni_links: ed8c961e47ed9ce42b6d91e1de8049e38a4b3152 - universal_ble: ff19787898040d721109c6324472e5dd4bc86adc - url_launcher_ios: 694010445543906933d732453a59da0a173ae33d - wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49 - workmanager: 01be2de7f184bd15de93a1812936a2b7f42ef07e + uni_links: d97da20c7701486ba192624d99bffaaffcfc298a + universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6 + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + wakelock_plus: 373cfe59b235a6dd5837d0fb88791d2f13a90d56 + workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6 PODFILE CHECKSUM: e448f662d4c41f0c0b1ccbb78afd57dbf895a597 -COCOAPODS: 1.16.2 +COCOAPODS: 1.15.2 diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index a52bd11e9..2d64c55a3 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -525,6 +525,10 @@ class SendPage extends BasePage { if (state is TransactionCommitted) { WidgetsBinding.instance.addPostFrameCallback((_) async { + if (!context.mounted) { + return; + } + final successMessage = S.of(context).send_success( sendViewModel.selectedCryptoCurrency.toString()); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 4b9eb3b2d..f52be7481 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -10,6 +10,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + sp_scanner ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 52b44e53e..42b9fa84c 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,7 @@ import FlutterMacOS import Foundation import connectivity_plus +import cw_mweb import device_info_plus import devicelocale import fast_scanner @@ -23,6 +24,7 @@ import wakelock_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) + CwMwebPlugin.register(with: registry.registrar(forPlugin: "CwMwebPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index f8f89611c..e0f2c11c0 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -14,6 +14,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + sp_scanner ) set(PLUGIN_BUNDLED_LIBRARIES) From 80b116b8ae31bb86830aca1fa50192919a0bb2af Mon Sep 17 00:00:00 2001 From: hidewrong Date: Tue, 14 Jan 2025 22:34:53 +0800 Subject: [PATCH 08/16] chore: fix some typos for build-guide-*.md (#1958) Signed-off-by: hidewrong --- build-guide-linux.md | 2 +- build-guide-win.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build-guide-linux.md b/build-guide-linux.md index 99c2ed0c8..df5f0f601 100644 --- a/build-guide-linux.md +++ b/build-guide-linux.md @@ -115,7 +115,7 @@ Install Flutter package dependencies with this command: > `$ ./cakewallet.sh` > and back to project root directory: > `$ cd ../..` -> and fetch dependecies again +> and fetch dependencies again > `$ flutter pub get` Your CakeWallet binary will be built with some specific keys for iterate with 3rd party services. You may generate these secret keys placeholders with the following command: diff --git a/build-guide-win.md b/build-guide-win.md index 6ace961af..8cfd02c4c 100644 --- a/build-guide-win.md +++ b/build-guide-win.md @@ -16,14 +16,14 @@ These steps will help you configure and execute a build of CakeWallet from its s ### 1. Installing Package Dependencies For build CakeWallet windows application from sources you will be needed to have: -> [Install Flutter]Follow installation guide (https://docs.flutter.dev/get-started/install/windows) and install do not miss to dev tools (install https://docs.flutter.dev/get-started/install/windows/desktop#development-tools) which are required for windows desktop development (need to install Git for Windows and Visual Studio 2022). Then install `Desktop development with C++` packages via GUI Visual Studio 2022, or Visual Studio Build Tools 2022 including: `C++ Build Tools core features`, `C++ 2022 Redistributable Update`, `C++ core desktop features`, `MVC v143 - VS 2022 C++ x64/x86 build tools`, `C++ CMake tools for Windwos`, `Testing tools core features - Build Tools`, `C++ AddressSanitizer`. +> [Install Flutter]Follow installation guide (https://docs.flutter.dev/get-started/install/windows) and install do not miss to dev tools (install https://docs.flutter.dev/get-started/install/windows/desktop#development-tools) which are required for windows desktop development (need to install Git for Windows and Visual Studio 2022). Then install `Desktop development with C++` packages via GUI Visual Studio 2022, or Visual Studio Build Tools 2022 including: `C++ Build Tools core features`, `C++ 2022 Redistributable Update`, `C++ core desktop features`, `MVC v143 - VS 2022 C++ x64/x86 build tools`, `C++ CMake tools for Windows`, `Testing tools core features - Build Tools`, `C++ AddressSanitizer`. > [Install WSL] for building monero dependencies need to install Windows WSL (https://learn.microsoft.com/en-us/windows/wsl/install) and required packages for WSL (Ubuntu): `$ sudo apt update ` `$ sudo apt build-essential cmake gcc-mingw-w64 g++-mingw-w64 autoconf libtool pkg-config` ### 2. Pull CakeWallet source code -You can downlaod CakeWallet source code from our [GitHub repository](github.com/cake-tech/cake_wallet) via git by following next command: +You can download CakeWallet source code from our [GitHub repository](github.com/cake-tech/cake_wallet) via git by following next command: `$ git clone https://github.com/cake-tech/cake_wallet.git --branch MrCyjaneK-cyjan-monerodart` OR you can download it as [Zip archive](https://github.com/cake-tech/cake_wallet/archive/refs/heads/MrCyjaneK-cyjan-monerodart.zip) From 3e10023e187ec098de80ee2c984401ef2ef664b4 Mon Sep 17 00:00:00 2001 From: cyan Date: Wed, 15 Jan 2025 12:09:59 +0100 Subject: [PATCH 09/16] CW-827 CI/CD update (#1948) * CI update - use existing build outputs in build_monero_all.sh - update $HOME, fix gh actions - add secrets earlier in the runtime (potentially speed up 'Build generated code' step) - add windows dockerfile - add linux/android dockerfile - update android/linux ci script * [skip slack] [run tests] Run tests on CI, fix tests * [skip slack] [run tests] force enable kvm in android * [skip slack] [run tests] remove inexisting flag * [run tests] [skip slack] update tests * add extra dependencies [skip ci] * [skip slack] [run tests] Add secrets * [skip slack] [run tests] Timeout test cases, continue on error * [skip slack] [run tests] Xvfb fix, timeout fix * [skip slack] [run tests] Start dbus to clean up the logs, use SIGKILL * [skip slack] [run tests] Enable network manager * [skip slack] [run tests] Screen record test, resize screen * [skip slack] [run tests] Improve status report for tests * [skip slack] [run tests] Increase framerate * [skip slack] [run tests] Remove test that I am unable to fix locally easily from CI * [skip slack] [run tests] Simplify ffmpeg command * [skip slack] [run tests] Increase timeout, add comment * [skip slack] Update dockerfile, migrate from mrcyjanek to cake-tech for the ghcr org * Update lib/entities/default_settings_migration.dart Co-authored-by: Omar Hatem --------- Co-authored-by: Omar Hatem --- .github/workflows/cache_dependencies.yml | 84 ---- .github/workflows/pr_test_build_android.yml | 412 +++++++++--------- .github/workflows/pr_test_build_linux.yml | 373 +++++++++------- cw_monero/lib/api/wallet.dart | 4 +- .../components/common_test_cases.dart | 5 + .../components/common_test_flows.dart | 34 +- integration_test/funds_related_tests.dart | 5 + integration_test/robots/auth_page_robot.dart | 5 + .../robots/pin_code_widget_robot.dart | 14 + .../restore_from_seed_or_key_robot.dart | 20 +- integration_test/robots/send_page_robot.dart | 35 +- .../robots/wallet_seed_page_robot.dart | 16 +- .../test_suites/confirm_seeds_flow_test.dart | 8 + .../test_suites/exchange_flow_test.dart | 4 + ...estore_wallet_through_seeds_flow_test.dart | 3 + integration_test_runner.sh | 5 + .../screens/new_wallet/new_wallet_page.dart | 2 + .../wallet_restore_from_keys_form.dart | 2 + .../wallet_restore_from_seed_form.dart | 2 + .../wallet_unlock/wallet_unlock_page.dart | 2 + linux/my_application.cc | 7 +- model_generator.sh | 34 +- scripts/android/.gitignore | 1 + scripts/android/build_monero_all.sh | 60 +-- scripts/android/build_mwebd.sh | 5 +- scripts/linux/Dockerfile.linux | 148 +++++++ scripts/linux/build_monero_all.sh | 18 +- scripts/prepare_moneroc.sh | 4 +- scripts/windows/.gitignore | 1 + scripts/windows/Dockerfile.windows | 68 +++ scripts/windows/ci_entrypoint.ps1 | 5 + scripts/windows/ci_register.ps1 | 30 ++ 32 files changed, 862 insertions(+), 554 deletions(-) delete mode 100644 .github/workflows/cache_dependencies.yml create mode 100644 scripts/android/.gitignore create mode 100644 scripts/linux/Dockerfile.linux create mode 100644 scripts/windows/.gitignore create mode 100644 scripts/windows/Dockerfile.windows create mode 100644 scripts/windows/ci_entrypoint.ps1 create mode 100644 scripts/windows/ci_register.ps1 diff --git a/.github/workflows/cache_dependencies.yml b/.github/workflows/cache_dependencies.yml deleted file mode 100644 index cb2afa396..000000000 --- a/.github/workflows/cache_dependencies.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: Cache Dependencies - -on: - workflow_dispatch: - push: - branches: [ main ] - -jobs: - test: - - runs-on: ubuntu-20.04 - - steps: - - name: Free Disk Space (Ubuntu) - uses: insightsengineering/disk-space-reclaimer@v1 - with: - tools-cache: true - android: false - dotnet: true - haskell: true - large-packages: true - swap-storage: true - docker-images: true - - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 - with: - distribution: "temurin" - java-version: "17" - - name: Configure placeholder git details - run: | - git config --global user.email "CI@cakewallet.com" - git config --global user.name "Cake Github Actions" - - name: Flutter action - uses: subosito/flutter-action@v1 - with: - flutter-version: "3.24.4" - channel: stable - - - name: Install package dependencies - run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang - - - name: Execute Build and Setup Commands - run: | - sudo mkdir -p /opt/android - sudo chown $USER /opt/android - cd /opt/android - -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - cargo install cargo-ndk - git clone https://github.com/cake-tech/cake_wallet.git --branch main - cd cake_wallet/scripts/android/ - ./install_ndk.sh - source ./app_env.sh cakewallet - chmod +x pubspec_gen.sh - ./app_config.sh - - - name: Cache Externals - id: cache-externals - uses: actions/cache@v3 - with: - path: | - /opt/android/cake_wallet/cw_haven/android/.cxx - /opt/android/cake_wallet/scripts/monero_c/release - key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }} - - - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} - name: Generate Externals - run: | - cd /opt/android/cake_wallet/scripts/android/ - source ./app_env.sh cakewallet - ./build_monero_all.sh - - - name: Cache Keystore - id: cache-keystore - uses: actions/cache@v3 - with: - path: /opt/android/cake_wallet/android/app - key: keystore - - - if: ${{ steps.cache-keystore.outputs.cache-hit != 'true' }} - name: Generate KeyStore - run: | - cd /opt/android/cake_wallet/android/app - keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml index cdd0e40b4..2a7eec5c9 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -1,169 +1,93 @@ -name: PR Test Build +name: Cake Wallet Android -on: - pull_request: - branches: [main] - workflow_dispatch: - inputs: - branch: - description: "Branch name to build" - required: true - default: "main" +on: [push] +defaults: + run: + shell: bash jobs: PR_test_build: - runs-on: ubuntu-20.04 + runs-on: linux-amd64 + container: + image: ghcr.io/cake-tech/cake_wallet:main-linux + env: + STORE_PASS: test@cake_wallet + KEY_PASS: test@cake_wallet + MONEROC_CACHE_DIR_ROOT: /opt/generic_cache + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + ANDROID_AVD_HOME: /root/.android/avd + volumes: + - /opt/cw_cache_android/root/.cache:/root/.cache + - /opt/cw_cache_android/root/.android/avd/:/root/.android/avd + - /opt/cw_cache_android/root/.ccache:/root/.ccache + - /opt/cw_cache_android/root/.pub-cache/:/root/.pub-cache + - /opt/cw_cache_android/root/.gradle/:/root/.gradle + - /opt/cw_cache_android/root/.android/:/root/.android + - /opt/cw_cache_android/root/go/pkg:/root/go/pkg + - /opt/cw_cache_android/opt/generic_cache:/opt/generic_cache + - /dev/kvm:/dev/kvm strategy: matrix: api-level: [29] - env: - STORE_PASS: test@cake_wallet - KEY_PASS: test@cake_wallet - PR_NUMBER: ${{ github.event.number }} steps: - - name: is pr - if: github.event_name == 'pull_request' - run: echo "BRANCH_NAME=${GITHUB_HEAD_REF}" >> $GITHUB_ENV - - - name: is not pr - if: github.event_name != 'pull_request' - run: echo "BRANCH_NAME=${{ github.event.inputs.branch }}" >> $GITHUB_ENV - - - name: Free Disk Space (Ubuntu) - uses: insightsengineering/disk-space-reclaimer@v1 - with: - tools-cache: true - android: false - dotnet: true - haskell: true - large-packages: true - swap-storage: true - docker-images: true - - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 - with: - distribution: "temurin" - java-version: "17" - - name: Configure placeholder git details + - name: Fix github actions messing up $HOME... + run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV' + - uses: actions/checkout@v4 + - name: configure git run: | - git config --global user.email "CI@cakewallet.com" - git config --global user.name "Cake Github Actions" - - name: Flutter action - uses: subosito/flutter-action@v1 - with: - flutter-version: "3.24.0" - channel: stable - - - name: Install package dependencies - run: | - sudo apt update - sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang - - - - name: Clone Repo - run: | - sudo mkdir -p /opt/android - sudo chown $USER /opt/android - cd /opt/android - git clone https://github.com/cake-tech/cake_wallet.git --branch ${{ env.BRANCH_NAME }} - -# - name: Cache Keystore -# id: cache-keystore -# uses: actions/cache@v3 -# with: -# path: /opt/android/cake_wallet/android/app -# key: keystore -# -# - if: ${{ steps.cache-keystore.outputs.cache-hit != 'true' }} - - name: Generate KeyStore - run: | - cd /opt/android/cake_wallet/android/app - keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS - - - name: Execute Build and Setup Commands - run: | - cd /opt/android - -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - cargo install cargo-ndk - cd cake_wallet/scripts/android/ - ./install_ndk.sh - source ./app_env.sh cakewallet - chmod +x pubspec_gen.sh - ./app_config.sh - - - name: Cache Externals - id: cache-externals - uses: actions/cache@v3 - with: - path: | - /opt/android/cake_wallet/cw_haven/android/.cxx - /opt/android/cake_wallet/scripts/monero_c/release - key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }} - - - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} - name: Generate Externals - run: | - cd /opt/android/cake_wallet/scripts/android/ - source ./app_env.sh cakewallet - ./build_monero_all.sh - - - name: Install Flutter dependencies - run: | - cd /opt/android/cake_wallet - flutter pub get - - - - name: Install go and gomobile - run: | - # install go > 1.23: - wget https://go.dev/dl/go1.23.1.linux-amd64.tar.gz - sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.23.1.linux-amd64.tar.gz - export PATH=$PATH:/usr/local/go/bin - export PATH=$PATH:~/go/bin - go install golang.org/x/mobile/cmd/gomobile@latest - gomobile init - - - name: Build mwebd - run: | - # paths are reset after each step, so we need to set them again: - export PATH=$PATH:/usr/local/go/bin - export PATH=$PATH:~/go/bin - cd /opt/android/cake_wallet/scripts/android/ - ./build_mwebd.sh --dont-install - - - name: Generate key properties - run: | - cd /opt/android/cake_wallet - dart run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS - - - name: Generate localization - run: | - cd /opt/android/cake_wallet - dart run tool/generate_localization.dart - - - name: Build generated code - run: | - cd /opt/android/cake_wallet - ./model_generator.sh - + git config --global user.email "ci@cakewallet.com" + git config --global user.name "CakeWallet CI" - name: Add secrets run: | - cd /opt/android/cake_wallet touch lib/.secrets.g.dart touch cw_evm/lib/.secrets.g.dart touch cw_solana/lib/.secrets.g.dart touch cw_core/lib/.secrets.g.dart touch cw_nano/lib/.secrets.g.dart touch cw_tron/lib/.secrets.g.dart - echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart - echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart - echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart - echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart - echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart - echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart - echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + if [[ "x${{ secrets.SALT }}" == "x" ]]; + then + echo "const salt = '954f787f12622067f7e548d9450c3832';" > lib/.secrets.g.dart + else + echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart + fi + if [[ "x${{ secrets.KEY_CHAIN_SALT }}" == "x" ]]; + then + echo "const keychainSalt = '2d2beba777dbf7dff7013b7a';" >> lib/.secrets.g.dart + else + echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.KEY }}" == "x" ]]; + then + echo "const key = '638e98820ec10a2945e968435c9397a3';" >> lib/.secrets.g.dart + else + echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.WALLET_SALT }}" == "x" ]]; + then + echo "const walletSalt = '8f7f1b70';" >> lib/.secrets.g.dart + else + echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.SHORT_KEY }}" == "x" ]]; + then + echo "const shortKey = '653f270c2c152bc7ec864afe';" >> lib/.secrets.g.dart + else + echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.BACKUP_SALT }}" == "x" ]]; + then + echo "const backupSalt = 'bf630d24ff0b6f60';" >> lib/.secrets.g.dart + else + echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.BACKUP_KEY_CHAIN_SALT }}" == "x" ]]; + then + echo "const backupKeychainSalt = 'bf630d24ff0b6f60';" >> lib/.secrets.g.dart + else + echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + fi echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart @@ -213,86 +137,152 @@ jobs: echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart + # for tests + echo "const moneroTestWalletSeeds ='${{ secrets.MONERO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const moneroLegacyTestWalletSeeds = '${{ secrets.MONERO_LEGACY_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const bitcoinTestWalletSeeds = '${{ secrets.BITCOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const ethereumTestWalletSeeds = '${{ secrets.ETHEREUM_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const litecoinTestWalletSeeds = '${{ secrets.LITECOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const nanoTestWalletSeeds = '${{ secrets.NANO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const wowneroTestWalletSeeds = '${{ secrets.WOWNERO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const moneroTestWalletReceiveAddress = '${{ secrets.MONERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const bitcoinTestWalletReceiveAddress = '${{ secrets.BITCOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const ethereumTestWalletReceiveAddress = '${{ secrets.ETHEREUM_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const litecoinTestWalletReceiveAddress = '${{ secrets.LITECOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const wowneroTestWalletReceiveAddress = '${{ secrets.WOWNERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const moneroTestWalletBlockHeight = '${{ secrets.MONERO_TEST_WALLET_BLOCK_HEIGHT }}';" >> lib/.secrets.g.dart + - name: prepare monero_c and cache + run: | + export MONEROC_HASH=$(cat scripts/prepare_moneroc.sh | grep 'git checkout' | xargs | awk '{ print $3 }') + echo MONEROC_HASH=$MONEROC_HASH >> /etc/environment + mkdir -p "$MONEROC_CACHE_DIR_ROOT/moneroc-$MONEROC_HASH/monero_c" + pushd scripts + ln -s "$MONEROC_CACHE_DIR_ROOT/moneroc-$MONEROC_HASH/monero_c" + ./prepare_moneroc.sh + popd + pushd scripts/monero_c + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/built" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/built" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/built" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/sources" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/sources" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/sources" || true + + rm -rf "$PWD/contrib/depends/built" "$PWD/monero/contrib/depends/built" "$PWD/wownero/contrib/depends/built" + rm -rf "$PWD/contrib/depends/sources" "$PWD/monero/contrib/depends/sources" "$PWD/wownero/contrib/depends/sources" + mkdir -p contrib/depends || true + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/built" "$PWD/contrib/depends/built" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/built" "$PWD/monero/contrib/depends/built" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/built" "$PWD/wownero/contrib/depends/built" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/sources" "$PWD/contrib/depends/sources" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/sources" "$PWD/monero/contrib/depends/sources" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/sources" "$PWD/wownero/contrib/depends/sources" + popd + + - name: Generate KeyStore + run: | + pushd /opt/generic_cache + if [[ ! -f key.jks ]]; + then + keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS + else + echo "$PWD/key.jks exist, not generating" + fi + popd + cp /opt/generic_cache/key.jks android/app + + - name: Execute Build and Setup Commands + run: | + pushd scripts/android + source ./app_env.sh cakewallet + ./app_config.sh + popd + + - name: Build monero_c + run: | + pushd scripts/android/ + source ./app_env.sh cakewallet + ./build_monero_all.sh + popd + + - name: Install Flutter dependencies + run: | + flutter pub get + + - name: Build mwebd + run: | + set -x -e + export MWEBD_HASH=$(cat scripts/android/build_mwebd.sh | grep 'git reset --hard' | xargs | awk '{ print $4 }') + echo MWEBD_HASH=$MWEBD_HASH >> /etc/environment + pushd scripts/android + gomobile init; + ./build_mwebd.sh --dont-install + popd + + - name: Build generated code + run: | + ./model_generator.sh async + + - name: Generate key properties + run: | + dart run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS + + - name: Generate localization + run: | + dart run tool/generate_localization.dart - name: Rename app run: | - echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties + sanitized_branch_name=${BRANCH_NAME#origin/} # Remove 'origin/' prefix if it exists + sanitized_branch_name=${sanitized_branch_name:0:16} # Take only the first 16 characters + sanitized_branch_name=$(echo "$sanitized_branch_name" | tr '[:upper:]' '[:lower:]') # Convert to lowercase + sanitized_branch_name=$(echo "$sanitized_branch_name" | sed 's/[^a-z0-9]//g') # Remove all special characters - # Step 3: Download previous build number - - name: Download previous build number - id: download-build-number - run: | - # Download the artifact if it exists - if [[ ! -f build_number.txt ]]; then - echo "1" > build_number.txt - fi - - # Step 4: Read and Increment Build Number - - name: Increment Build Number - id: increment-build-number - run: | - # Read current build number from file - BUILD_NUMBER=$(cat build_number.txt) - BUILD_NUMBER=$((BUILD_NUMBER + 1)) - echo "New build number: $BUILD_NUMBER" - - # Save the incremented build number - echo "$BUILD_NUMBER" > build_number.txt - - # Export the build number to use in later steps - echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_ENV - - # Step 5: Update pubspec.yaml with new build number - - name: Update build number - run: | - cd /opt/android/cake_wallet - sed -i "s/^version: .*/version: 1.0.$BUILD_NUMBER/" pubspec.yaml + echo -e "id=com.cakewallet.test_${sanitized_branch_name}\nname=${BRANCH_NAME}" > android/app.properties - name: Build run: | - cd /opt/android/cake_wallet flutter build apk --release --split-per-abi - # - name: Push to App Center - # run: | - # echo 'Installing App Center CLI tools' - # npm install -g appcenter-cli - # echo "Publishing test to App Center" - # appcenter distribute release \ - # --group "Testers" \ - # --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \ - # --release-notes ${{ env.BRANCH_NAME }} \ - # --app Cake-Labs/Cake-Wallet \ - # --token ${{ secrets.APP_CENTER_TOKEN }} \ - # --quiet - - name: Rename apk file run: | - cd /opt/android/cake_wallet/build/app/outputs/flutter-apk + cd build/app/outputs/flutter-apk mkdir test-apk - cp app-arm64-v8a-release.apk test-apk/${{env.BRANCH_NAME}}.apk - cp app-x86_64-release.apk test-apk/${{env.BRANCH_NAME}}_x86.apk + cp app-arm64-v8a-release.apk test-apk/${BRANCH_NAME}.apk + cp app-x86_64-release.apk test-apk/${BRANCH_NAME}_x86.apk + cd test-apk + cp ${BRANCH_NAME}.apk ${BRANCH_NAME}_slack.apk - - name: Upload Artifact - uses: kittaakos/upload-artifact-as-is@v0 - with: - path: /opt/android/cake_wallet/build/app/outputs/flutter-apk/test-apk/ - - # Re-upload updated build number for the next run - - name: Upload updated build number - uses: actions/upload-artifact@v3 - with: - name: build_number - path: build_number.txt - - - name: Send Test APK + - name: Find APK file + id: find_apk + run: | + set -x + apk_file=$(ls build/app/outputs/flutter-apk/test-apk/*_slack.apk || exit 1) + echo "APK_FILE=$apk_file" >> $GITHUB_ENV + + - name: Upload artifact to slack + if: ${{ !contains(github.event.head_commit.message, 'skip slack') }} continue-on-error: true uses: adrey/slack-file-upload-action@1.0.5 with: token: ${{ secrets.SLACK_APP_TOKEN }} - path: /opt/android/cake_wallet/build/app/outputs/flutter-apk/test-apk/${{env.BRANCH_NAME}}.apk + path: ${{ env.APK_FILE }} channel: ${{ secrets.SLACK_APK_CHANNEL }} - title: "${{ env.BRANCH_NAME }}.apk" - filename: ${{ env.BRANCH_NAME }}.apk initial_comment: ${{ github.event.head_commit.message }} + - name: cleanup + run: rm -rf build/app/outputs/flutter-apk/test-apk/ + + - name: Upload Artifact to github + uses: kittaakos/upload-artifact-as-is@v0 + with: + path: ${{ github.workspace }}/build/app/outputs/flutter-apk \ No newline at end of file diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index 891327d1e..6a29e4033 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -1,139 +1,89 @@ -name: PR Test Build linux +name: Cake Wallet Linux -on: - pull_request: - branches: [main] - workflow_dispatch: - inputs: - branch: - description: "Branch name to build" - required: true - default: "main" +on: [push] +defaults: + run: + shell: bash jobs: PR_test_build: - runs-on: ubuntu-20.04 - env: - STORE_PASS: test@cake_wallet - KEY_PASS: test@cake_wallet - PR_NUMBER: ${{ github.event.number }} + runs-on: linux-amd64 + container: + image: ghcr.io/cake-tech/cake_wallet:main-linux + env: + STORE_PASS: test@cake_wallet + KEY_PASS: test@cake_wallet + MONEROC_CACHE_DIR_ROOT: /opt/generic_cache + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + DESKTOP_FORCE_MOBILE: Y + volumes: + - /opt/cw_cache_linux/root/.cache:/root/.cache + - /opt/cw_cache_linux/root/.ccache:/root/.ccache + - /opt/cw_cache_linux/root/.pub-cache/:/root/.pub-cache + - /opt/cw_cache_linux/root/go/pkg:/root/go/pkg + - /opt/cw_cache_linux/opt/generic_cache:/opt/generic_cache + strategy: + matrix: + api-level: [29] steps: - - name: is pr - if: github.event_name == 'pull_request' - run: echo "BRANCH_NAME=${GITHUB_HEAD_REF}" >> $GITHUB_ENV - - - name: is not pr - if: github.event_name != 'pull_request' - run: echo "BRANCH_NAME=${{ github.event.inputs.branch }}" >> $GITHUB_ENVg - - - uses: actions/checkout@v2 - - uses: actions/setup-java@v1 - with: - java-version: "17.x" - - name: Configure placeholder git details + - name: Fix github actions messing up $HOME... + run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV' + - uses: actions/checkout@v4 + - name: configure git run: | - git config --global user.email "CI@cakewallet.com" - git config --global user.name "Cake Github Actions" - - name: Flutter action - uses: subosito/flutter-action@v1 - with: - flutter-version: "3.24.0" - channel: stable - - - name: Install package dependencies - run: | - sudo apt update - sudo apt-get install -y curl unzip automake build-essential file pkg-config git python-is-python3 libtool libtinfo5 cmake clang - - - name: Install desktop dependencies - run: | - sudo apt update - sudo apt install -y ninja-build libgtk-3-dev gperf - - name: Execute Build and Setup Commands - run: | - sudo mkdir -p /opt/android - sudo chown $USER /opt/android - cd /opt/android - -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - cargo install cargo-ndk - git clone https://github.com/cake-tech/cake_wallet.git --branch ${{ env.BRANCH_NAME }} - cd scripts && ./gen_android_manifest.sh && cd .. - cd cake_wallet/scripts/android/ - source ./app_env.sh cakewallet - ./app_config.sh - cd ../../.. - cd cake_wallet/scripts/linux/ - source ./app_env.sh cakewallet - ./app_config.sh - cd ../../.. - - - name: Cache Externals - id: cache-externals - uses: actions/cache@v3 - with: - path: | - /opt/android/cake_wallet/cw_haven/android/.cxx - /opt/android/cake_wallet/scripts/monero_c/release - key: linux_${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }} - - - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} - name: Generate Externals - run: | - cd /opt/android/cake_wallet/scripts/linux/ - source ./app_env.sh cakewallet - ./build_monero_all.sh - - - name: Install Flutter dependencies - run: | - cd /opt/android/cake_wallet - flutter pub get - - - name: Install go and gomobile - run: | - # install go > 1.23: - wget https://go.dev/dl/go1.23.1.linux-amd64.tar.gz - sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.23.1.linux-amd64.tar.gz - export PATH=$PATH:/usr/local/go/bin - export PATH=$PATH:~/go/bin - go install golang.org/x/mobile/cmd/gomobile@latest - gomobile init - - - name: Build mwebd - run: | - # paths are reset after each step, so we need to set them again: - export PATH=$PATH:/usr/local/go/bin - export PATH=$PATH:~/go/bin - # build mwebd: - cd /opt/android/cake_wallet/scripts/android/ - ./build_mwebd.sh --dont-install - - - name: Generate localization - run: | - cd /opt/android/cake_wallet - dart run tool/generate_localization.dart - - - name: Build generated code - run: | - cd /opt/android/cake_wallet - ./model_generator.sh - + git config --global user.email "ci@cakewallet.com" + git config --global user.name "CakeWallet CI" - name: Add secrets run: | - cd /opt/android/cake_wallet touch lib/.secrets.g.dart touch cw_evm/lib/.secrets.g.dart touch cw_solana/lib/.secrets.g.dart touch cw_core/lib/.secrets.g.dart touch cw_nano/lib/.secrets.g.dart touch cw_tron/lib/.secrets.g.dart - echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart - echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart - echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart - echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart - echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart - echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart - echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + if [[ "x${{ secrets.SALT }}" == "x" ]]; + then + echo "const salt = '954f787f12622067f7e548d9450c3832';" > lib/.secrets.g.dart + else + echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart + fi + if [[ "x${{ secrets.KEY_CHAIN_SALT }}" == "x" ]]; + then + echo "const keychainSalt = '2d2beba777dbf7dff7013b7a';" >> lib/.secrets.g.dart + else + echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.KEY }}" == "x" ]]; + then + echo "const key = '638e98820ec10a2945e968435c9397a3';" >> lib/.secrets.g.dart + else + echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.WALLET_SALT }}" == "x" ]]; + then + echo "const walletSalt = '8f7f1b70';" >> lib/.secrets.g.dart + else + echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.SHORT_KEY }}" == "x" ]]; + then + echo "const shortKey = '653f270c2c152bc7ec864afe';" >> lib/.secrets.g.dart + else + echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.BACKUP_SALT }}" == "x" ]]; + then + echo "const backupSalt = 'bf630d24ff0b6f60';" >> lib/.secrets.g.dart + else + echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.BACKUP_KEY_CHAIN_SALT }}" == "x" ]]; + then + echo "const backupKeychainSalt = 'bf630d24ff0b6f60';" >> lib/.secrets.g.dart + else + echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + fi echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart @@ -144,8 +94,6 @@ jobs: echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart - echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart - echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> lib/.secrets.g.dart echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart @@ -156,8 +104,11 @@ jobs: echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> lib/.secrets.g.dart + echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> lib/.secrets.g.dart + echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart @@ -165,7 +116,6 @@ jobs: echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart @@ -178,39 +128,170 @@ jobs: echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart echo "const meldTestApiKey = '${{ secrets.MELD_TEST_API_KEY }}';" >> lib/.secrets.g.dart - echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dar + echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dart echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart - - - name: Rename app + # tests + echo "const moneroTestWalletSeeds ='${{ secrets.MONERO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const moneroLegacyTestWalletSeeds = '${{ secrets.MONERO_LEGACY_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const bitcoinTestWalletSeeds = '${{ secrets.BITCOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const ethereumTestWalletSeeds = '${{ secrets.ETHEREUM_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const litecoinTestWalletSeeds = '${{ secrets.LITECOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const nanoTestWalletSeeds = '${{ secrets.NANO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const wowneroTestWalletSeeds = '${{ secrets.WOWNERO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const moneroTestWalletReceiveAddress = '${{ secrets.MONERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const bitcoinTestWalletReceiveAddress = '${{ secrets.BITCOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const ethereumTestWalletReceiveAddress = '${{ secrets.ETHEREUM_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const litecoinTestWalletReceiveAddress = '${{ secrets.LITECOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const wowneroTestWalletReceiveAddress = '${{ secrets.WOWNERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const moneroTestWalletBlockHeight = '${{ secrets.MONERO_TEST_WALLET_BLOCK_HEIGHT }}';" >> lib/.secrets.g.dart + - name: prepare monero_c and cache run: | - echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties + export MONEROC_HASH=$(cat scripts/prepare_moneroc.sh | grep 'git checkout' | xargs | awk '{ print $3 }') + echo MONEROC_HASH=$MONEROC_HASH >> /etc/environment + mkdir -p "$MONEROC_CACHE_DIR_ROOT/moneroc-$MONEROC_HASH/monero_c" + pushd scripts + ln -s "$MONEROC_CACHE_DIR_ROOT/moneroc-$MONEROC_HASH/monero_c" + ./prepare_moneroc.sh + popd + pushd scripts/monero_c + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/built" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/built" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/built" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/sources" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/sources" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/sources" || true - - name: Build + rm -rf "$PWD/contrib/depends/built" "$PWD/monero/contrib/depends/built" "$PWD/wownero/contrib/depends/built" + rm -rf "$PWD/contrib/depends/sources" "$PWD/monero/contrib/depends/sources" "$PWD/wownero/contrib/depends/sources" + mkdir -p contrib/depends || true + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/built" "$PWD/contrib/depends/built" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/built" "$PWD/monero/contrib/depends/built" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/built" "$PWD/wownero/contrib/depends/built" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/sources" "$PWD/contrib/depends/sources" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/sources" "$PWD/monero/contrib/depends/sources" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/sources" "$PWD/wownero/contrib/depends/sources" + popd + + - name: Execute Build and Setup Commands + run: | + pushd scripts/linux + source ./app_env.sh cakewallet + ./app_config.sh + popd + + - name: Build monero_c + run: | + pushd scripts/linux/ + source ./app_env.sh cakewallet + ./build_monero_all.sh + popd + + - name: Install Flutter dependencies + run: | + flutter pub get + + - name: Build generated code + run: | + ./model_generator.sh async + + - name: Generate localization + run: | + dart run tool/generate_localization.dart + + - name: Build linux run: | - cd /opt/android/cake_wallet flutter build linux --release - - name: Prepare release zip file + - name: Compress release run: | - cd /opt/android/cake_wallet/build/linux/x64/release - zip -r ${{env.BRANCH_NAME}}.zip bundle + pushd build/linux/x64/release + zip -r cakewallet_linux.zip bundle + popd - - name: Upload Artifact + - name: Upload Artifact to github uses: kittaakos/upload-artifact-as-is@v0 with: - path: /opt/android/cake_wallet/build/linux/x64/release/${{env.BRANCH_NAME}}.zip + path: ${{ github.workspace }}/build/linux/x64/release/cakewallet_linux.zip -# Just as an artifact would be enough -# - name: Send Test APK -# continue-on-error: true -# uses: adrey/slack-file-upload-action@1.0.5 -# with: -# token: ${{ secrets.SLACK_APP_TOKEN }} -# path: /opt/android/cake_wallet/build/linux/x64/release/${{env.BRANCH_NAME}}.zip -# channel: ${{ secrets.SLACK_APK_CHANNEL }} -# title: "${{ env.BRANCH_NAME }}_linux.zip" -# filename: ${{ env.BRANCH_NAME }}_linux.zip -# initial_comment: ${{ github.event.head_commit.message }} + - name: Prepare virtual desktop + if: ${{ contains(github.event.head_commit.message, 'run tests') }} + run: | + nohup Xvfb :99 -screen 0 720x1280x16 & + echo DISPLAY=:99 | sudo tee -a $GITHUB_ENV + dbus-daemon --system --fork + nohup NetworkManager & + nohup ffmpeg -framerate 60 -video_size 720x1280 -f x11grab -i :99 -c:v libx264 -c:a aac /opt/screen_grab.mkv & + + # Note for people adding tests: + # - Tests are ran on Linux, with some things being mocked out. + # - Screen recording is being provided for the entire length of the test, you can download it in github articats. + # - Screen recordeding is encrypted, look at step "Stop screen recording, encrypt and upload", and add your key if you want + # Reason for encryption is the fact that we restore the wallet from seed, and we don't want to leak that, while there + # isn't much in those wallets anyway, we still wouldn't like to leak it to anyone who is able to access github. + + - name: Test [confirm_seeds_flow_test] + if: ${{ contains(github.event.head_commit.message, 'run tests') }} + timeout-minutes: 20 + run: | + xmessage -timeout 30 "confirm_seeds_flow_test" & + rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet + exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/confirm_seeds_flow_test.dart + - name: Test [create_wallet_flow_test] + if: ${{ contains(github.event.head_commit.message, 'run tests') }} + timeout-minutes: 20 + run: | + xmessage -timeout 30 "create_wallet_flow_test" & + rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet + exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/create_wallet_flow_test.dart + - name: Test [exchange_flow_test] + if: ${{ contains(github.event.head_commit.message, 'run tests') }} + timeout-minutes: 20 + run: | + xmessage -timeout 30 "exchange_flow_test" & + rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet + exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/exchange_flow_test.dart + - name: Test [restore_wallet_through_seeds_flow_test] + if: ${{ contains(github.event.head_commit.message, 'run tests') }} + timeout-minutes: 20 + run: | + xmessage -timeout 30 "restore_wallet_through_seeds_flow_test" & + rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet + exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart + - name: Stop screen recording, encrypt and upload + if: always() + run: | + if [[ ! -f "/opt/screen_grab.mkv" ]]; + then + exit 0; + fi + killall ffmpeg + sleep 5 + killall -9 ffmpeg || true + sleep 5 + # Feel free to add your own public key if you wish + gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys 6B3199AD9B3D23B8 # konstantin@cakewallet.com + gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys 35C8DBAFB8D9ACAC # cyjan@mrcyjanek.net + gpg --trust-model always --encrypt --output /opt/screen_grab.mkv.gpg \ + --recipient 6B3199AD9B3D23B8 \ + --recipient 35C8DBAFB8D9ACAC \ + /opt/screen_grab.mkv + rm /opt/screen_grab.mkv + mv /opt/screen_grab.mkv.gpg ./screen_grab.mkv.gpg + - name: Upload Artifact to github + if: always() + continue-on-error: true + uses: kittaakos/upload-artifact-as-is@v0 + with: + path: ${{ github.workspace }}/screen_grab.mkv.gpg diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 537c9802e..2a343d430 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -9,6 +9,8 @@ import 'package:flutter/foundation.dart'; import 'package:monero/monero.dart' as monero; import 'package:mutex/mutex.dart'; +bool debugMonero = false; + int getSyncingHeight() { // final height = monero.MONERO_cw_WalletListener_height(getWlptr()); final h2 = monero.Wallet_blockChainHeight(wptr!); @@ -132,7 +134,7 @@ Future setupNodeSync( } } - if (kDebugMode) { + if (kDebugMode && debugMonero) { monero.Wallet_init3( wptr!, argv0: '', defaultLogBaseName: 'moneroc', diff --git a/integration_test/components/common_test_cases.dart b/integration_test/components/common_test_cases.dart index 83bbb0449..cc1e6d6d7 100644 --- a/integration_test/components/common_test_cases.dart +++ b/integration_test/components/common_test_cases.dart @@ -32,6 +32,11 @@ class CommonTestCases { expect(textWidget, hasWidget ? findsOneWidget : findsNothing); } + void hasTextAtLestOnce(String text, {bool hasWidget = true}) { + final textWidget = find.text(text); + expect(textWidget, hasWidget ? findsAny : findsNothing); + } + void hasType() { final typeWidget = find.byType(T); expect(typeWidget, findsOneWidget); diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart index 8350b5859..c9e605339 100644 --- a/integration_test/components/common_test_flows.dart +++ b/integration_test/components/common_test_flows.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/reactions/bip39_wallet_utils.dart'; import 'package:cake_wallet/wallet_types.g.dart'; @@ -85,6 +87,7 @@ class CommonTestFlows { await _confirmPreSeedInfo(); await _confirmWalletDetails(); + await _commonTestCases.defaultSleepTime(); } //* ========== Handles flow from welcome to restoring wallet from seeds =============== @@ -168,8 +171,8 @@ class CommonTestFlows { await _walletListPageRobot.navigateToRestoreWalletOptionsPage(); await _commonTestCases.defaultSleepTime(); - await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage(); - await _commonTestCases.defaultSleepTime(); + if (!Platform.isLinux) await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage(); + if (!Platform.isLinux) await _commonTestCases.defaultSleepTime(); await _selectWalletTypeForWallet(walletType); await _commonTestCases.defaultSleepTime(); @@ -180,6 +183,7 @@ class CommonTestFlows { //* ========== Handles setting up pin code for wallet on first install =============== Future setupPinCodeForWallet(List pin) async { + if (Platform.isLinux) return; // ----------- SetupPinCode Page ------------- // Confirm initial defaults - Widgets to be displayed etc await _setupPinCodeRobot.isSetupPinCodePage(); @@ -212,7 +216,7 @@ class CommonTestFlows { await _welcomePageRobot.navigateToRestoreWalletPage(); - await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage(); + if (!Platform.isLinux) await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage(); await _selectWalletTypeForWallet(walletTypeToRestore); } @@ -234,6 +238,12 @@ class CommonTestFlows { await _newWalletPageRobot.generateWalletName(); + if (Platform.isLinux) { + // manual pin input + await _restoreFromSeedOrKeysPageRobot.enterPasswordForWalletRestore(CommonTestConstants.pin.join("")); + await _restoreFromSeedOrKeysPageRobot.enterPasswordRepeatForWalletRestore(CommonTestConstants.pin.join("")); + } + await _newWalletPageRobot.onNextButtonPressed(); } @@ -252,11 +262,15 @@ class CommonTestFlows { _walletSeedPageRobot.confirmWalletSeedReminderDisplays(); - await _walletSeedPageRobot.onCopySeedsButtonPressed(); + // await _walletSeedPageRobot.onCopySeedsButtonPressed(); - await _walletSeedPageRobot.onNextButtonPressed(); - - await _walletSeedPageRobot.onConfirmButtonOnSeedAlertDialogPressed(); + await _walletSeedPageRobot.onSeedPageVerifyButtonPressed(); + // Turns out the popup about "Copied to clipboard" prevents + //the button from being pressed on the first try, by just + //tapping it again we fix it. + // await _walletSeedPageRobot.onSeedPageVerifyButtonPressed(); + + await _walletSeedPageRobot.onOpenWalletButtonPressed(); } //* Main Restore Actions - On the RestoreFromSeed/Keys Page - Restore from Seeds Action @@ -277,6 +291,12 @@ class CommonTestFlows { .enterBlockHeightForWalletRestore(secrets.moneroTestWalletBlockHeight); } + if (Platform.isLinux) { + // manual pin input + await _restoreFromSeedOrKeysPageRobot.enterPasswordForWalletRestore(CommonTestConstants.pin.join("")); + await _restoreFromSeedOrKeysPageRobot.enterPasswordRepeatForWalletRestore(CommonTestConstants.pin.join("")); + } + await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed(); } diff --git a/integration_test/funds_related_tests.dart b/integration_test/funds_related_tests.dart index db24fbc0b..27187dc2f 100644 --- a/integration_test/funds_related_tests.dart +++ b/integration_test/funds_related_tests.dart @@ -67,6 +67,11 @@ void main() { await authPageRobot.enterPinCode(CommonTestConstants.pin); } + final onAuthPageDesktop = authPageRobot.onAuthPageDesktop(); + if (onAuthPageDesktop) { + await authPageRobot.enterPassword(CommonTestConstants.pin.join("")); + } + // ----------- Exchange Confirm Page ------------- await exchangeConfirmPageRobot.isExchangeConfirmPage(); diff --git a/integration_test/robots/auth_page_robot.dart b/integration_test/robots/auth_page_robot.dart index 6358d4398..2f5c43627 100644 --- a/integration_test/robots/auth_page_robot.dart +++ b/integration_test/robots/auth_page_robot.dart @@ -20,6 +20,11 @@ class AuthPageRobot extends PinCodeWidgetRobot { return hasPin; } + bool onAuthPageDesktop() { + final hasWalletPasswordInput = find.byKey(ValueKey('enter_wallet_password')); + return hasWalletPasswordInput.tryEvaluate(); + } + Future isAuthPage() async { await commonTestCases.isSpecificPage(); } diff --git a/integration_test/robots/pin_code_widget_robot.dart b/integration_test/robots/pin_code_widget_robot.dart index 18dc5fba4..62e606703 100644 --- a/integration_test/robots/pin_code_widget_robot.dart +++ b/integration_test/robots/pin_code_widget_robot.dart @@ -24,6 +24,20 @@ class PinCodeWidgetRobot { commonTestCases.hasValueKey('pin_code_button_0_key'); } + Future enterPassword(String password, {int pumpDuration = 100}) async { + await commonTestCases.enterText( + password, + 'enter_wallet_password', + ); + await tester.pumpAndSettle(); + await commonTestCases.tapItemByKey( + 'unlock', + ); + await tester.pumpAndSettle(); + + await commonTestCases.defaultSleepTime(); + } + Future enterPinCode(List pinCode, {int pumpDuration = 100}) async { for (int pin in pinCode) { await commonTestCases.tapItemByKey( diff --git a/integration_test/robots/restore_from_seed_or_key_robot.dart b/integration_test/robots/restore_from_seed_or_key_robot.dart index 9d973061b..015a9e46f 100644 --- a/integration_test/robots/restore_from_seed_or_key_robot.dart +++ b/integration_test/robots/restore_from_seed_or_key_robot.dart @@ -1,7 +1,9 @@ import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart'; +import 'package:cake_wallet/src/widgets/seed_widget.dart'; import 'package:cake_wallet/src/widgets/validable_annotated_editable_text.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import '../components/common_test_cases.dart'; @@ -65,12 +67,28 @@ class RestoreFromSeedOrKeysPageRobot { Future enterSeedPhraseForWalletRestore(String text) async { ValidatableAnnotatedEditableTextState seedTextState = - await tester.state(find.byType(ValidatableAnnotatedEditableText)); + await tester.state(find.byType(ValidatableAnnotatedEditableText)); seedTextState.widget.controller.text = text; await tester.pumpAndSettle(); } + Future enterPasswordForWalletRestore(String text) async { + await commonTestCases.enterText( + text, + 'password', + ); + await tester.pumpAndSettle(); + } + + Future enterPasswordRepeatForWalletRestore(String text) async { + await commonTestCases.enterText( + text, + 'repeat_wallet_password', + ); + await tester.pumpAndSettle(); + } + Future enterBlockHeightForWalletRestore(String blockHeight) async { await commonTestCases.enterText( blockHeight, diff --git a/integration_test/robots/send_page_robot.dart b/integration_test/robots/send_page_robot.dart index f8e1a49ad..b705c803f 100644 --- a/integration_test/robots/send_page_robot.dart +++ b/integration_test/robots/send_page_robot.dart @@ -183,32 +183,15 @@ class SendPageRobot { } Future _handleAuthPage() async { - tester.printToConsole('Inside _handleAuth'); - await tester.pump(); - tester.printToConsole('starting auth checks'); - - final authPage = authPageRobot.onAuthPage(); - - tester.printToConsole('hasAuth:$authPage'); - - if (authPage) { - await tester.pump(); - tester.printToConsole('Starting inner _handleAuth loop checks'); - - try { - await authPageRobot.enterPinCode(CommonTestConstants.pin, pumpDuration: 500); - tester.printToConsole('Auth done'); - - await tester.pump(); - - tester.printToConsole('Auth pump done'); - } catch (e) { - tester.printToConsole('Auth failed, retrying'); - await tester.pump(); - _handleAuthPage(); - } + final onAuthPage = authPageRobot.onAuthPage(); + if (onAuthPage) { + await authPageRobot.enterPinCode(CommonTestConstants.pin); + } + + final onAuthPageDesktop = authPageRobot.onAuthPageDesktop(); + if (onAuthPageDesktop) { + await authPageRobot.enterPassword(CommonTestConstants.pin.join("")); } - await tester.pump(); } Future handleSendResult() async { @@ -221,7 +204,7 @@ class SendPageRobot { tester.printToConsole('Has an Error in the handle: $hasError'); - int maxRetries = 20; + int maxRetries = 3; int retries = 0; while (hasError && retries < maxRetries) { diff --git a/integration_test/robots/wallet_seed_page_robot.dart b/integration_test/robots/wallet_seed_page_robot.dart index d52f3b1ec..576bff0d6 100644 --- a/integration_test/robots/wallet_seed_page_robot.dart +++ b/integration_test/robots/wallet_seed_page_robot.dart @@ -14,8 +14,13 @@ class WalletSeedPageRobot { await commonTestCases.isSpecificPage(); } - Future onNextButtonPressed() async { - await commonTestCases.tapItemByKey('wallet_seed_page_next_button_key'); + Future onSeedPageVerifyButtonPressed() async { + await commonTestCases.tapItemByKey('wallet_seed_page_verify_seed_button_key'); + await commonTestCases.defaultSleepTime(); + } + + Future onOpenWalletButtonPressed() async { + await commonTestCases.tapItemByKey('wallet_seed_page_open_wallet_button_key'); await commonTestCases.defaultSleepTime(); } @@ -38,11 +43,14 @@ class WalletSeedPageRobot { final walletSeeds = walletSeedViewModel.seed; commonTestCases.hasText(walletName); - commonTestCases.hasText(walletSeeds); + final seedList = walletSeeds.trim().split(" "); + for (final seedWord in seedList) { + commonTestCases.hasTextAtLestOnce(seedWord); + } } void confirmWalletSeedReminderDisplays() { - commonTestCases.hasText(S.current.seed_reminder); + commonTestCases.hasText(S.current.cake_seeds_save_disclaimer); } Future onSaveSeedsButtonPressed() async { diff --git a/integration_test/test_suites/confirm_seeds_flow_test.dart b/integration_test/test_suites/confirm_seeds_flow_test.dart index 2d11a2cc4..a62ce3f60 100644 --- a/integration_test/test_suites/confirm_seeds_flow_test.dart +++ b/integration_test/test_suites/confirm_seeds_flow_test.dart @@ -1,9 +1,13 @@ +import 'dart:io'; + import 'package:cake_wallet/wallet_types.g.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import '../components/common_test_cases.dart'; import '../components/common_test_constants.dart'; import '../components/common_test_flows.dart'; import '../robots/auth_page_robot.dart'; @@ -95,6 +99,10 @@ Future _confirmSeedsFlowForWalletType( await authPageRobot.enterPinCode(CommonTestConstants.pin); } + final onAuthPageDesktop = authPageRobot.onAuthPageDesktop(); + if (onAuthPageDesktop) { + await authPageRobot.enterPassword(CommonTestConstants.pin.join("")); + } await tester.pumpAndSettle(); await walletKeysAndSeedPageRobot.isWalletKeysAndSeedPage(); diff --git a/integration_test/test_suites/exchange_flow_test.dart b/integration_test/test_suites/exchange_flow_test.dart index c36ef9396..8ec2e54e7 100644 --- a/integration_test/test_suites/exchange_flow_test.dart +++ b/integration_test/test_suites/exchange_flow_test.dart @@ -56,6 +56,10 @@ void main() { await authPageRobot.enterPinCode(CommonTestConstants.pin); } + final onAuthPageDesktop = authPageRobot.onAuthPageDesktop(); + if (onAuthPageDesktop) { + await authPageRobot.enterPassword(CommonTestConstants.pin.join("")); + } await exchangeConfirmPageRobot.onSavedTradeIdButtonPressed(); await exchangeTradePageRobot.onGotItButtonPressed(); }); diff --git a/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart b/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart index a810aa722..0589d16ba 100644 --- a/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart +++ b/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart @@ -1,4 +1,7 @@ +import 'dart:io'; + import 'package:cake_wallet/wallet_types.g.dart'; +import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/integration_test_runner.sh b/integration_test_runner.sh index 34c9227c0..86e28f0b8 100755 --- a/integration_test_runner.sh +++ b/integration_test_runner.sh @@ -1,4 +1,5 @@ #!/bin/bash +export DESKTOP_FORCE_MOBILE="Y" declare -a targets declare -a passed_tests @@ -12,6 +13,10 @@ done < <(find integration_test/test_suites -name "*.dart" -type f -print0) # Run each test and collect results for target in "${targets[@]}" do + if [[ "x$REMOVE_DATA_DIRECTORY" == "xY" ]]; + then + rm -rf ~/.local/share/com.example.cake_wallet ~/Documents/cake_wallet + fi echo "Running test: $target" if flutter drive \ --driver=test_driver/integration_test.dart \ diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index e63a01f61..e2d5a953c 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -221,6 +221,7 @@ class _WalletNameFormState extends State { ), if (_walletNewVM.hasWalletPassword) ...[ TextFormField( + key: ValueKey('password'), onChanged: (value) => _walletNewVM.walletPassword = value, controller: _passwordController, textAlign: TextAlign.center, @@ -257,6 +258,7 @@ class _WalletNameFormState extends State { ), ), TextFormField( + key: ValueKey('repeat_wallet_password'), onChanged: (value) => _walletNewVM.repeatedWalletPassword = value, controller: _repeatedPasswordController, textAlign: TextAlign.center, diff --git a/lib/src/screens/restore/wallet_restore_from_keys_form.dart b/lib/src/screens/restore/wallet_restore_from_keys_form.dart index 83772f866..0c9af6910 100644 --- a/lib/src/screens/restore/wallet_restore_from_keys_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_keys_form.dart @@ -148,12 +148,14 @@ class WalletRestoreFromKeysFromState extends State { ), if (widget.displayWalletPassword) ...[Container( + key: ValueKey('password'), padding: EdgeInsets.only(top: 20.0), child: BaseTextFormField( controller: passwordTextEditingController, hintText: S.of(context).password, obscureText: true)), Container( + key: ValueKey('repeat_wallet_password'), padding: EdgeInsets.only(top: 20.0), child: BaseTextFormField( controller: repeatedPasswordTextEditingController, diff --git a/lib/src/screens/restore/wallet_restore_from_seed_form.dart b/lib/src/screens/restore/wallet_restore_from_seed_form.dart index 1684f6f92..6a493087b 100644 --- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart @@ -223,12 +223,14 @@ class WalletRestoreFromSeedFormState extends State { ), if (widget.displayWalletPassword) ...[BaseTextFormField( + key: ValueKey('password'), controller: passwordTextEditingController, hintText: S .of(context) .password, obscureText: true), BaseTextFormField( + key: ValueKey('repeat_wallet_password'), controller: repeatedPasswordTextEditingController, hintText: S .of(context) diff --git a/lib/src/screens/wallet_unlock/wallet_unlock_page.dart b/lib/src/screens/wallet_unlock/wallet_unlock_page.dart index 3e6966f9d..4afbfe2c1 100644 --- a/lib/src/screens/wallet_unlock/wallet_unlock_page.dart +++ b/lib/src/screens/wallet_unlock/wallet_unlock_page.dart @@ -170,6 +170,7 @@ class WalletUnlockPageState extends AuthPageState { SizedBox(height: 24), Form( child: TextFormField( + key: ValueKey('enter_wallet_password'), onChanged: (value) => null, controller: _passwordController, textAlign: TextAlign.center, @@ -205,6 +206,7 @@ class WalletUnlockPageState extends AuthPageState { ), ), Padding( + key: ValueKey('unlock'), padding: EdgeInsets.only(bottom: 24), child: Observer( builder: (_) => LoadingPrimaryButton( diff --git a/linux/my_application.cc b/linux/my_application.cc index 49ed75499..49f9ae139 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -46,8 +46,11 @@ static void my_application_activate(GApplication* application) { } else { gtk_window_set_title(window, "Cake Wallet"); } - - gtk_window_set_default_size(window, 1280, 720); + if (getenv("DESKTOP_FORCE_MOBILE")) { + gtk_window_set_default_size(window, 720, 1280); + } else { + gtk_window_set_default_size(window, 1280, 720); + } gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); diff --git a/model_generator.sh b/model_generator.sh index 730817c24..1443b0fc9 100755 --- a/model_generator.sh +++ b/model_generator.sh @@ -1,18 +1,24 @@ #!/bin/bash set -x -e -cd cw_core; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_evm; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_monero; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_bitcoin; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_haven; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_nano; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_bitcoin_cash; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_solana; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_tron; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_wownero; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_polygon; flutter pub get; cd .. -cd cw_ethereum; flutter pub get; cd .. -cd cw_mweb && flutter pub get && cd .. -dart run build_runner build --delete-conflicting-outputs +for cwcoin in cw_{core,evm,monero,bitcoin,haven,nano,bitcoin_cash,solana,tron,wownero} +do + if [[ "x$1" == "xasync" ]]; + then + bash -c "cd $cwcoin; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .." & + else + bash -c "cd $cwcoin; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .." + fi +done +for cwcoin in cw_{polygon,ethereum,mwebd}; +do + if [[ "x$1" == "xasync" ]]; + then + bash -c "cd $cwcoin; flutter pub get; cd .." & + else + bash -c "cd $cwcoin; flutter pub get; cd .." + fi +done +flutter pub get +dart run build_runner build --delete-conflicting-outputs diff --git a/scripts/android/.gitignore b/scripts/android/.gitignore new file mode 100644 index 000000000..f7e94b7c0 --- /dev/null +++ b/scripts/android/.gitignore @@ -0,0 +1 @@ +mwebd \ No newline at end of file diff --git a/scripts/android/build_monero_all.sh b/scripts/android/build_monero_all.sh index 261ebd560..71a6b6228 100755 --- a/scripts/android/build_monero_all.sh +++ b/scripts/android/build_monero_all.sh @@ -8,50 +8,20 @@ cd "$(dirname "$0")" NPROC="-j$(nproc)" -if [[ "x$(uname)" == "xDarwin" ]]; -then - USE_DOCKER="ON" - NPROC="-j1" -fi - ../prepare_moneroc.sh -if [[ ! "x$RUNNER_OS" == "x" ]]; -then - REMOVE_CACHES=ON -fi - -# NOTE: -j1 is intentional. Otherwise you will run into weird behaviour on macos -if [[ ! "x$USE_DOCKER" == "x" ]]; -then - for COIN in monero wownero; - do - pushd ../monero_c - docker run --platform linux/amd64 -v$HOME/.cache/ccache:/root/.ccache -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc g++ libtinfo5 gperf; ./build_single.sh ${COIN} x86_64-linux-android $NPROC" - # docker run --platform linux/amd64 -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc g++ libtinfo5 gperf; ./build_single.sh ${COIN} i686-linux-android $NPROC" - docker run --platform linux/amd64 -v$HOME/.cache/ccache:/root/.ccache -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc g++ libtinfo5 gperf; ./build_single.sh ${COIN} armv7a-linux-androideabi $NPROC" - docker run --platform linux/amd64 -v$HOME/.cache/ccache:/root/.ccache -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc g++ libtinfo5 gperf; ./build_single.sh ${COIN} aarch64-linux-android $NPROC" - popd - done -else - for COIN in monero wownero; - do - pushd ../monero_c - env -i ./build_single.sh ${COIN} x86_64-linux-android $NPROC - [[ ! "x$REMOVE_CACHES" == "x" ]] && rm -rf ${COIN}/contrib/depends/x86_64-linux-android - # ./build_single.sh ${COIN} i686-linux-android $NPROC - # [[ ! "x$REMOVE_CACHES" == "x" ]] && rm -rf ${COIN}/contrib/depends/i686-linux-android - env -i ./build_single.sh ${COIN} armv7a-linux-androideabi $NPROC - [[ ! "x$REMOVE_CACHES" == "x" ]] && rm -rf ${COIN}/contrib/depends/armv7a-linux-androideabi - env -i ./build_single.sh ${COIN} aarch64-linux-android $NPROC - [[ ! "x$REMOVE_CACHES" == "x" ]] && rm -rf ${COIN}/contrib/depends/aarch64-linux-android - - popd - unxz -f ../monero_c/release/${COIN}/x86_64-linux-android_libwallet2_api_c.so.xz - - unxz -f ../monero_c/release/${COIN}/armv7a-linux-androideabi_libwallet2_api_c.so.xz - - unxz -f ../monero_c/release/${COIN}/aarch64-linux-android_libwallet2_api_c.so.xz - [[ ! "x$REMOVE_CACHES" == "x" ]] && rm -rf ${COIN}/contrib/depends/{built,sources} - done -fi +for COIN in monero wownero; +do + pushd ../monero_c + for target in {x86_64,aarch64}-linux-android armv7a-linux-androideabi + do + if [[ -f "release/${COIN}/${target}_libwallet2_api_c.so" ]]; + then + echo "file exist, not building monero_c for ${COIN}/$target."; + else + env -i ./build_single.sh ${COIN} $target $NPROC + unxz -f ../monero_c/release/${COIN}/${target}_libwallet2_api_c.so.xz + fi + done + popd +done \ No newline at end of file diff --git a/scripts/android/build_mwebd.sh b/scripts/android/build_mwebd.sh index 4434e30f1..cd4e2c1f4 100755 --- a/scripts/android/build_mwebd.sh +++ b/scripts/android/build_mwebd.sh @@ -16,7 +16,4 @@ cd mwebd git reset --hard 555349415f76a42ec5c76152b64c4ab9aabc448f gomobile bind -target=android -androidapi 21 . mkdir -p ../../../cw_mweb/android/libs/ -mv ./mwebd.aar $_ -# cleanup: -cd .. -rm -rf mwebd \ No newline at end of file +cp ./mwebd.aar $_ \ No newline at end of file diff --git a/scripts/linux/Dockerfile.linux b/scripts/linux/Dockerfile.linux new file mode 100644 index 000000000..c8f4d3bde --- /dev/null +++ b/scripts/linux/Dockerfile.linux @@ -0,0 +1,148 @@ +# Usage: +# docker build . -f Dockerfile.linux -t ghcr.io/cake-tech/cake_wallet:main-linux +# docker push ghcr.io/cake-tech/cake_wallet:main-linux + +FROM --platform=linux/amd64 docker.io/debian:12 + +LABEL org.opencontainers.image.source=https://github.com/cake-tech/cake_wallet + +ENV GOLANG_VERSION=1.23.4 +# comes from https://developer.android.com/studio/#command-tools +ENV ANDROID_SDK_TOOLS_VERSION=11076708 +# https://developer.android.com/studio/releases/build-tools +ENV ANDROID_PLATFORM_VERSION=34 +ENV ANDROID_BUILD_TOOLS_VERSION=34.0.0 + +ENV FLUTTER_VERSION=3.24.0 + +# If we ever need to migrate the home directory... +RUN sed -i 's|^root:[^:]*:[^:]*:[^:]*:[^:]*:/root:|root:x:0:0:root:/root:|' /etc/passwd +# mkdir -p /root && rm -rf /root && cp -a /root /root +ENV HOME=/root +# Heavily inspired by cirrusci images +# https://github.com/cirruslabs/docker-images-android/blob/master/sdk/tools/Dockerfile +# https://github.com/cirruslabs/docker-images-android/blob/master/sdk/34/Dockerfile +# https://github.com/cirruslabs/docker-images-android/blob/master/sdk/34-ndk/Dockerfile +# https://github.com/cirruslabs/docker-images-flutter/blob/master/sdk/Dockerfile + +ENV ANDROID_HOME=/opt/android-sdk-linux \ + LANG=en_US.UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANGUAGE=en_US:en + +ENV ANDROID_SDK_ROOT=$ANDROID_HOME \ + PATH=${PATH}:${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tools:${ANDROID_HOME}/emulator + +RUN set -o xtrace \ + && cd /opt \ + && apt-get update \ + && apt-get upgrade -y \ + && apt-get install -y jq \ + && apt-get install -y default-jdk \ + && apt-get install -y sudo wget zip unzip git openssh-client curl bc software-properties-common build-essential ruby-full ruby-bundler libstdc++6 libpulse0 libglu1-mesa locales lcov libsqlite3-dev --no-install-recommends \ + # for x86 emulators + && apt-get install -y libxtst6 libnss3-dev libnspr4 libxss1 libatk-bridge2.0-0 libgtk-3-0 libgdk-pixbuf2.0-0 \ + && apt-get install -y -qq xxd \ + && apt-get install -y lftp \ + && apt-get install -qq -y sqlite3 libsqlite3-dev \ + # linux desktop dependencies + && apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev \ + # monero_c dependencies + && apt-get install -y ccache build-essential autoconf libtool gperf llvm \ + # extra stuff for KVM + && apt-get install -y udev qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils \ + # for linux tests + && apt-get install -y xvfb network-manager ffmpeg x11-utils \ + && rm -rf /var/lib/apt/lists/* \ + && sh -c 'echo "en_US.UTF-8 UTF-8" > /etc/locale.gen' \ + && locale-gen \ + && update-locale LANG=en_US.UTF-8 + +# install nodejs for actions +RUN apt-get update && \ + apt-get install -y curl && \ + curl -fsSL https://deb.nodesource.com/setup_23.x | bash - && \ + apt-get install -y nodejs && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +RUN wget https://go.dev/dl/go${GOLANG_VERSION}.linux-amd64.tar.gz &&\ + rm -rf /usr/local/go &&\ + tar -C /usr/local -xzf go${GOLANG_VERSION}.linux-amd64.tar.gz + +ENV PATH=${PATH}:/usr/local/go/bin:${HOME}/go/bin +ENV GOROOT=/usr/local/go +ENV GOPATH=${HOME}/go +RUN go install golang.org/x/mobile/cmd/gomobile@latest +RUN gomobile init + +RUN wget -q https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS_VERSION}_latest.zip -O android-sdk-tools.zip \ + && mkdir -p ${ANDROID_HOME}/cmdline-tools/ \ + && unzip -q android-sdk-tools.zip -d ${ANDROID_HOME}/cmdline-tools/ \ + && mv ${ANDROID_HOME}/cmdline-tools/cmdline-tools ${ANDROID_HOME}/cmdline-tools/latest \ + && chown -R root:root $ANDROID_HOME \ + && rm android-sdk-tools.zip \ + && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ + && yes | sdkmanager --licenses \ + && wget -O /usr/bin/android-wait-for-emulator https://raw.githubusercontent.com/travis-ci/travis-cookbooks/master/community-cookbooks/android-sdk/files/default/android-wait-for-emulator \ + && chmod +x /usr/bin/android-wait-for-emulator \ + && sdkmanager platform-tools \ + && mkdir -p ${HOME}/.android \ + && touch ${HOME}/.android/repositories.cfg \ + && git config --global user.email "czarek@cakewallet.com" \ + && git config --global user.name "CakeWallet CI" + +# emulator is not available on linux/arm64 (https://issuetracker.google.com/issues/227219818) +RUN if [ $(uname -m) == "x86_64" ]; then sdkmanager emulator ; fi + +# Extra dependencies to not download them for cake wallet build +RUN yes | sdkmanager \ + "platforms;android-$ANDROID_PLATFORM_VERSION" \ + "build-tools;$ANDROID_BUILD_TOOLS_VERSION" \ + "platforms;android-33" \ + "build-tools;33.0.2" \ + "build-tools;33.0.1" \ + "build-tools;33.0.0" \ + "build-tools;35.0.0" + +ENV ANDROID_NDK_VERSION=27.2.12479018 + +# Extra ndk dependency for sp_scanner +RUN yes | sdkmanager "ndk;$ANDROID_NDK_VERSION" \ + "ndk;27.0.12077973" + +# https://github.com/ReactiveCircus/android-emulator-runner dependencies for tests +RUN yes | sdkmanager "system-images;android-29;default;x86" \ + "system-images;android-29;default;x86_64" \ + "system-images;android-31;default;x86_64" \ + "platforms;android-29" + +# fake the KVM status so android emulator doesn't complain (that much) +RUN (addgroup kvm || true) && \ + adduser root kvm && \ + mkdir -p /etc/udev/rules.d/ && \ + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | tee /etc/udev/rules.d/99-kvm4all.rules + +ENV PATH=${HOME}/.cargo/bin:${PATH} + +RUN curl https://sh.rustup.rs -sSf | bash -s -- -y && \ + cargo install cargo-ndk && \ + for target in aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android x86_64-unknown-linux-gnu; \ + do \ + rustup target add --toolchain stable $target; \ + done + + +ENV HOME=${HOME} +ENV FLUTTER_HOME=${HOME}/sdks/flutter/${FLUTTER_VERSION} +ENV FLUTTER_ROOT=$FLUTTER_HOME + +ENV PATH=${PATH}:${FLUTTER_HOME}/bin:${FLUTTER_HOME}/bin/cache/dart-sdk/bin + +RUN git clone --depth 1 --branch ${FLUTTER_VERSION} https://github.com/flutter/flutter.git ${FLUTTER_HOME} + +RUN yes | flutter doctor --android-licenses \ + && flutter doctor \ + && chown -R root:root ${FLUTTER_HOME} + +RUN flutter precache diff --git a/scripts/linux/build_monero_all.sh b/scripts/linux/build_monero_all.sh index 5dc512527..558423219 100755 --- a/scripts/linux/build_monero_all.sh +++ b/scripts/linux/build_monero_all.sh @@ -1,9 +1,5 @@ #!/bin/bash - -. ./config.sh - - set -x -e cd "$(dirname "$0")" @@ -15,7 +11,15 @@ NPROC="-j$(nproc)" for COIN in monero wownero; do pushd ../monero_c - ./build_single.sh ${COIN} $(gcc -dumpmachine) $NPROC + for target in x86_64-linux-gnu + do + if [[ -f "release/${COIN}/${target}_libwallet2_api_c.so" ]]; + then + echo "file exist, not building monero_c for ${COIN}/$target."; + else + ./build_single.sh ${COIN} $target $NPROC + unxz -f ../monero_c/release/${COIN}/${target}_libwallet2_api_c.so.xz + fi + done popd - unxz -f ../monero_c/release/${COIN}/$(gcc -dumpmachine)_libwallet2_api_c.so.xz -done +done \ No newline at end of file diff --git a/scripts/prepare_moneroc.sh b/scripts/prepare_moneroc.sh index c345408dd..c0de33f6f 100755 --- a/scripts/prepare_moneroc.sh +++ b/scripts/prepare_moneroc.sh @@ -4,9 +4,9 @@ set -x -e cd "$(dirname "$0")" -if [[ ! -d "monero_c" ]]; +if [[ ! -d "monero_c/.git" ]]; then - git clone https://github.com/mrcyjanek/monero_c --branch master + git clone https://github.com/mrcyjanek/monero_c --branch master monero_c cd monero_c git checkout af5277f96073917185864d3596e82b67bee54e78 git reset --hard diff --git a/scripts/windows/.gitignore b/scripts/windows/.gitignore new file mode 100644 index 000000000..bb28076cd --- /dev/null +++ b/scripts/windows/.gitignore @@ -0,0 +1 @@ +actions-runner \ No newline at end of file diff --git a/scripts/windows/Dockerfile.windows b/scripts/windows/Dockerfile.windows new file mode 100644 index 000000000..f2a08b41c --- /dev/null +++ b/scripts/windows/Dockerfile.windows @@ -0,0 +1,68 @@ +# Usage: +# docker build . -f Dockerfile.windows -t ghcr.io/cake-tech/cake_wallet:main-windows +# docker push ghcr.io/cake-tech/cake_wallet:main-windows + +FROM mcr.microsoft.com/windows/servercore:ltsc2022 + +ENV FLUTTER_VERSION=3.24.0 +ENV GIT_VERSION=2.47.1 +ENV VS_INSTALLED_DIR="C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools" +ENV PATH="C:\Users\ContainerAdministrator\.cargo\bin;C:\ProgramData\chocolatey\bin;C:\flutter\flutter\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Users\ContainerAdministrator\AppData\Local\Microsoft\WindowsApps" +ENV RUNNER_VERSION=2.321.0 +ENV RUNNER_URL=https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-win-x64-${RUNNER_VERSION}.zip +ENV RUNNER_WORKDIR=_work + +RUN powershell -Command \ + curl.exe -L https://aka.ms/vs/17/release/vc_redist.x64.exe -o vc_redist.x64.exe ; \ + Start-Process -Wait -FilePath .\vc_redist.x64.exe -ArgumentList '/quiet', '/install' ; \ + Remove-Item -Force vc_redist.x64.exe + +RUN powershell -Command \ + $GIT_VERSION = [Environment]::GetEnvironmentVariable('GIT_VERSION'); \ + curl.exe -L https://github.com/git-for-windows/git/releases/download/v$($GIT_VERSION).windows.1/Git-$($GIT_VERSION)-64-bit.exe -o git_installer.exe ; \ + Start-Process -Wait -FilePath .\git_installer.exe -ArgumentList '/SILENT', '/NOICONS' ; \ + Remove-Item -Force git_installer.exe + +RUN powershell -NoProfile -ExecutionPolicy Bypass -Command \ + Set-ExecutionPolicy RemoteSigned -Scope Process; \ + [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; \ + Invoke-WebRequest https://chocolatey.org/install.ps1 -UseBasicP -OutFile install.ps1; \ + powershell -NoProfile -ExecutionPolicy Bypass -File install.ps1; \ + Remove-Item -Force install.ps1 + +RUN choco install -y visualstudio2022community +RUN choco install -y visualstudio2022-workload-nativedesktop +RUN choco install -y nodejs +RUN choco install -y go +RUN choco install -y 7zip + +RUN powershell -Command \ + curl.exe -L https://win.rustup.rs -o rustup-init.exe; \ + Start-Process -Wait -FilePath .\rustup-init.exe -ArgumentList "-y"; \ + Remove-Item -Force .\rustup-init.exe + +RUN powershell -Command \ + curl.exe -L https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -o C:\Windows\System32\nuget.exe + +RUN powershell -Command \ + $FLUTTER_VERSION = [Environment]::GetEnvironmentVariable('FLUTTER_VERSION'); \ + curl.exe -L https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_$($FLUTTER_VERSION)-stable.zip -o flutter.zip ; \ + 7z x flutter.zip -oC:\flutter -bsp1 -bse1 ; \ + Remove-Item -Force flutter.zip + +RUN flutter precache + +WORKDIR C:\\actions-runner + +RUN powershell -Command \ + curl.exe -L $env:RUNNER_URL -o 'actions-runner.zip'; \ + 7z x actions-runner.zip -oC:\actions-runner -bsp1 -bse1 ; \ + Remove-Item -Path 'actions-runner.zip' + +COPY actions-runner/.credentials /actions-runner/.credentials +COPY actions-runner/.credentials_rsaparams /actions-runner/.credentials_rsaparams +COPY actions-runner/.runner /actions-runner/.runner + +COPY ci_entrypoint.ps1 /actions-runner/ci_entrypoint.ps1 + +ENTRYPOINT ["powershell", "-File", "ci_entrypoint.ps1"] \ No newline at end of file diff --git a/scripts/windows/ci_entrypoint.ps1 b/scripts/windows/ci_entrypoint.ps1 new file mode 100644 index 000000000..d68d0f5ec --- /dev/null +++ b/scripts/windows/ci_entrypoint.ps1 @@ -0,0 +1,5 @@ +$runnerDir = "C:\actions-runner" +$runCmd = "$runnerDir\run.cmd" + +Write-Host "Starting the runner..." +& $runCmd \ No newline at end of file diff --git a/scripts/windows/ci_register.ps1 b/scripts/windows/ci_register.ps1 new file mode 100644 index 000000000..a39048cdb --- /dev/null +++ b/scripts/windows/ci_register.ps1 @@ -0,0 +1,30 @@ +# Variables for paths and config +$runnerDir = "C:\actions-runner" +$configCmd = "$runnerDir\config.cmd" +$runCmd = "$runnerDir\run.cmd" + +# Check required environment variables +if (-not $env:RUNNER_TOKEN) { + Write-Error "RUNNER_TOKEN is not set. Exiting." + exit 1 +} +if (-not $env:RUNNER_REPO_URL) { + Write-Error "RUNNER_REPO_URL is not set. Exiting." + exit 1 +} +$env:RUNNER_NAME = "windows-amd64-cake" +$env:RUNNER_WORKDIR = "_work" + +# Register the runner +Write-Host "Registering the runner..." +Write-Host "--url $env:RUNNER_REPO_URL" +Write-Host "--token $env:RUNNER_TOKEN" +Write-Host "--name $env:RUNNER_NAME" +Write-Host "--work $env:RUNNER_WORKDIR" + +& $configCmd --url $env:RUNNER_REPO_URL ` + --token $env:RUNNER_TOKEN ` + --name $env:RUNNER_NAME ` + --work $env:RUNNER_WORKDIR ` + --unattended ` + --replace \ No newline at end of file From f431091f5ee5dd1d945a668c787324b9d1e9fddc Mon Sep 17 00:00:00 2001 From: cyan Date: Wed, 15 Jan 2025 14:24:29 +0100 Subject: [PATCH 10/16] Hotfix CI (#1963) * [skip slack] It looks like during our sleep github decided to break artifact upload api.. * [skip slack] Name the artifact --- .github/workflows/pr_test_build_android.yml | 5 +++-- .github/workflows/pr_test_build_linux.yml | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml index 2a7eec5c9..c5f875128 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -283,6 +283,7 @@ jobs: run: rm -rf build/app/outputs/flutter-apk/test-apk/ - name: Upload Artifact to github - uses: kittaakos/upload-artifact-as-is@v0 + uses: actions/upload-artifact@v4 with: - path: ${{ github.workspace }}/build/app/outputs/flutter-apk \ No newline at end of file + path: ${{ github.workspace }}/build/app/outputs/flutter-apk + name: "android apk" \ No newline at end of file diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index 6a29e4033..345c95c4f 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -221,9 +221,10 @@ jobs: popd - name: Upload Artifact to github - uses: kittaakos/upload-artifact-as-is@v0 + uses: actions/upload-artifact@v4 with: path: ${{ github.workspace }}/build/linux/x64/release/cakewallet_linux.zip + name: cakewallet_linux - name: Prepare virtual desktop if: ${{ contains(github.event.head_commit.message, 'run tests') }} @@ -292,6 +293,7 @@ jobs: - name: Upload Artifact to github if: always() continue-on-error: true - uses: kittaakos/upload-artifact-as-is@v0 + uses: actions/upload-artifact@v4 with: path: ${{ github.workspace }}/screen_grab.mkv.gpg + name: tests_screen_grab From f072bc8dce3b94acda037449b41297d2588aa71f Mon Sep 17 00:00:00 2001 From: cyan Date: Wed, 15 Jan 2025 22:15:42 +0100 Subject: [PATCH 11/16] Pin ledger_flutter_plus dependency to fix builds (#1965) --- pubspec_base.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pubspec_base.yaml b/pubspec_base.yaml index e87b5a44e..450977a40 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -107,7 +107,10 @@ dependencies: polyseed: ^0.0.6 nostr_tools: ^1.0.9 solana: ^0.31.0+1 - ledger_flutter_plus: ^1.4.1 + ledger_flutter_plus: + git: + url: https://github.com/vespr-wallet/ledger-flutter-plus + ref: c2e341d8038f1108690ad6f80f7b4b7156aacc76 hashlib: ^1.19.2 dev_dependencies: @@ -146,6 +149,10 @@ dependency_overrides: url: https://github.com/cake-tech/bitcoin_base ref: cake-update-v9 ffi: 2.1.0 + ledger_flutter_plus: + git: + url: https://github.com/vespr-wallet/ledger-flutter-plus + ref: c2e341d8038f1108690ad6f80f7b4b7156aacc76 flutter_icons: image_path: "assets/images/app_logo.png" From 60e7dcffa957278866829b3d9f3279d452da3e67 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Wed, 15 Jan 2025 17:03:30 -0500 Subject: [PATCH 12/16] .well-known domain support (#1956) * add well-known setting [wip] * should work * fix * minor fix (tested and working) --- lib/core/backup_service.dart | 6 ++ lib/entities/parse_address_from_domain.dart | 12 +++ lib/entities/parsed_address.dart | 11 ++- lib/entities/preferences_key.dart | 1 + lib/entities/wellknown_record.dart | 92 +++++++++++++++++++ .../widgets/extract_address_from_parsed.dart | 5 + .../screens/settings/domain_lookups_page.dart | 4 + lib/store/settings_store.dart | 11 +++ .../settings/privacy_settings_view_model.dart | 6 ++ 9 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 lib/entities/wellknown_record.dart diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index f101ed7e1..03f20363d 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -293,6 +293,7 @@ class BackupService { final lookupsUnstoppableDomains = data[PreferencesKey.lookupsUnstoppableDomains] as bool?; final lookupsOpenAlias = data[PreferencesKey.lookupsOpenAlias] as bool?; final lookupsENS = data[PreferencesKey.lookupsENS] as bool?; + final lookupsWellKnown = data[PreferencesKey.lookupsWellKnown] as bool?; final syncAll = data[PreferencesKey.syncAllKey] as bool?; final syncMode = data[PreferencesKey.syncModeKey] as int?; final autoGenerateSubaddressStatus = @@ -403,6 +404,9 @@ class BackupService { if (lookupsENS != null) await _sharedPreferences.setBool(PreferencesKey.lookupsENS, lookupsENS); + if (lookupsWellKnown != null) + await _sharedPreferences.setBool(PreferencesKey.lookupsWellKnown, lookupsWellKnown); + if (syncAll != null) await _sharedPreferences.setBool(PreferencesKey.syncAllKey, syncAll); if (syncMode != null) await _sharedPreferences.setInt(PreferencesKey.syncModeKey, syncMode); @@ -542,6 +546,8 @@ class BackupService { _sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains), PreferencesKey.lookupsOpenAlias: _sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias), PreferencesKey.lookupsENS: _sharedPreferences.getBool(PreferencesKey.lookupsENS), + PreferencesKey.lookupsWellKnown: + _sharedPreferences.getBool(PreferencesKey.lookupsWellKnown), PreferencesKey.syncModeKey: _sharedPreferences.getInt(PreferencesKey.syncModeKey), PreferencesKey.syncAllKey: _sharedPreferences.getBool(PreferencesKey.syncAllKey), PreferencesKey.autoGenerateSubaddressStatusKey: diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index b13dfa9ad..9be125081 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/entities/openalias_record.dart'; import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/entities/unstoppable_domain_address.dart'; import 'package:cake_wallet/entities/emoji_string_extension.dart'; +import 'package:cake_wallet/entities/wellknown_record.dart'; import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; import 'package:cake_wallet/mastodon/mastodon_api.dart'; import 'package:cake_wallet/nostr/nostr_api.dart'; @@ -208,6 +209,17 @@ class AddressResolver { } } + // .well-known scheme: + if (text.contains('.') && text.contains('@')) { + if (settingsStore.lookupsWellKnown) { + final record = + await WellKnownRecord.fetchAddressAndName(formattedName: text, currency: currency); + if (record != null) { + return ParsedAddress.fetchWellKnownAddress(address: record.address, name: text); + } + } + } + if (!text.startsWith('@') && text.contains('@') && !text.contains('.')) { final bool isFioRegistered = await FioAddressProvider.checkAvail(text); if (isFioRegistered) { diff --git a/lib/entities/parsed_address.dart b/lib/entities/parsed_address.dart index cfd69acbe..eabc606db 100644 --- a/lib/entities/parsed_address.dart +++ b/lib/entities/parsed_address.dart @@ -12,7 +12,8 @@ enum ParseFrom { contact, mastodon, nostr, - thorChain + thorChain, + wellKnown } class ParsedAddress { @@ -142,6 +143,14 @@ class ParsedAddress { ); } + factory ParsedAddress.fetchWellKnownAddress({required String address, required String name}) { + return ParsedAddress( + addresses: [address], + name: name, + parseFrom: ParseFrom.wellKnown, + ); + } + final List addresses; final String name; final String description; diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 58a540278..4955690e2 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -76,6 +76,7 @@ class PreferencesKey { static const lookupsUnstoppableDomains = 'looks_up_unstoppable_domain'; static const lookupsOpenAlias = 'looks_up_open_alias'; static const lookupsENS = 'looks_up_ens'; + static const lookupsWellKnown = 'looks_up_well_known'; static const showCameraConsent = 'show_camera_consent'; static String moneroWalletUpdateV1Key(String name) => diff --git a/lib/entities/wellknown_record.dart b/lib/entities/wellknown_record.dart new file mode 100644 index 000000000..edc972f76 --- /dev/null +++ b/lib/entities/wellknown_record.dart @@ -0,0 +1,92 @@ +import 'dart:convert'; + +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:http/http.dart' as http; + +class WellKnownRecord { + WellKnownRecord({ + required this.address, + required this.name, + }); + + final String name; + final String address; + + static Future checkWellKnownUsername(String username, CryptoCurrency currency) async { + String jsonLocation = ""; + switch (currency) { + case CryptoCurrency.nano: + jsonLocation = "nano-currency"; + break; + // TODO: add other currencies + default: + return null; + } + + // split the string by the @ symbol: + try { + final List splitStrs = username.split("@"); + String name = splitStrs.first.toLowerCase(); + final String domain = splitStrs.last; + + if (splitStrs.length == 3) { + // for username like @alice@domain.org instead of alice@domain.org + name = splitStrs[1]; + } + + if (name.isEmpty) { + name = "_"; + } + + // lookup domain/.well-known/nano-currency.json and check if it has a nano address: + final http.Response response = await http.get( + Uri.parse("https://$domain/.well-known/$jsonLocation.json?names=$name"), + headers: {"Accept": "application/json"}, + ); + + if (response.statusCode != 200) { + return null; + } + final Map decoded = json.decode(response.body) as Map; + + // Access the first element in the names array and retrieve its address + final List names = decoded["names"] as List; + for (final dynamic item in names) { + if (item["name"].toLowerCase() == name) { + return item["address"] as String; + } + } + } catch (e) { + printV("error checking well-known username: $e"); + } + return null; + } + + static String formatDomainName(String name) { + String formattedName = name; + + if (name.contains("@")) { + formattedName = name.replaceAll("@", "."); + } + + return formattedName; + } + + static Future fetchAddressAndName({ + required String formattedName, + required CryptoCurrency currency, + }) async { + String name = formattedName; + + print("formattedName: $formattedName"); + + final address = await checkWellKnownUsername(formattedName, currency); + + if (address == null) { + return null; + } + + return WellKnownRecord(address: address, name: name); + } +} diff --git a/lib/src/screens/send/widgets/extract_address_from_parsed.dart b/lib/src/screens/send/widgets/extract_address_from_parsed.dart index 9ce3ca2b1..106be97ee 100644 --- a/lib/src/screens/send/widgets/extract_address_from_parsed.dart +++ b/lib/src/screens/send/widgets/extract_address_from_parsed.dart @@ -30,6 +30,11 @@ Future extractAddressFromParsed( content = S.of(context).extracted_address_content('${parsedAddress.name} (OpenAlias)'); address = parsedAddress.addresses.first; break; + case ParseFrom.wellKnown: + title = S.of(context).address_detected; + content = S.of(context).extracted_address_content('${parsedAddress.name} (Well-Known)'); + address = parsedAddress.addresses.first; + break; case ParseFrom.fio: title = S.of(context).address_detected; content = S.of(context).extracted_address_content('${parsedAddress.name} (FIO)'); diff --git a/lib/src/screens/settings/domain_lookups_page.dart b/lib/src/screens/settings/domain_lookups_page.dart index aa7e68cd0..0eb559817 100644 --- a/lib/src/screens/settings/domain_lookups_page.dart +++ b/lib/src/screens/settings/domain_lookups_page.dart @@ -45,6 +45,10 @@ class DomainLookupsPage extends BasePage { title: 'Ethereum Name Service', value: _privacySettingsViewModel.looksUpENS, onValueChange: (_, bool value) => _privacySettingsViewModel.setLookupsENS(value)), + SettingsSwitcherCell( + title: '.well-known', + value: _privacySettingsViewModel.looksUpWellKnown, + onValueChange: (_, bool value) => _privacySettingsViewModel.setLookupsWellKnown(value)), //if (!isHaven) it does not work correctly ], diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index aa7df4ba9..318be637e 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -115,6 +115,7 @@ abstract class SettingsStoreBase with Store { required this.lookupsUnstoppableDomains, required this.lookupsOpenAlias, required this.lookupsENS, + required this.lookupsWellKnown, required this.customBitcoinFeeRate, required this.silentPaymentsCardDisplay, required this.silentPaymentsAlwaysScan, @@ -459,6 +460,11 @@ abstract class SettingsStoreBase with Store { reaction((_) => lookupsENS, (bool looksUpENS) => _sharedPreferences.setBool(PreferencesKey.lookupsENS, looksUpENS)); + reaction( + (_) => lookupsWellKnown, + (bool looksUpWellKnown) => + _sharedPreferences.setBool(PreferencesKey.lookupsWellKnown, looksUpWellKnown)); + // secure storage keys: reaction( (_) => allowBiometricalAuthentication, @@ -772,6 +778,8 @@ abstract class SettingsStoreBase with Store { @observable bool lookupsENS; + @observable + bool lookupsWellKnown; @observable SyncMode currentSyncMode; @@ -967,6 +975,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true; final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true; final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true; + final lookupsWellKnown = sharedPreferences.getBool(PreferencesKey.lookupsWellKnown) ?? true; final customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1; final silentPaymentsCardDisplay = sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true; @@ -1245,6 +1254,7 @@ abstract class SettingsStoreBase with Store { lookupsUnstoppableDomains: lookupsUnstoppableDomains, lookupsOpenAlias: lookupsOpenAlias, lookupsENS: lookupsENS, + lookupsWellKnown: lookupsWellKnown, customBitcoinFeeRate: customBitcoinFeeRate, silentPaymentsCardDisplay: silentPaymentsCardDisplay, silentPaymentsAlwaysScan: silentPaymentsAlwaysScan, @@ -1414,6 +1424,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true; lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true; lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true; + lookupsWellKnown = sharedPreferences.getBool(PreferencesKey.lookupsWellKnown) ?? true; customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1; silentPaymentsCardDisplay = sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true; diff --git a/lib/view_model/settings/privacy_settings_view_model.dart b/lib/view_model/settings/privacy_settings_view_model.dart index eaa9f9e84..67f0d88a0 100644 --- a/lib/view_model/settings/privacy_settings_view_model.dart +++ b/lib/view_model/settings/privacy_settings_view_model.dart @@ -94,6 +94,9 @@ abstract class PrivacySettingsViewModelBase with Store { @computed bool get looksUpENS => _settingsStore.lookupsENS; + @computed + bool get looksUpWellKnown => _settingsStore.lookupsWellKnown; + bool get canUseEtherscan => _wallet.type == WalletType.ethereum; bool get canUsePolygonScan => _wallet.type == WalletType.polygon; @@ -130,6 +133,9 @@ abstract class PrivacySettingsViewModelBase with Store { @action void setLookupsENS(bool value) => _settingsStore.lookupsENS = value; + @action + void setLookupsWellKnown(bool value) => _settingsStore.lookupsWellKnown = value; + @action void setLookupsYatService(bool value) => _settingsStore.lookupsYatService = value; From c66a93580a1e32590adb8d6e6c91f8fa4b704a5b Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Thu, 16 Jan 2025 00:21:54 +0200 Subject: [PATCH 13/16] Update wellknown_record.dart [skip ci] --- lib/entities/wellknown_record.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/entities/wellknown_record.dart b/lib/entities/wellknown_record.dart index edc972f76..dbe808281 100644 --- a/lib/entities/wellknown_record.dart +++ b/lib/entities/wellknown_record.dart @@ -79,7 +79,7 @@ class WellKnownRecord { }) async { String name = formattedName; - print("formattedName: $formattedName"); + printV("formattedName: $formattedName"); final address = await checkWellKnownUsername(formattedName, currency); From 24c96edc854c612e99ab64911f1e7e48f13c8cd4 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Thu, 16 Jan 2025 19:46:53 +0200 Subject: [PATCH 14/16] unfocus fields when tapping exchange [skip ci] --- lib/src/screens/exchange/exchange_page.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 2f8e3eb5c..4fb2a2c87 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -231,6 +231,8 @@ class ExchangePage extends BasePage { key: ValueKey('exchange_page_exchange_button_key'), text: S.of(context).exchange, onPressed: () { + FocusScope.of(context).unfocus(); + if (_formKey.currentState != null && _formKey.currentState!.validate()) { if ((exchangeViewModel.depositCurrency == CryptoCurrency.xmr) && From 2fb07dd5d491bb9a220141420fa8ecb8eee2edbc Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Fri, 17 Jan 2025 16:31:57 +0200 Subject: [PATCH 15/16] unfocus fields when tapping button Minor cleanup [skip ci] --- cw_monero/lib/monero_transaction_info.dart | 25 ---------------------- lib/src/widgets/primary_button.dart | 5 ++++- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/cw_monero/lib/monero_transaction_info.dart b/cw_monero/lib/monero_transaction_info.dart index 76064ad11..0ac48dcba 100644 --- a/cw_monero/lib/monero_transaction_info.dart +++ b/cw_monero/lib/monero_transaction_info.dart @@ -1,9 +1,7 @@ import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/monero_amount_format.dart'; -import 'package:cw_core/parseBoolFromString.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/format_amount.dart'; -import 'package:cw_monero/api/transaction_history.dart'; class MoneroTransactionInfo extends TransactionInfo { MoneroTransactionInfo(this.txHash, this.height, this.direction, this.date, @@ -11,29 +9,6 @@ class MoneroTransactionInfo extends TransactionInfo { this.confirmations) : id = "${txHash}_${amount}_${accountIndex}_${addressIndex}"; - MoneroTransactionInfo.fromMap(Map map) - : id = "${map['hash']}_${map['amount']}_${map['accountIndex']}_${map['addressIndex']}", - txHash = map['hash'] as String, - height = (map['height'] ?? 0) as int, - direction = map['direction'] != null - ? parseTransactionDirectionFromNumber(map['direction'] as String) - : TransactionDirection.incoming, - date = DateTime.fromMillisecondsSinceEpoch( - (int.tryParse(map['timestamp'] as String? ?? '') ?? 0) * 1000), - isPending = parseBoolFromString(map['isPending'] as String), - amount = map['amount'] as int, - accountIndex = int.parse(map['accountIndex'] as String), - addressIndex = map['addressIndex'] as int, - confirmations = map['confirmations'] as int, - key = getTxKey((map['hash'] ?? '') as String), - fee = map['fee'] as int? ?? 0 { - additionalInfo = { - 'key': key, - 'accountIndex': accountIndex, - 'addressIndex': addressIndex - }; - } - final String id; final String txHash; final int height; diff --git a/lib/src/widgets/primary_button.dart b/lib/src/widgets/primary_button.dart index 06bfda157..d5800aa5b 100644 --- a/lib/src/widgets/primary_button.dart +++ b/lib/src/widgets/primary_button.dart @@ -91,7 +91,10 @@ class LoadingPrimaryButton extends StatelessWidget { width: double.infinity, height: 52.0, child: TextButton( - onPressed: (isLoading || isDisabled) ? null : onPressed, + onPressed: (isLoading || isDisabled) ? null : () { + FocusScope.of(context).unfocus(); + onPressed.call(); + }, style: ButtonStyle( backgroundColor: MaterialStateProperty.all(isDisabled ? color.withOpacity(0.5) : color), From aef71d16f145652febfbf16224e2142ea48d8b05 Mon Sep 17 00:00:00 2001 From: cyan Date: Mon, 20 Jan 2025 16:52:13 +0100 Subject: [PATCH 16/16] CW-860 Fix status for http auth nodes (#1943) --- cw_core/lib/node.dart | 186 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 174 insertions(+), 12 deletions(-) diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 7d0c2411f..7b3294043 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -1,13 +1,18 @@ import 'dart:io'; +import 'dart:math'; import 'package:cw_core/keyable.dart'; +import 'package:cw_core/utils/print_verbose.dart'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:hive/hive.dart'; import 'package:cw_core/hive_type_ids.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:http/io_client.dart' as ioc; +import 'dart:math' as math; +import 'package:convert/convert.dart'; +import 'package:crypto/crypto.dart' as crypto; -// import 'package:tor/tor.dart'; +import 'package:crypto/crypto.dart'; part 'node.g.dart'; @@ -170,34 +175,43 @@ class Node extends HiveObject with Keyable { } Future requestMoneroNode() async { - if (uri.toString().contains(".onion") || useSocksProxy) { + if (useSocksProxy) { return await requestNodeWithProxy(); } + + final path = '/json_rpc'; final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path); - final realm = 'monero-rpc'; final body = {'jsonrpc': '2.0', 'id': '0', 'method': 'get_info'}; + try { final authenticatingClient = HttpClient(); - authenticatingClient.badCertificateCallback = ((X509Certificate cert, String host, int port) => true); - authenticatingClient.addCredentials( - rpcUri, - realm, - HttpClientDigestCredentials(login ?? '', password ?? ''), - ); final http.Client client = ioc.IOClient(authenticatingClient); + final jsonBody = json.encode(body); + final response = await client.post( rpcUri, headers: {'Content-Type': 'application/json'}, - body: json.encode(body), + body: jsonBody, ); - client.close(); + // Check if we received a 401 Unauthorized response + if (response.statusCode == 401) { + final daemonRpc = DaemonRpc( + rpcUri.toString(), + username: login??'', + password: password??'', + ); + final response = await daemonRpc.call('get_info', {}); + return !(response['offline'] as bool); + } + + printV("node check response: ${response.body}"); if ((response.body.contains("400 Bad Request") // Some other generic error || @@ -225,7 +239,8 @@ class Node extends HiveObject with Keyable { final resBody = json.decode(response.body) as Map; return !(resBody['result']['offline'] as bool); - } catch (_) { + } catch (e) { + printV("error: $e"); return false; } } @@ -316,3 +331,150 @@ class Node extends HiveObject with Keyable { } } } + +/// https://github.com/ManyMath/digest_auth/ +/// HTTP Digest authentication. +/// +/// Adapted from https://github.com/dart-lang/http/issues/605#issue-963962341. +/// +/// Created because http_auth was not working for Monero daemon RPC responses. +class DigestAuth { + final String username; + final String password; + String? realm; + String? nonce; + String? uri; + String? qop = "auth"; + int _nonceCount = 0; + + DigestAuth(this.username, this.password); + + /// Initialize Digest parameters from the `WWW-Authenticate` header. + void initFromAuthorizationHeader(String authInfo) { + final Map? values = _splitAuthenticateHeader(authInfo); + if (values != null) { + realm = values['realm']; + // Check if the nonce has changed. + if (nonce != values['nonce']) { + nonce = values['nonce']; + _nonceCount = 0; // Reset nonce count when nonce changes. + } + } + } + + /// Generate the Digest Authorization header. + String getAuthString(String method, String uri) { + this.uri = uri; + _nonceCount++; + String cnonce = _computeCnonce(); + String nc = _formatNonceCount(_nonceCount); + + String ha1 = md5Hash("$username:$realm:$password"); + String ha2 = md5Hash("$method:$uri"); + String response = md5Hash("$ha1:$nonce:$nc:$cnonce:$qop:$ha2"); + + return 'Digest username="$username", realm="$realm", nonce="$nonce", uri="$uri", qop=$qop, nc=$nc, cnonce="$cnonce", response="$response"'; + } + + /// Helper to parse the `WWW-Authenticate` header. + Map? _splitAuthenticateHeader(String? header) { + if (header == null || !header.startsWith('Digest ')) { + return null; + } + String token = header.substring(7); // Remove 'Digest '. + final Map result = {}; + + final components = token.split(',').map((token) => token.trim()); + for (final component in components) { + final kv = component.split('='); + final key = kv[0]; + final value = kv.sublist(1).join('=').replaceAll('"', ''); + result[key] = value; + } + return result; + } + + /// Helper to compute a random cnonce. + String _computeCnonce() { + final math.Random rnd = math.Random(); + final List values = List.generate(16, (i) => rnd.nextInt(256)); + return hex.encode(values); + } + + /// Helper to format the nonce count. + String _formatNonceCount(int count) => + count.toRadixString(16).padLeft(8, '0'); + + /// Compute the MD5 hash of a string. + String md5Hash(String input) { + return md5.convert(utf8.encode(input)).toString(); + } +} + +class DaemonRpc { + final String rpcUrl; + final String username; + final String password; + + DaemonRpc(this.rpcUrl, {required this.username, required this.password}); + + /// Perform a JSON-RPC call with Digest Authentication. + Future> call( + String method, Map params) async { + final http.Client client = http.Client(); + final DigestAuth digestAuth = DigestAuth(username, password); + + // Initial request to get the `WWW-Authenticate` header. + final initialResponse = await client.post( + Uri.parse(rpcUrl), + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode({ + 'jsonrpc': '2.0', + 'id': '0', + 'method': method, + 'params': params, + }), + ); + + if (initialResponse.statusCode != 401 || + !initialResponse.headers.containsKey('www-authenticate')) { + throw Exception('Unexpected response: ${initialResponse.body}'); + } + + // Extract Digest details from `WWW-Authenticate` header. + final String authInfo = initialResponse.headers['www-authenticate']!; + digestAuth.initFromAuthorizationHeader(authInfo); + + // Create Authorization header for the second request. + String uri = Uri.parse(rpcUrl).path; + String authHeader = digestAuth.getAuthString('POST', uri); + + // Make the authenticated request. + final authenticatedResponse = await client.post( + Uri.parse(rpcUrl), + headers: { + 'Content-Type': 'application/json', + 'Authorization': authHeader, + }, + body: jsonEncode({ + 'jsonrpc': '2.0', + 'id': '0', + 'method': method, + 'params': params, + }), + ); + + if (authenticatedResponse.statusCode != 200) { + throw Exception('RPC call failed: ${authenticatedResponse.body}'); + } + + final Map result = jsonDecode(authenticatedResponse.body) as Map; + if (result['error'] != null) { + throw Exception('RPC Error: ${result['error']}'); + } + + return result['result'] as Map; + } +} \ No newline at end of file