Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-438-add-nano

This commit is contained in:
fosse 2023-08-07 11:47:35 -04:00
commit 82c7648897
58 changed files with 672 additions and 252 deletions

View file

@ -1,6 +1,8 @@
Improved edit/delete for nodes and wallets
Wallets can now be renamed
Accessibility improvements
Improve Monero wallet rescan
Additional exchange assets: SHIB, AAVE, ARB, BAT, COMP, CRO, ENS, FTM, FRAX, GUSD, GTC, GRT, LDO, NEXO, CAKE, PEPE, STORJ, TUSD, WBTC, WETH, ZRX, DYDX, STETH
Cake Pay is temporarily removed, see https://cakelabs.com/news/cake-pay-mobile-to-shut-down/
Monero background syncing! See https://guides.cakewallet.com/docs/monero/#background-syncing
Cake 2FA access control settings! See https://guides.cakewallet.com/docs/advanced-features/authentication/#cake-2fa-presets-and-access-control-settings
Support Monero node proxy
UI improvements when sending to Address Book entry
Allow renaming Monero account names
Send templates now support multiple recipients (try using to make Monero change)
Onramper improvements
Scan node QR codes (for Umbrel)

View file

@ -1,7 +1,9 @@
Improved edit/delete for nodes and wallets
Wallets can now be renamed
Accessibility improvements
Bitcoin transaction bug fixes
Improve Monero wallet rescan
Additional exchange assets: SHIB, AAVE, ARB, BAT, COMP, CRO, ENS, FTM, FRAX, GUSD, GTC, GRT, LDO, NEXO, CAKE, PEPE, STORJ, TUSD, WBTC, WETH, ZRX, DYDX, STETH
Cake Pay is temporarily removed, see https://cakelabs.com/news/cake-pay-mobile-to-shut-down/
Ethereum! Store ETH and ERC-20 tokens
Monero background syncing! See https://guides.cakewallet.com/docs/monero/#background-syncing
Cake 2FA access control settings! See https://guides.cakewallet.com/docs/advanced-features/authentication/#cake-2fa-presets-and-access-control-settings
Support Monero node proxy
UI improvements when sending to Address Book entry
Allow renaming Monero/Haven account names
Send templates now support multiple recipients (try using to make Monero change)
Onramper improvements
Scan node QR codes (for Umbrel)

View file

@ -1,7 +1,9 @@
import 'package:flutter/services.dart';
const utils = const MethodChannel('com.cake_wallet/native_utils');
void setIsAppSecureNative(bool isAppSecure) {
utils.invokeMethod<Uint8List>('setIsAppSecure', {'isAppSecure': isAppSecure});
try {
final utils = const MethodChannel('com.cake_wallet/native_utils');
utils.invokeMethod<Uint8List>('setIsAppSecure', {'isAppSecure': isAppSecure});
} catch (_) {}
}

View file

@ -134,6 +134,8 @@ PODS:
- SDWebImage (5.16.0):
- SDWebImage/Core (= 5.16.0)
- SDWebImage/Core (5.16.0)
- sensitive_clipboard (0.0.1):
- Flutter
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
@ -150,6 +152,8 @@ PODS:
- Flutter
- wakelock (0.0.1):
- Flutter
- workmanager (0.0.1):
- Flutter
DEPENDENCIES:
- barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`)
@ -173,12 +177,14 @@ DEPENDENCIES:
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- platform_device_id (from `.symlinks/plugins/platform_device_id/ios`)
- 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/ios`)
- uni_links (from `.symlinks/plugins/uni_links/ios`)
- UnstoppableDomainsResolution (~> 4.0.0)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
- workmanager (from `.symlinks/plugins/workmanager/ios`)
SPEC REPOS:
https://github.com/CocoaPods/Specs.git:
@ -235,6 +241,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/permission_handler_apple/ios"
platform_device_id:
:path: ".symlinks/plugins/platform_device_id/ios"
sensitive_clipboard:
:path: ".symlinks/plugins/sensitive_clipboard/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
@ -245,6 +253,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/url_launcher_ios/ios"
wakelock:
:path: ".symlinks/plugins/wakelock/ios"
workmanager:
:path: ".symlinks/plugins/workmanager/ios"
SPEC CHECKSUMS:
barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0
@ -270,11 +280,12 @@ SPEC CHECKSUMS:
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
platform_device_id: 81b3e2993881f87d0c82ef151dc274df4869aef5
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
SDWebImage: 2aea163b50bfcb569a2726b6a754c54a4506fcf6
sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
SwiftProtobuf: 40bd808372cb8706108f22d28f8ab4a6b9bc6989
@ -283,6 +294,7 @@ SPEC CHECKSUMS:
UnstoppableDomainsResolution: c3c67f4d0a5e2437cb00d4bd50c2e00d6e743841
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6
PODFILE CHECKSUM: 09df1114e7c360f55770d35a79356bf5446e0100

View file

@ -606,4 +606,9 @@
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
SystemCapabilities = {
com.apple.BackgroundModes = {
enabled = 1;
};
};
}

View file

@ -1,6 +1,7 @@
import UIKit
import Flutter
import UnstoppableDomainsResolution
import workmanager
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
@ -16,6 +17,15 @@ import UnstoppableDomainsResolution
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
WorkmanagerPlugin.setPluginRegistrantCallback { registry in
// Registry in this case is the FlutterEngine that is created in Workmanager's
// performFetchWithCompletionHandler or BGAppRefreshTask.
// This will make other plugins available during a background operation.
GeneratedPluginRegistrant.register(with: registry)
}
WorkmanagerPlugin.registerTask(withIdentifier: "com.fotolockr.cakewallet.monero_sync_task")
makeSecure()
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController

View file

@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.fotolockr.cakewallet.monero_sync_task</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
@ -113,6 +117,7 @@
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>

View file

@ -243,6 +243,8 @@ class BackupService {
final sortBalanceTokensBy = data[PreferencesKey.sortBalanceBy] as int?;
final pinNativeTokenAtTop = data[PreferencesKey.pinNativeTokenAtTop] as bool?;
final useEtherscan = data[PreferencesKey.useEtherscan] as bool?;
final syncAll = data[PreferencesKey.syncAllKey] as bool?;
final syncMode = data[PreferencesKey.syncModeKey] as int?;
await _sharedPreferences.setString(PreferencesKey.currentWalletName, currentWalletName);
@ -361,11 +363,11 @@ class BackupService {
if (useEtherscan != null)
await _sharedPreferences.setBool(PreferencesKey.useEtherscan, useEtherscan);
if (sortBalanceTokensBy != null)
await _sharedPreferences.setInt(PreferencesKey.sortBalanceBy, sortBalanceTokensBy);
if (syncAll != null)
await _sharedPreferences.setBool(PreferencesKey.syncAllKey, syncAll);
if (pinNativeTokenAtTop != null)
await _sharedPreferences.setBool(PreferencesKey.pinNativeTokenAtTop, pinNativeTokenAtTop);
if (syncMode != null)
await _sharedPreferences.setInt(PreferencesKey.syncModeKey, syncMode);
await preferencesFile.delete();
}
@ -516,6 +518,10 @@ class BackupService {
_sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop),
PreferencesKey.useEtherscan:
_sharedPreferences.getBool(PreferencesKey.useEtherscan),
PreferencesKey.syncModeKey:
_sharedPreferences.getInt(PreferencesKey.syncModeKey),
PreferencesKey.syncAllKey:
_sharedPreferences.getBool(PreferencesKey.syncAllKey),
};
return json.encode(preferences);

View file

@ -4,6 +4,7 @@ import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
import 'package:cake_wallet/core/yat_service.dart';
import 'package:cake_wallet/entities/background_tasks.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/receive_page_option.dart';
@ -24,6 +25,7 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
@ -245,6 +247,10 @@ Future setup({
getIt.registerSingletonAsync<SharedPreferences>(() => SharedPreferences.getInstance());
}
if (!_isSetupFinished) {
getIt.registerFactory(() => BackgroundTasks());
}
final isBitcoinBuyEnabled = (secrets.wyreSecretKey.isNotEmpty) &&
(secrets.wyreApiKey.isNotEmpty) &&
(secrets.wyreAccountId.isNotEmpty);
@ -682,8 +688,7 @@ Future setup({
return NodeListViewModel(_nodeSource, appStore);
});
getIt.registerFactory(
() => ConnectionSyncPage(getIt.get<NodeListViewModel>(), getIt.get<DashboardViewModel>()));
getIt.registerFactory(() => ConnectionSyncPage(getIt.get<DashboardViewModel>()));
getIt.registerFactory(
() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>(), getIt.get<AuthService>()));
@ -1058,5 +1063,7 @@ Future setup({
),
);
getIt.registerFactory<ManageNodesPage>(() => ManageNodesPage(getIt.get<NodeListViewModel>()));
_isSetupFinished = true;
}

View file

@ -0,0 +1,164 @@
import 'dart:io';
import 'package:cake_wallet/core/wallet_loading_service.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:workmanager/workmanager.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/di.dart';
const moneroSyncTaskKey = "com.fotolockr.cakewallet.monero_sync_task";
@pragma('vm:entry-point')
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) async {
try {
switch (task) {
case moneroSyncTaskKey:
/// The work manager runs on a separate isolate from the main flutter isolate.
/// thus we initialize app configs first; hive, getIt, etc...
await initializeAppConfigs();
final walletLoadingService = getIt.get<WalletLoadingService>();
final node = getIt.get<SettingsStore>().getCurrentNode(WalletType.monero);
final typeRaw = getIt.get<SharedPreferences>().getInt(PreferencesKey.currentWalletType);
WalletBase? wallet;
if (inputData!['sync_all'] as bool) {
/// get all Monero wallets of the user and sync them
final List<WalletListItem> moneroWallets = getIt
.get<WalletListViewModel>()
.wallets
.where((element) => element.type == WalletType.monero)
.toList();
for (int i = 0; i < moneroWallets.length; i++) {
wallet = await walletLoadingService.load(WalletType.monero, moneroWallets[i].name);
await wallet.connectToNode(node: node);
await wallet.startSync();
}
} else {
/// if the user chose to sync only active wallet
/// if the current wallet is monero; sync it only
if (typeRaw == WalletType.monero.index) {
final name =
getIt.get<SharedPreferences>().getString(PreferencesKey.currentWalletName);
wallet = await walletLoadingService.load(WalletType.monero, name!);
await wallet.connectToNode(node: node);
await wallet.startSync();
}
}
if (wallet?.syncStatus.progress() == null) {
return Future.error("No Monero wallet found");
}
for (int i = 0;; i++) {
await Future<void>.delayed(const Duration(seconds: 1));
if (wallet?.syncStatus.progress() == 1.0) {
break;
}
if (i > 600) {
return Future.error("Synchronization Timed out");
}
}
break;
}
return Future.value(true);
} catch (error, stackTrace) {
print(error);
print(stackTrace);
return Future.error(error);
}
});
}
class BackgroundTasks {
void registerSyncTask({bool changeExisting = false}) async {
try {
bool hasMonero = getIt
.get<WalletListViewModel>()
.wallets
.any((element) => element.type == WalletType.monero);
/// if its not android nor ios, or the user has no monero wallets; exit
if (!DeviceInfo.instance.isMobile || !hasMonero) {
return;
}
final settingsStore = getIt.get<SettingsStore>();
final SyncMode syncMode = settingsStore.currentSyncMode;
final bool syncAll = settingsStore.currentSyncAll;
if (syncMode.type == SyncType.disabled) {
cancelSyncTask();
return;
}
await Workmanager().initialize(
callbackDispatcher,
isInDebugMode: kDebugMode,
);
final inputData = <String, dynamic>{"sync_all": syncAll};
final constraints = Constraints(
networkType:
syncMode.type == SyncType.unobtrusive ? NetworkType.unmetered : NetworkType.connected,
requiresBatteryNotLow: syncMode.type == SyncType.unobtrusive,
requiresCharging: syncMode.type == SyncType.unobtrusive,
requiresDeviceIdle: syncMode.type == SyncType.unobtrusive,
);
if (Platform.isIOS) {
await Workmanager().registerOneOffTask(
moneroSyncTaskKey,
moneroSyncTaskKey,
initialDelay: syncMode.frequency,
existingWorkPolicy: ExistingWorkPolicy.replace,
inputData: inputData,
constraints: constraints,
);
return;
}
await Workmanager().registerPeriodicTask(
moneroSyncTaskKey,
moneroSyncTaskKey,
initialDelay: syncMode.frequency,
frequency: syncMode.frequency,
existingWorkPolicy: changeExisting ? ExistingWorkPolicy.replace : ExistingWorkPolicy.keep,
inputData: inputData,
constraints: constraints,
);
} catch (error, stackTrace) {
print(error);
print(stackTrace);
}
}
void cancelSyncTask() {
try {
Workmanager().cancelByUniqueName(moneroSyncTaskKey);
} catch (error, stackTrace) {
print(error);
print(stackTrace);
}
}
}

View file

@ -1,8 +1,7 @@
import 'package:cake_wallet/di.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cake_wallet/entities/background_tasks.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/core/wallet_loading_service.dart';
@ -24,4 +23,6 @@ Future<void> loadCurrentWallet() async {
final walletLoadingService = getIt.get<WalletLoadingService>();
final wallet = await walletLoadingService.load(type, name);
appStore.changeCurrentWallet(wallet);
getIt.get<BackgroundTasks>().registerSyncTask();
}

View file

@ -37,6 +37,8 @@ class PreferencesKey {
static const shouldShowReceiveWarning = 'should_show_receive_warning';
static const shouldShowYatPopup = 'should_show_yat_popup';
static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1';
static const syncModeKey = 'sync_mode';
static const syncAllKey = 'sync_all';
static const pinTimeOutDuration = 'pin_timeout_duration';
static const lastAuthTimeMilliseconds = 'last_auth_time_milliseconds';
static const lastPopupDate = 'last_popup_date';

View file

@ -57,97 +57,103 @@ Future<void> main() async {
return true;
};
final appDir = await getApplicationDocumentsDirectory();
await Hive.close();
Hive.init(appDir.path);
if (!Hive.isAdapterRegistered(Contact.typeId)) {
Hive.registerAdapter(ContactAdapter());
}
await initializeAppConfigs();
if (!Hive.isAdapterRegistered(Node.typeId)) {
Hive.registerAdapter(NodeAdapter());
}
if (!Hive.isAdapterRegistered(TransactionDescription.typeId)) {
Hive.registerAdapter(TransactionDescriptionAdapter());
}
if (!Hive.isAdapterRegistered(Trade.typeId)) {
Hive.registerAdapter(TradeAdapter());
}
if (!Hive.isAdapterRegistered(WalletInfo.typeId)) {
Hive.registerAdapter(WalletInfoAdapter());
}
if (!Hive.isAdapterRegistered(walletTypeTypeId)) {
Hive.registerAdapter(WalletTypeAdapter());
}
if (!Hive.isAdapterRegistered(Template.typeId)) {
Hive.registerAdapter(TemplateAdapter());
}
if (!Hive.isAdapterRegistered(ExchangeTemplate.typeId)) {
Hive.registerAdapter(ExchangeTemplateAdapter());
}
if (!Hive.isAdapterRegistered(Order.typeId)) {
Hive.registerAdapter(OrderAdapter());
}
if (!isMoneroOnly && !Hive.isAdapterRegistered(UnspentCoinsInfo.typeId)) {
Hive.registerAdapter(UnspentCoinsInfoAdapter());
}
if (!Hive.isAdapterRegistered(AnonpayInvoiceInfo.typeId)) {
Hive.registerAdapter(AnonpayInvoiceInfoAdapter());
}
final secureStorage = FlutterSecureStorage();
final transactionDescriptionsBoxKey =
await getEncryptionKey(secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
final tradesBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Trade.boxKey);
final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey);
final contacts = await Hive.openBox<Contact>(Contact.boxName);
final nodes = await Hive.openBox<Node>(Node.boxName);
final transactionDescriptions = await Hive.openBox<TransactionDescription>(
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey);
final trades = await Hive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
final orders = await Hive.openBox<Order>(Order.boxName, encryptionKey: ordersBoxKey);
final walletInfoSource = await Hive.openBox<WalletInfo>(WalletInfo.boxName);
final templates = await Hive.openBox<Template>(Template.boxName);
final exchangeTemplates = await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
final anonpayInvoiceInfo = await Hive.openBox<AnonpayInvoiceInfo>(AnonpayInvoiceInfo.boxName);
Box<UnspentCoinsInfo>? unspentCoinsInfoSource;
if (!isMoneroOnly) {
unspentCoinsInfoSource = await Hive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.boxName);
}
await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes,
walletInfoSource: walletInfoSource,
contactSource: contacts,
tradesSource: trades,
ordersSource: orders,
unspentCoinsInfoSource: unspentCoinsInfoSource,
// fiatConvertationService: fiatConvertationService,
templates: templates,
exchangeTemplates: exchangeTemplates,
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 22);
runApp(App());
}, (error, stackTrace) async {
ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stackTrace));
});
}
Future<void> initializeAppConfigs() async {
final appDir = await getApplicationDocumentsDirectory();
Hive.init(appDir.path);
if (!Hive.isAdapterRegistered(Contact.typeId)) {
Hive.registerAdapter(ContactAdapter());
}
if (!Hive.isAdapterRegistered(Node.typeId)) {
Hive.registerAdapter(NodeAdapter());
}
if (!Hive.isAdapterRegistered(TransactionDescription.typeId)) {
Hive.registerAdapter(TransactionDescriptionAdapter());
}
if (!Hive.isAdapterRegistered(Trade.typeId)) {
Hive.registerAdapter(TradeAdapter());
}
if (!Hive.isAdapterRegistered(WalletInfo.typeId)) {
Hive.registerAdapter(WalletInfoAdapter());
}
if (!Hive.isAdapterRegistered(walletTypeTypeId)) {
Hive.registerAdapter(WalletTypeAdapter());
}
if (!Hive.isAdapterRegistered(Template.typeId)) {
Hive.registerAdapter(TemplateAdapter());
}
if (!Hive.isAdapterRegistered(ExchangeTemplate.typeId)) {
Hive.registerAdapter(ExchangeTemplateAdapter());
}
if (!Hive.isAdapterRegistered(Order.typeId)) {
Hive.registerAdapter(OrderAdapter());
}
if (!isMoneroOnly && !Hive.isAdapterRegistered(UnspentCoinsInfo.typeId)) {
Hive.registerAdapter(UnspentCoinsInfoAdapter());
}
if (!Hive.isAdapterRegistered(AnonpayInvoiceInfo.typeId)) {
Hive.registerAdapter(AnonpayInvoiceInfoAdapter());
}
final secureStorage = FlutterSecureStorage();
final transactionDescriptionsBoxKey =
await getEncryptionKey(secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
final tradesBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Trade.boxKey);
final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey);
final contacts = await Hive.openBox<Contact>(Contact.boxName);
final nodes = await Hive.openBox<Node>(Node.boxName);
final transactionDescriptions = await Hive.openBox<TransactionDescription>(
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey);
final trades = await Hive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
final orders = await Hive.openBox<Order>(Order.boxName, encryptionKey: ordersBoxKey);
final walletInfoSource = await Hive.openBox<WalletInfo>(WalletInfo.boxName);
final templates = await Hive.openBox<Template>(Template.boxName);
final exchangeTemplates = await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
final anonpayInvoiceInfo = await Hive.openBox<AnonpayInvoiceInfo>(AnonpayInvoiceInfo.boxName);
Box<UnspentCoinsInfo>? unspentCoinsInfoSource;
if (!isMoneroOnly) {
unspentCoinsInfoSource = await Hive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.boxName);
}
await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes,
walletInfoSource: walletInfoSource,
contactSource: contacts,
tradesSource: trades,
ordersSource: orders,
unspentCoinsInfoSource: unspentCoinsInfoSource,
// fiatConvertationService: fiatConvertationService,
templates: templates,
exchangeTemplates: exchangeTemplates,
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 21);
}
Future<void> initialSetup(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes,

View file

@ -20,6 +20,7 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashbo
import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
@ -569,6 +570,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
),
);
case Routes.manageNodes:
return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>());
default:
return MaterialPageRoute<void>(
builder: (_) => Scaffold(

View file

@ -91,4 +91,5 @@ class Routes {
static const modify2FAPage = '/modify_2fa_page';
static const homeSettings = '/home_settings';
static const editToken = '/edit_token';
static const manageNodes = '/manage_nodes';
}

View file

@ -298,24 +298,7 @@ class _DashboardPageView extends BasePage {
}
});
final sharedPrefs = await SharedPreferences.getInstance();
final currentAppVersion =
VersionComparator.getExtendedVersionNumber(dashboardViewModel.settingsStore.appVersion);
final lastSeenAppVersion = sharedPrefs.getInt(PreferencesKey.lastSeenAppVersion);
final isNewInstall = sharedPrefs.getBool(PreferencesKey.isNewInstall);
if (currentAppVersion != lastSeenAppVersion && !isNewInstall!) {
await Future<void>.delayed(Duration(seconds: 1));
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return ReleaseNotesScreen(
title: 'Version ${dashboardViewModel.settingsStore.appVersion}');
});
sharedPrefs.setInt(PreferencesKey.lastSeenAppVersion, currentAppVersion);
} else if (isNewInstall!) {
sharedPrefs.setInt(PreferencesKey.lastSeenAppVersion, currentAppVersion);
}
_showReleaseNotesPopup(context);
var needToPresentYat = false;
var isInactive = false;
@ -341,4 +324,27 @@ class _DashboardPageView extends BasePage {
needToPresentYat = true;
});
}
void _showReleaseNotesPopup(BuildContext context) async {
final sharedPrefs = await SharedPreferences.getInstance();
final currentAppVersion =
VersionComparator.getExtendedVersionNumber(dashboardViewModel.settingsStore.appVersion);
final lastSeenAppVersion = sharedPrefs.getInt(PreferencesKey.lastSeenAppVersion);
final isNewInstall = sharedPrefs.getBool(PreferencesKey.isNewInstall);
if (currentAppVersion != lastSeenAppVersion && !isNewInstall!) {
Future<void>.delayed(Duration(seconds: 1), () {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return ReleaseNotesScreen(
title: 'Version ${dashboardViewModel.settingsStore.appVersion}');
});
});
sharedPrefs.setInt(PreferencesKey.lastSeenAppVersion, currentAppVersion);
} else if (isNewInstall!) {
sharedPrefs.setInt(PreferencesKey.lastSeenAppVersion, currentAppVersion);
}
}
}

View file

@ -1,25 +1,25 @@
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/node.dart';
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_list_row.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class ConnectionSyncPage extends BasePage {
ConnectionSyncPage(this.nodeListViewModel, this.dashboardViewModel);
ConnectionSyncPage(this.dashboardViewModel);
@override
String get title => S.current.connection_sync;
final NodeListViewModel nodeListViewModel;
final DashboardViewModel dashboardViewModel;
@override
@ -33,72 +33,39 @@ class ConnectionSyncPage extends BasePage {
title: S.current.reconnect,
handler: (context) => _presentReconnectAlert(context),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
if (dashboardViewModel.hasRescan)
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
if (dashboardViewModel.hasRescan) ...[
SettingsCellWithArrow(
title: S.current.rescan,
handler: (context) => Navigator.of(context).pushNamed(Routes.rescan),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
Semantics(
button: true,
child: NodeHeaderListRow(
title: S.of(context).add_new_node,
onTap: (_) async =>
await Navigator.of(context).pushNamed(Routes.newNode),
),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
SizedBox(height: 100),
Observer(
builder: (BuildContext context) {
return Flexible(
child: SectionStandardList(
sectionCount: 1,
context: context,
dividerPadding: EdgeInsets.symmetric(horizontal: 24),
itemCounter: (int sectionIndex) {
return nodeListViewModel.nodes.length;
},
itemBuilder: (_, sectionIndex, index) {
final node = nodeListViewModel.nodes[index];
final isSelected = node.keyIndex == nodeListViewModel.currentNode.keyIndex;
final nodeListRow = NodeListRow(
title: node.uriRaw,
node: node,
isSelected: isSelected,
onTap: (_) async {
if (isSelected) {
return;
}
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle:
S.of(context).change_current_node_title,
alertContent: nodeListViewModel
.getAlertContent(node.uriRaw),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () =>
Navigator.of(context).pop(),
actionRightButton: () async {
await nodeListViewModel.setAsCurrent(node);
Navigator.of(context).pop();
},
);
});
},
);
return nodeListRow;
},
),
);
},
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
if (DeviceInfo.instance.isMobile) ...[
Observer(builder: (context) {
return SettingsPickerCell<SyncMode>(
title: S.current.background_sync_mode,
items: SyncMode.all,
displayItem: (SyncMode syncMode) => syncMode.name,
selectedItem: dashboardViewModel.syncMode,
onItemSelected: dashboardViewModel.setSyncMode,
);
}),
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
Observer(builder: (context) {
return SettingsSwitcherCell(
title: S.current.sync_all_wallets,
value: dashboardViewModel.syncAll,
onValueChange: (_, bool value) => dashboardViewModel.setSyncAll(value),
);
}),
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
],
],
SettingsCellWithArrow(
title: S.current.manage_nodes,
handler: (context) => Navigator.of(context).pushNamed(Routes.manageNodes),
),
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
],
),
);

View file

@ -0,0 +1,85 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_list_row.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class ManageNodesPage extends BasePage {
ManageNodesPage(this.nodeListViewModel);
final NodeListViewModel nodeListViewModel;
@override
String get title => S.current.manage_nodes;
@override
Widget body(BuildContext context) {
return Padding(
padding: EdgeInsets.only(top: 10),
child: Column(
children: [
Semantics(
button: true,
child: NodeHeaderListRow(
title: S.of(context).add_new_node,
onTap: (_) async => await Navigator.of(context).pushNamed(Routes.newNode),
),
),
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
SizedBox(height: 20),
Observer(
builder: (BuildContext context) {
return Flexible(
child: SectionStandardList(
sectionCount: 1,
context: context,
dividerPadding: EdgeInsets.symmetric(horizontal: 24),
itemCounter: (int sectionIndex) {
return nodeListViewModel.nodes.length;
},
itemBuilder: (_, sectionIndex, index) {
final node = nodeListViewModel.nodes[index];
final isSelected = node.keyIndex == nodeListViewModel.currentNode.keyIndex;
final nodeListRow = NodeListRow(
title: node.uriRaw,
node: node,
isSelected: isSelected,
onTap: (_) async {
if (isSelected) {
return;
}
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title,
alertContent: nodeListViewModel.getAlertContent(node.uriRaw),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () => Navigator.of(context).pop(),
actionRightButton: () async {
await nodeListViewModel.setAsCurrent(node);
Navigator.of(context).pop();
},
);
});
},
);
return nodeListRow;
},
),
);
},
),
],
),
);
}
}

View file

@ -20,33 +20,38 @@ class OtherSettingsPage extends BasePage {
@override
Widget body(BuildContext context) {
return Observer(builder: (_) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Column(children: [
SettingsPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayPriority,
selectedItem: _otherSettingsViewModel.transactionPriority,
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
return Observer(
builder: (_) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Column(
children: [
SettingsPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayPriority,
selectedItem: _otherSettingsViewModel.transactionPriority,
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
),
if (_otherSettingsViewModel.changeRepresentativeEnabled)
SettingsCellWithArrow(
title: S.current.change_rep,
handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.changeRep),
),
SettingsCellWithArrow(
title: S.current.settings_terms_and_conditions,
handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.readDisclaimer),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
Spacer(),
SettingsVersionCell(
title: S.of(context).version(_otherSettingsViewModel.currentVersion)),
],
),
if (_otherSettingsViewModel.changeRepresentativeEnabled)
SettingsCellWithArrow(
title: S.current.change_rep,
handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.changeRep),
),
SettingsCellWithArrow(
title: S.current.settings_terms_and_conditions,
handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.readDisclaimer),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
Spacer(),
SettingsVersionCell(title: S.of(context).version(_otherSettingsViewModel.currentVersion))
]),
);
});
);
},
);
}
}

View file

@ -78,7 +78,7 @@ class SectionHeaderListRow extends StatelessWidget {
class StandardListSeparator extends StatelessWidget {
StandardListSeparator({this.padding, this.height = 1});
const StandardListSeparator({this.padding, this.height = 1});
final EdgeInsets? padding;
final double height;

View file

@ -2,10 +2,12 @@ import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
import 'package:cake_wallet/entities/background_tasks.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/pin_code_required_duration.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/sort_balance_types.dart';
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cw_core/transaction_priority.dart';
@ -34,7 +36,8 @@ class SettingsStore = SettingsStoreBase with _$SettingsStore;
abstract class SettingsStoreBase with Store {
SettingsStoreBase(
{required SharedPreferences sharedPreferences,
{required BackgroundTasks backgroundTasks,
required SharedPreferences sharedPreferences,
required bool initialShouldShowMarketPlaceInDashboard,
required FiatCurrency initialFiatCurrency,
required BalanceDisplayMode initialBalanceDisplayMode,
@ -51,6 +54,8 @@ abstract class SettingsStoreBase with Store {
required ThemeBase initialTheme,
required int initialPinLength,
required String initialLanguageCode,
required SyncMode initialSyncMode,
required bool initialSyncAll,
// required String initialCurrentLocale,
required this.appVersion,
required this.deviceName,
@ -78,6 +83,7 @@ abstract class SettingsStoreBase with Store {
TransactionPriority? initialEthereumTransactionPriority})
: nodes = ObservableMap<WalletType, Node>.of(nodes),
_sharedPreferences = sharedPreferences,
_backgroundTasks = backgroundTasks,
fiatCurrency = initialFiatCurrency,
balanceDisplayMode = initialBalanceDisplayMode,
shouldSaveRecipientAddress = initialSaveRecipientAddress,
@ -107,6 +113,8 @@ abstract class SettingsStoreBase with Store {
initialShouldRequireTOTP2FAForCreatingNewWallets,
shouldRequireTOTP2FAForAllSecurityAndBackupSettings =
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings,
currentSyncMode = initialSyncMode,
currentSyncAll = initialSyncAll,
priority = ObservableMap<WalletType, TransactionPriority>() {
//this.nodes = ObservableMap<WalletType, Node>.of(nodes);
@ -287,6 +295,18 @@ abstract class SettingsStoreBase with Store {
(BalanceDisplayMode mode) => sharedPreferences.setInt(
PreferencesKey.currentBalanceDisplayModeKey, mode.serialize()));
reaction((_) => currentSyncMode, (SyncMode syncMode) {
sharedPreferences.setInt(PreferencesKey.syncModeKey, syncMode.type.index);
_backgroundTasks.registerSyncTask(changeExisting: true);
});
reaction((_) => currentSyncAll, (bool syncAll) {
sharedPreferences.setBool(PreferencesKey.syncAllKey, syncAll);
_backgroundTasks.registerSyncTask(changeExisting: true);
});
reaction(
(_) => exchangeStatus,
(ExchangeApiMode mode) =>
@ -422,11 +442,18 @@ abstract class SettingsStoreBase with Store {
@observable
bool useEtherscan;
@observable
SyncMode currentSyncMode;
@observable
bool currentSyncAll;
String appVersion;
String deviceName;
SharedPreferences _sharedPreferences;
final SharedPreferences _sharedPreferences;
final BackgroundTasks _backgroundTasks;
ObservableMap<WalletType, Node> nodes;
@ -455,6 +482,7 @@ abstract class SettingsStoreBase with Store {
BalanceDisplayMode initialBalanceDisplayMode = BalanceDisplayMode.availableBalance,
ThemeBase? initialTheme}) async {
final sharedPreferences = await getIt.getAsync<SharedPreferences>();
final backgroundTasks = getIt.get<BackgroundTasks>();
final currentFiatCurrency = FiatCurrency.deserialize(
raw: sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey)!);
@ -603,6 +631,11 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.nano] = nanoNode;
}
final savedSyncMode = SyncMode.all.firstWhere((element) {
return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1);
});
final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true;
return SettingsStore(
sharedPreferences: sharedPreferences,
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
@ -647,6 +680,9 @@ abstract class SettingsStoreBase with Store {
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
initialEthereumTransactionPriority: ethereumTransactionPriority,
backgroundTasks: backgroundTasks,
initialSyncMode: savedSyncMode,
initialSyncAll: savedSyncAll,
shouldShowYatPopup: shouldShowYatPopup);
}

View file

@ -1,6 +1,7 @@
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/balance.dart';
@ -403,4 +404,16 @@ abstract class DashboardViewModelBase with Store {
hasBuyAction = !isHaven;
hasSellAction = !isHaven;
}
@computed
SyncMode get syncMode => settingsStore.currentSyncMode;
@action
void setSyncMode(SyncMode syncMode) => settingsStore.currentSyncMode = syncMode;
@computed
bool get syncAll => settingsStore.currentSyncAll;
@action
void setSyncAll(bool value) => settingsStore.currentSyncAll = value;
}

View file

@ -1,20 +1,19 @@
import 'package:flutter/foundation.dart';
import 'package:cake_wallet/view_model/settings/settings_list_item.dart';
import 'package:flutter/material.dart';
class ChoicesListItem<ItemType> extends SettingsListItem {
ChoicesListItem(
{required String title,
required this.selectedItem,
required this.items,
this.displayItem,
String Function(ItemType item)? displayItem,
void Function(ItemType item)? onItemSelected})
: _onItemSelected = onItemSelected,
_displayItem = displayItem,
super(title);
final ItemType selectedItem;
final List<ItemType> items;
final String Function(ItemType item)? displayItem;
final String Function(ItemType item)? _displayItem;
final void Function(ItemType item)? _onItemSelected;
void onItemSelected(dynamic item) {
@ -22,4 +21,11 @@ class ChoicesListItem<ItemType> extends SettingsListItem {
_onItemSelected?.call(item);
}
}
String displayItem(dynamic item) {
if (item is ItemType && _displayItem != null) {
return _displayItem!.call(item);
}
return item.toString();
}
}

View file

@ -0,0 +1,15 @@
enum SyncType { disabled, unobtrusive, aggressive }
class SyncMode {
SyncMode(this.name, this.type, this.frequency);
final String name;
final SyncType type;
final Duration frequency;
static final all = [
SyncMode("Disabled", SyncType.disabled, Duration.zero),
SyncMode("Unobtrusive", SyncType.unobtrusive, Duration(days: 1)),
SyncMode("Aggressive", SyncType.aggressive, Duration(hours: 6)),
];
}

View file

@ -1,4 +1,6 @@
import 'package:cake_wallet/core/wallet_creation_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/background_tasks.dart';
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
@ -71,6 +73,7 @@ abstract class WalletCreationVMBase with Store {
walletInfo.address = wallet.walletAddresses.address;
await _walletInfoSource.add(walletInfo);
_appStore.changeCurrentWallet(wallet);
getIt.get<BackgroundTasks>().registerSyncTask();
_appStore.authenticationStore.allowed();
state = ExecutedSuccessfullyState();
} catch (e) {

View file

@ -1,6 +1,5 @@
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/wallet_loading_service.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/store/app_store.dart';
@ -58,8 +57,8 @@ abstract class WalletListViewModelBase with Store {
name: info.name,
type: info.type,
key: info.key,
isCurrent: info.name == _appStore.wallet!.name &&
info.type == _appStore.wallet!.type,
isCurrent: info.name == _appStore.wallet?.name &&
info.type == _appStore.wallet?.type,
isEnabled: availableWalletTypes.contains(info.type),
),
),

View file

@ -110,13 +110,13 @@ SPEC CHECKSUMS:
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
in_app_review: a850789fad746e89bce03d4aeee8078b45a53fd0
package_info: 6eba2fd8d3371dda2d85c8db6fe97488f24b74b2
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
platform_device_id: 3e414428f45df149bbbfb623e2c0ca27c545b763
platform_device_id_macos: f763bb55f088be804d61b96eb4710b8ab6598e94
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9
PODFILE CHECKSUM: 5107934592df7813b33d744aebc8ddc6b5a5445f

View file

@ -66,6 +66,7 @@ dependencies:
# check unorm_dart for usage and for replace
permission_handler: ^10.0.0
device_display_brightness: ^0.0.6
workmanager: ^0.5.1
platform_device_id: ^1.0.1
wakelock: ^0.6.2
flutter_mailer: ^2.0.2

View file

@ -669,5 +669,6 @@
"etherscan_history": "Etherscan تاريخ",
"template_name": "اسم القالب",
"change_rep": "ﺏﻭﺪﻨﻣ ﺮﻴﻴﻐﺗ",
"change_rep_message": "؟ﻦﻴﻠﺜﻤﻤﻟﺍ ﺮﻴﻴﻐﺗ ﺪﻳﺮﺗ ﻚﻧﺃ ﺪﻛﺄﺘﻣ ﺖﻧﺃ ﻞﻫ"
"change_rep_message": "؟ﻦﻴﻠﺜﻤﻤﻟﺍ ﺮﻴﻴﻐﺗ ﺪﻳﺮﺗ ﻚﻧﺃ ﺪﻛﺄﺘﻣ ﺖﻧﺃ ﻞﻫ",
"manage_nodes": "ﺪﻘﻌﻟﺍ ﺓﺭﺍﺩﺇ"
}

View file

@ -665,5 +665,6 @@
"etherscan_history": "История на Etherscan",
"template_name": "Име на шаблон",
"change_rep": "Смяна на представител",
"change_rep_message": "Сигурни ли сте, че искате да смените представителите?"
"change_rep_message": "Сигурни ли сте, че искате да смените представителите?",
"manage_nodes": "Управление на възли"
}

View file

@ -662,6 +662,7 @@
"balance_page": "Stránka zůstatku",
"share": "Podíl",
"slidable": "Posuvné",
"manage_nodes": "Spravovat uzly",
"etherscan_history": "Historie Etherscanu",
"template_name": "Název šablony",
"change_rep": "Změna zástupce",

View file

@ -538,6 +538,8 @@
"open_gift_card": "Geschenkkarte öffnen",
"contact_support": "Support kontaktieren",
"gift_cards_unavailable": "Geschenkkarten können derzeit nur über Monero, Bitcoin und Litecoin erworben werden",
"background_sync_mode": "Hintergrundsynchronisierungsmodus",
"sync_all_wallets": "Alle Wallets synchronisieren",
"introducing_cake_pay": "Einführung von Cake Pay!",
"cake_pay_learn_more": "Kaufen und lösen Sie Geschenkkarten sofort in der App ein!\nWischen Sie von links nach rechts, um mehr zu erfahren.",
"automatic": "Automatisch",
@ -668,6 +670,7 @@
"balance_page": "Balance-Seite",
"share": "Aktie",
"slidable": "Verschiebbar",
"manage_nodes": "Knoten verwalten",
"etherscan_history": "Etherscan-Geschichte",
"template_name": "Vorlagenname",
"change_rep": "Change-Beauftragter",

View file

@ -538,6 +538,8 @@
"open_gift_card": "Open Gift Card",
"contact_support": "Contact Support",
"gift_cards_unavailable": "Gift cards are available for purchase only with Monero, Bitcoin, and Litecoin at this time",
"background_sync_mode": "Background sync mode",
"sync_all_wallets": "Sync all wallets",
"introducing_cake_pay": "Introducing Cake Pay!",
"cake_pay_learn_more": "Instantly purchase and redeem gift cards in the app!\nSwipe left to right to learn more.",
"automatic": "Automatic",
@ -668,6 +670,7 @@
"balance_page": "Balance Page",
"share": "Share",
"slidable": "Slidable",
"manage_nodes": "Manage nodes",
"etherscan_history": "Etherscan history",
"template_name": "Template Name",
"change_rep": "Change Representative",

View file

@ -538,6 +538,8 @@
"open_gift_card": "Abrir tarjeta de regalo",
"contact_support": "Contactar con Soporte",
"gift_cards_unavailable": "Las tarjetas de regalo están disponibles para comprar solo a través de Monero, Bitcoin y Litecoin en este momento",
"background_sync_mode": "Modo de sincronización en segundo plano",
"sync_all_wallets": "Sincronizar todas las billeteras",
"introducing_cake_pay": "¡Presentamos Cake Pay!",
"cake_pay_learn_more": "¡Compre y canjee tarjetas de regalo al instante en la aplicación!\nDeslice el dedo de izquierda a derecha para obtener más información.",
"automatic": "Automático",
@ -668,6 +670,7 @@
"balance_page": "Página de saldo",
"share": "Compartir",
"slidable": "deslizable",
"manage_nodes": "Administrar nodos",
"etherscan_history": "historia de etherscan",
"template_name": "Nombre de la plantilla",
"change_rep": "Representante de cambio",

View file

@ -536,8 +536,10 @@
"gift_card_is_generated": "La carte-cadeau est générée",
"open_gift_card": "Ouvrir la carte-cadeau",
"contact_support": "Contacter l'assistance",
"gift_cards": "Cartes-Cadeaux",
"gift_cards_unavailable": "Les cartes-cadeaux ne sont disponibles à l'achat que via Monero, Bitcoin et Litecoin pour le moment",
"background_sync_mode": "Mode de synchronisation en arrière-plan",
"sync_all_wallets": "Synchroniser tous les portefeuilles",
"gift_cards": "Cartes-Cadeaux",
"introducing_cake_pay": "Présentation de Cake Pay !",
"cake_pay_learn_more": "Achetez et utilisez instantanément des cartes-cadeaux dans l'application !\nBalayer de gauche à droite pour en savoir plus.",
"automatic": "Automatique",
@ -668,6 +670,7 @@
"balance_page": "Page Solde",
"share": "Partager",
"slidable": "Glissable",
"manage_nodes": "Gérer les nœuds",
"etherscan_history": "Historique d'Etherscan",
"template_name": "Nom du modèle",
"change_rep": "Changer de représentant",

View file

@ -651,5 +651,6 @@
"etherscan_history": "Etherscan tarihin kowane zamani",
"template_name": "Sunan Samfura",
"change_rep": "Canza Wakili",
"change_rep_message": "Shin kun tabbata kuna son canza wakilai?"
"change_rep_message": "Shin kun tabbata kuna son canza wakilai?",
"manage_nodes": "Sarrafa nodes"
}

View file

@ -538,6 +538,8 @@
"open_gift_card": "गिफ्ट कार्ड खोलें",
"contact_support": "सहायता से संपर्क करें",
"gift_cards_unavailable": "उपहार कार्ड इस समय केवल मोनेरो, बिटकॉइन और लिटकोइन के माध्यम से खरीदने के लिए उपलब्ध हैं",
"background_sync_mode": "बैकग्राउंड सिंक मोड",
"sync_all_wallets": "सभी वॉलेट सिंक करें",
"introducing_cake_pay": "परिचय Cake Pay!",
"cake_pay_learn_more": "ऐप में उपहार कार्ड तुरंत खरीदें और रिडीम करें!\nअधिक जानने के लिए बाएं से दाएं स्वाइप करें।",
"automatic": "स्वचालित",
@ -668,6 +670,7 @@
"balance_page": "बैलेंस पेज",
"share": "शेयर करना",
"slidable": "फिसलने लायक",
"manage_nodes": "नोड्स प्रबंधित करें",
"etherscan_history": "इथरस्कैन इतिहास",
"template_name": "टेम्पलेट नाम",
"change_rep": "प्रतिनिधि बदलें",

View file

@ -538,6 +538,8 @@
"open_gift_card": "Otvori darovnu karticu",
"contact_support": "Kontaktirajte podršku",
"gift_cards_unavailable": "Poklon kartice trenutno su dostupne za kupnju samo putem Monera, Bitcoina i Litecoina",
"background_sync_mode": "Sinkronizacija u pozadini",
"sync_all_wallets": "Sinkronizirajte sve novčanike",
"introducing_cake_pay": "Predstavljamo Cake Pay!",
"cake_pay_learn_more": "Azonnal vásárolhat és válthat be ajándékutalványokat az alkalmazásban!\nTovábbi információért csúsztassa balról jobbra az ujját.",
"automatic": "Automatski",
@ -668,6 +670,7 @@
"balance_page": "Stranica sa stanjem",
"share": "Udio",
"slidable": "Klizna",
"manage_nodes": "Upravljanje čvorovima",
"etherscan_history": "Etherscan povijest",
"template_name": "Naziv predloška",
"change_rep": "Promjena predstavnika",

View file

@ -658,6 +658,7 @@
"balance_page": "Halaman Saldo",
"share": "Membagikan",
"slidable": "Dapat digeser",
"manage_nodes": "Kelola node",
"etherscan_history": "Sejarah Etherscan",
"template_name": "Nama Templat",
"change_rep": "Ubah Perwakilan",

View file

@ -538,6 +538,8 @@
"open_gift_card": "Apri carta regalo",
"contact_support": "Contatta l'assistenza",
"gift_cards_unavailable": "Le carte regalo sono disponibili per l'acquisto solo tramite Monero, Bitcoin e Litecoin in questo momento",
"background_sync_mode": "Modalità di sincronizzazione in background",
"sync_all_wallets": "Sincronizza tutti i portafogli",
"introducing_cake_pay": "Presentazione di Cake Pay!",
"cake_pay_learn_more": "Acquista e riscatta istantaneamente carte regalo nell'app!\nScorri da sinistra a destra per saperne di più.",
"automatic": "Automatico",
@ -668,6 +670,7 @@
"balance_page": "Pagina di equilibrio",
"share": "Condividere",
"slidable": "Scorrevole",
"manage_nodes": "Gestisci i nodi",
"etherscan_history": "Storia Etherscan",
"template_name": "Nome modello",
"change_rep": "Cambia rappresentante",

View file

@ -538,6 +538,8 @@
"open_gift_card": "オープンギフトカード",
"contact_support": "サポートに連絡する",
"gift_cards_unavailable": "現時点では、ギフトカードはMonero、Bitcoin、Litecoinからのみ購入できます。",
"background_sync_mode": "バックグラウンド同期モード",
"sync_all_wallets": "すべてのウォレットを同期",
"introducing_cake_pay": "序章Cake Pay",
"cake_pay_learn_more": "アプリですぐにギフトカードを購入して引き換えましょう!\n左から右にスワイプして詳細をご覧ください。",
"automatic": "自動",
@ -668,6 +670,7 @@
"balance_page": "残高ページ",
"share": "共有",
"slidable": "スライド可能",
"manage_nodes": "ノードの管理",
"etherscan_history": "イーサスキャンの歴史",
"template_name": "テンプレート名",
"change_rep": "代表者の変更",

View file

@ -539,6 +539,8 @@
"open_gift_card": "기프트 카드 열기",
"contact_support": "지원팀에 문의",
"gift_cards_unavailable": "기프트 카드는 현재 Monero, Bitcoin 및 Litecoin을 통해서만 구매할 수 있습니다.",
"background_sync_mode": "백그라운드 동기화 모드",
"sync_all_wallets": "모든 지갑 동기화",
"introducing_cake_pay": "소개 Cake Pay!",
"cake_pay_learn_more": "앱에서 즉시 기프트 카드를 구매하고 사용하세요!\n자세히 알아보려면 왼쪽에서 오른쪽으로 스와이프하세요.",
"automatic": "자동적 인",
@ -669,6 +671,7 @@
"balance_page": "잔액 페이지",
"share": "공유하다",
"slidable": "슬라이딩 가능",
"manage_nodes": "노드 관리",
"etherscan_history": "이더스캔 역사",
"template_name": "템플릿 이름",
"change_rep": "대표자 변경",

View file

@ -668,6 +668,7 @@
"balance_page": "လက်ကျန်စာမျက်နှာ",
"share": "မျှဝေပါ။",
"slidable": "လျှောချနိုင်သည်။",
"manage_nodes": "ဆုံမှတ်များကို စီမံပါ။",
"etherscan_history": "Etherscan သမိုင်း",
"template_name": "နမူနာပုံစံ",
"change_rep": "ကိုယ်စားလှယ်ပြောင်းပါ။",

View file

@ -538,6 +538,8 @@
"open_gift_card": "Geschenkkaart openen",
"contact_support": "Contact opnemen met ondersteuning",
"gift_cards_unavailable": "Cadeaubonnen kunnen momenteel alleen worden gekocht via Monero, Bitcoin en Litecoin",
"background_sync_mode": "Achtergrondsynchronisatiemodus",
"sync_all_wallets": "Alle portemonnees synchroniseren",
"introducing_cake_pay": "Introductie van Cake Pay!",
"cake_pay_learn_more": "Koop en wissel cadeaubonnen direct in de app in!\nSwipe van links naar rechts voor meer informatie.",
"automatic": "automatisch",
@ -668,6 +670,7 @@
"balance_page": "Saldo pagina",
"share": "Deel",
"slidable": "Verschuifbaar",
"manage_nodes": "Beheer knooppunten",
"etherscan_history": "Etherscan-geschiedenis",
"template_name": "Sjabloonnaam",
"change_rep": "Vertegenwoordiger wijzigen",

View file

@ -538,6 +538,8 @@
"open_gift_card": "Otwórz kartę podarunkową",
"contact_support": "Skontaktuj się z pomocą techniczną",
"gift_cards_unavailable": "Karty podarunkowe można obecnie kupić tylko za pośrednictwem Monero, Bitcoin i Litecoin",
"background_sync_mode": "Tryb synchronizacji w tle",
"sync_all_wallets": "Synchronizuj wszystkie portfele",
"introducing_cake_pay": "Przedstawiamy Cake Pay!",
"cake_pay_learn_more": "Kupuj i wykorzystuj karty podarunkowe od razu w aplikacji!\nPrzesuń od lewej do prawej, aby dowiedzieć się więcej.",
"automatic": "Automatyczny",
@ -668,6 +670,7 @@
"balance_page": "Strona salda",
"share": "Udział",
"slidable": "Przesuwne",
"manage_nodes": "Zarządzaj węzłami",
"etherscan_history": "Historia Etherscanu",
"template_name": "Nazwa szablonu",
"change_rep": "Zmień przedstawiciela",

View file

@ -537,6 +537,8 @@
"open_gift_card": "Abrir vale-presente",
"contact_support": "Contatar Suporte",
"gift_cards_unavailable": "Os cartões-presente estão disponíveis para compra apenas através do Monero, Bitcoin e Litecoin no momento",
"background_sync_mode": "Modo de sincronização em segundo plano",
"sync_all_wallets": "Sincronize todas as carteiras",
"introducing_cake_pay": "Apresentando o Cake Pay!",
"cake_pay_learn_more": "Compre e resgate vales-presente instantaneamente no app!\nDeslize da esquerda para a direita para saber mais.",
"automatic": "Automático",
@ -667,6 +669,7 @@
"balance_page": "Página de saldo",
"share": "Compartilhar",
"slidable": "Deslizável",
"manage_nodes": "Gerenciar nós",
"etherscan_history": "história Etherscan",
"template_name": "Nome do modelo",
"change_rep": "Alterar representante",

View file

@ -538,6 +538,8 @@
"open_gift_card": "Открыть подарочную карту",
"contact_support": "Связаться со службой поддержки",
"gift_cards_unavailable": "В настоящее время подарочные карты можно приобрести только через Monero, Bitcoin и Litecoin.",
"background_sync_mode": "Режим фоновой синхронизации",
"sync_all_wallets": "Синхронизировать все кошельки",
"introducing_cake_pay": "Представляем Cake Pay!",
"cake_pay_learn_more": "Мгновенно покупайте и используйте подарочные карты в приложении!\nПроведите по экрану слева направо, чтобы узнать больше.",
"automatic": "автоматический",
@ -668,6 +670,7 @@
"balance_page": "Страница баланса",
"share": "Делиться",
"slidable": "Скользящий",
"manage_nodes": "Управление узлами",
"etherscan_history": "История Эфириума",
"template_name": "Имя Шаблона",
"change_rep": "Изменить представителя",

View file

@ -668,6 +668,7 @@
"balance_page": "หน้ายอดคงเหลือ",
"share": "แบ่งปัน",
"slidable": "เลื่อนได้",
"manage_nodes": "จัดการโหนด",
"etherscan_history": "ประวัติอีเธอร์สแกน",
"template_name": "ชื่อแม่แบบ",
"change_rep": "เปลี่ยนผู้แทน",

View file

@ -669,6 +669,7 @@
"balance_page": "Bakiye Sayfası",
"share": "Paylaşmak",
"slidable": "kaydırılabilir",
"manage_nodes": "Düğümleri yönet",
"etherscan_history": "Etherscan geçmişi",
"template_name": "şablon adı",
"change_rep": "Temsilciyi Değiştir",

View file

@ -538,6 +538,8 @@
"open_gift_card": "Відкрити подарункову картку",
"contact_support": "Звернутися до служби підтримки",
"gift_cards_unavailable": "Наразі подарункові картки можна придбати лише через Monero, Bitcoin і Litecoin",
"background_sync_mode": "Фоновий режим синхронізації",
"sync_all_wallets": "Синхронізувати всі гаманці",
"introducing_cake_pay": "Представляємо Cake Pay!",
"cake_pay_learn_more": "Миттєво купуйте та активуйте подарункові картки в додатку!\nПроведіть пальцем зліва направо, щоб дізнатися більше.",
"automatic": "Автоматичний",
@ -668,6 +670,7 @@
"balance_page": "Сторінка балансу",
"share": "Поділіться",
"slidable": "Розсувний",
"manage_nodes": "Керуйте вузлами",
"etherscan_history": "Історія Etherscan",
"template_name": "Назва шаблону",
"change_rep": "Зміна представника",

View file

@ -662,6 +662,7 @@
"balance_page": "بیلنس صفحہ",
"share": "بانٹیں",
"slidable": "سلائیڈ ایبل",
"manage_nodes": "۔ﮟﯾﺮﮐ ﻢﻈﻧ ﺎﮐ ﺱﮈﻮﻧ",
"etherscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﺮﮭﺘﯾﺍ",
"template_name": "ٹیمپلیٹ کا نام",
"change_rep": "۔ﮟﯾﺮﮐ ﻞﯾﺪﺒﺗ ﮦﺪﻨﺋﺎﻤﻧ",

View file

@ -664,6 +664,7 @@
"balance_page": "Oju-iwe iwọntunwọnsi",
"share": "Pinpin",
"slidable": "Slidable",
"manage_nodes": "Ṣakoso awọn apa",
"etherscan_history": "Etherscan itan",
"template_name": "Orukọ Awoṣe",
"change_rep": "Yi Aṣoju",

View file

@ -537,6 +537,8 @@
"open_gift_card": "打开礼品卡",
"contact_support": "联系支持",
"gift_cards_unavailable": "目前只能通过门罗币、比特币和莱特币购买礼品卡",
"background_sync_mode": "后台同步模式",
"sync_all_wallets": "同步所有钱包",
"introducing_cake_pay": "介绍 Cake Pay!",
"cake_pay_learn_more": "立即在应用中购买和兑换礼品卡!\n从左向右滑动以了解详情。",
"automatic": "自动的",
@ -667,6 +669,7 @@
"balance_page": "余额页",
"share": "分享",
"slidable": "可滑动",
"manage_nodes": "管理节点",
"etherscan_history": "以太扫描历史",
"template_name": "模板名称",
"change_rep": "变革代表",

View file

@ -14,14 +14,14 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
APP_ANDROID_TYPE=$1
MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="1.4.0"
MONERO_COM_BUILD_NUMBER=52
MONERO_COM_VERSION="1.5.0"
MONERO_COM_BUILD_NUMBER=54
MONERO_COM_BUNDLE_ID="com.monero.app"
MONERO_COM_PACKAGE="com.monero.app"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.7.0"
CAKEWALLET_BUILD_NUMBER=162
CAKEWALLET_VERSION="4.8.0"
CAKEWALLET_BUILD_NUMBER=167
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"

View file

@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
APP_IOS_TYPE=$1
MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="1.4.0"
MONERO_COM_BUILD_NUMBER=50
MONERO_COM_VERSION="1.5.0"
MONERO_COM_BUILD_NUMBER=52
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.7.0"
CAKEWALLET_BUILD_NUMBER=165
CAKEWALLET_VERSION="4.8.0"
CAKEWALLET_BUILD_NUMBER=175
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
HAVEN_NAME="Haven"

View file

@ -23,7 +23,7 @@ CONFIG_ARGS=""
case $APP_MACOS_TYPE in
$CAKEWALLET)
CONFIG_ARGS="--monero --bitcoin";; #--haven
CONFIG_ARGS="--monero --bitcoin --ethereum";; #--haven
esac
cp -rf pubspec_description.yaml pubspec.yaml

View file

@ -15,8 +15,8 @@ if [ -n "$1" ]; then
fi
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="1.0.7"
CAKEWALLET_BUILD_NUMBER=26
CAKEWALLET_VERSION="1.1.0"
CAKEWALLET_BUILD_NUMBER=28
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then

View file

@ -497,8 +497,6 @@ Future<void> generateEthereum(bool hasImplementation) async {
final outputFile = File(ethereumOutputPath);
const ethereumCommonHeaders = """
""";
const ethereumCWHeaders = """
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/crypto_amount_format.dart';
import 'package:cw_core/crypto_currency.dart';
@ -510,6 +508,9 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:hive/hive.dart';
""";
const ethereumCWHeaders = """
import 'package:cw_ethereum/ethereum_formatter.dart';
import 'package:cw_ethereum/ethereum_mnemonics.dart';
import 'package:cw_ethereum/ethereum_transaction_credentials.dart';
@ -518,7 +519,6 @@ import 'package:cw_ethereum/ethereum_wallet.dart';
import 'package:cw_ethereum/ethereum_wallet_creation_credentials.dart';
import 'package:cw_ethereum/ethereum_wallet_service.dart';
import 'package:cw_ethereum/ethereum_transaction_priority.dart';
import 'package:hive/hive.dart';
""";
const ethereumCwPart = "part 'cw_ethereum.dart';";
const ethereumContent = """