diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index 23902f110..69c632967 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -151,6 +151,7 @@ jobs: 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 ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart + echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - name: Rename app diff --git a/.gitignore b/.gitignore index f1e5b6da3..24b7291f8 100644 --- a/.gitignore +++ b/.gitignore @@ -94,6 +94,7 @@ android/app/key.jks **/tool/.evm-secrets-config.json **/tool/.ethereum-secrets-config.json **/tool/.solana-secrets-config.json +**/tool/.nano-secrets-config.json **/tool/.tron-secrets-config.json **/lib/.secrets.g.dart **/cw_evm/lib/.secrets.g.dart diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index 23207d629..57462099c 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -91,6 +91,13 @@ + + + + + + + with Serializable implemen element.tag == walletCurrency?.tag)); } catch (_) {} + // search by fullName if not found by title: + try { + return CryptoCurrency.all.firstWhere((element) => element.fullName?.toLowerCase() == name); + } catch (_) {} + if (CryptoCurrency._nameCurrencyMap[name.toLowerCase()] == null) { final s = 'Unexpected token: $name for CryptoCurrency fromString'; throw ArgumentError.value(name, 'name', s); } + return CryptoCurrency._nameCurrencyMap[name.toLowerCase()]!; } diff --git a/cw_nano/lib/banano_balance.dart b/cw_nano/lib/banano_balance.dart index b904a35cb..d766077fc 100644 --- a/cw_nano/lib/banano_balance.dart +++ b/cw_nano/lib/banano_balance.dart @@ -1,12 +1,28 @@ import 'package:cw_core/balance.dart'; import 'package:nanoutil/nanoutil.dart'; +BigInt stringAmountToBigIntBanano(String amount) { + return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerBanano)); +} + class BananoBalance extends Balance { final BigInt currentBalance; final BigInt receivableBalance; BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0); + BananoBalance.fromFormattedString( + {required String formattedCurrentBalance, required String formattedReceivableBalance}) + : currentBalance = stringAmountToBigIntBanano(formattedCurrentBalance), + receivableBalance = stringAmountToBigIntBanano(formattedReceivableBalance), + super(0, 0); + + BananoBalance.fromRawString( + {required String currentBalance, required String receivableBalance}) + : currentBalance = BigInt.parse(currentBalance), + receivableBalance = BigInt.parse(receivableBalance), + super(0, 0); + @override String get formattedAvailableBalance { return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerBanano); diff --git a/cw_nano/lib/nano_balance.dart b/cw_nano/lib/nano_balance.dart index 8b8c93b33..691b3a32d 100644 --- a/cw_nano/lib/nano_balance.dart +++ b/cw_nano/lib/nano_balance.dart @@ -1,7 +1,7 @@ import 'package:cw_core/balance.dart'; import 'package:nanoutil/nanoutil.dart'; -BigInt stringAmountToBigInt(String amount) { +BigInt stringAmountToBigIntNano(String amount) { return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerNano)); } @@ -13,8 +13,8 @@ class NanoBalance extends Balance { NanoBalance.fromFormattedString( {required String formattedCurrentBalance, required String formattedReceivableBalance}) - : currentBalance = stringAmountToBigInt(formattedCurrentBalance), - receivableBalance = stringAmountToBigInt(formattedReceivableBalance), + : currentBalance = stringAmountToBigIntNano(formattedCurrentBalance), + receivableBalance = stringAmountToBigIntNano(formattedReceivableBalance), super(0, 0); NanoBalance.fromRawString( diff --git a/cw_nano/lib/nano_client.dart b/cw_nano/lib/nano_client.dart index 064a0bdee..3b388e5e8 100644 --- a/cw_nano/lib/nano_client.dart +++ b/cw_nano/lib/nano_client.dart @@ -10,6 +10,7 @@ import 'package:nanodart/nanodart.dart'; import 'package:cw_core/node.dart'; import 'package:nanoutil/nanoutil.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:cw_nano/.secrets.g.dart' as secrets; class NanoClient { static const Map CAKE_HEADERS = { @@ -52,10 +53,19 @@ class NanoClient { } } + Map getHeaders() { + if (_node!.uri == "https://rpc.nano.to") { + return CAKE_HEADERS..addAll({ + "key": secrets.nano2ApiKey, + }); + } + return CAKE_HEADERS; + } + Future getBalance(String address) async { final response = await http.post( _node!.uri, - headers: CAKE_HEADERS, + headers: getHeaders(), body: jsonEncode( { "action": "account_balance", @@ -82,7 +92,7 @@ class NanoClient { try { final response = await http.post( _node!.uri, - headers: CAKE_HEADERS, + headers: getHeaders(), body: jsonEncode( { "action": "account_info", @@ -94,7 +104,7 @@ class NanoClient { final data = await jsonDecode(response.body); return AccountInfoResponse.fromJson(data as Map); } catch (e) { - print("error while getting account info"); + print("error while getting account info $e"); return null; } } @@ -149,7 +159,7 @@ class NanoClient { Future requestWork(String hash) async { final response = await http.post( _powNode!.uri, - headers: CAKE_HEADERS, + headers: getHeaders(), body: json.encode( { "action": "work_generate", @@ -192,7 +202,7 @@ class NanoClient { final processResponse = await http.post( _node!.uri, - headers: CAKE_HEADERS, + headers: getHeaders(), body: processBody, ); @@ -351,7 +361,7 @@ class NanoClient { }); final processResponse = await http.post( _node!.uri, - headers: CAKE_HEADERS, + headers: getHeaders(), body: processBody, ); @@ -367,7 +377,7 @@ class NanoClient { required String privateKey, }) async { final receivableResponse = await http.post(_node!.uri, - headers: CAKE_HEADERS, + headers: getHeaders(), body: jsonEncode({ "action": "receivable", "account": destinationAddress, @@ -417,7 +427,7 @@ class NanoClient { Future> fetchTransactions(String address) async { try { final response = await http.post(_node!.uri, - headers: CAKE_HEADERS, + headers: getHeaders(), body: jsonEncode({ "action": "account_history", "account": address, diff --git a/ios/Runner/InfoBase.plist b/ios/Runner/InfoBase.plist index 02365bda7..83e60b542 100644 --- a/ios/Runner/InfoBase.plist +++ b/ios/Runner/InfoBase.plist @@ -140,6 +140,16 @@ nano-wallet + + CFBundleTypeRole + Viewer + CFBundleURLName + nano-gpt + CFBundleURLSchemes + + nano-gpt + + CFBundleTypeRole Editor diff --git a/lib/di.dart b/lib/di.dart index 35da7c751..15a72e80c 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -26,6 +26,7 @@ import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; +import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart'; import 'package:cw_core/receive_page_option.dart'; @@ -266,6 +267,7 @@ Future setup({ required Box unspentCoinsInfoSource, required Box anonpayInvoiceInfoSource, required FlutterSecureStorage secureStorage, + required GlobalKey navigatorKey, }) async { _walletInfoSource = walletInfoSource; _nodeSource = nodeSource; @@ -427,68 +429,89 @@ Future setup({ ), ); - getIt.registerFactory(() { - return AuthPage(getIt.get(), + getIt.registerLazySingleton(() { + return LinkViewModel( + appStore: getIt.get(), + settingsStore: getIt.get(), + authenticationStore: getIt.get(), + navigatorKey: navigatorKey, + ); + }); + + getIt.registerFactory(instanceName: 'login', () { + return AuthPage(getIt.get(), closable: false, onAuthenticationFinished: (isAuthenticated, AuthPageState authPageState) { if (!isAuthenticated) { return; - } else { - final authStore = getIt.get(); - final appStore = getIt.get(); - final useTotp = appStore.settingsStore.useTOTP2FA; - final shouldUseTotp2FAToAccessWallets = - appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet; - if (useTotp && shouldUseTotp2FAToAccessWallets) { - authPageState.close( - route: Routes.totpAuthCodePage, - arguments: TotpAuthArgumentsModel( - isForSetup: false, - isClosable: false, - onTotpAuthenticationFinished: (bool isAuthenticatedSuccessfully, - TotpAuthCodePageState totpAuthPageState) async { - if (!isAuthenticatedSuccessfully) { - return; - } - if (appStore.wallet != null) { - authStore.allowed(); - return; - } - - totpAuthPageState.changeProcessText('Loading the wallet'); - - if (loginError != null) { - totpAuthPageState.changeProcessText('ERROR: ${loginError.toString()}'); - } - - ReactionDisposer? _reaction; - _reaction = reaction((_) => appStore.wallet, (Object? _) { - _reaction?.reaction.dispose(); - authStore.allowed(); - }); - }, - ), - ); - } else { - if (appStore.wallet != null) { - authStore.allowed(); - return; - } - - authPageState.changeProcessText('Loading the wallet'); - - if (loginError != null) { - authPageState.changeProcessText('ERROR: ${loginError.toString()}'); - } - - ReactionDisposer? _reaction; - _reaction = reaction((_) => appStore.wallet, (Object? _) { - _reaction?.reaction.dispose(); - authStore.allowed(); - }); - } } - }, closable: false); - }, instanceName: 'login'); + final authStore = getIt.get(); + final appStore = getIt.get(); + final useTotp = appStore.settingsStore.useTOTP2FA; + final shouldUseTotp2FAToAccessWallets = + appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet; + if (useTotp && shouldUseTotp2FAToAccessWallets) { + authPageState.close( + route: Routes.totpAuthCodePage, + arguments: TotpAuthArgumentsModel( + isForSetup: false, + isClosable: false, + onTotpAuthenticationFinished: + (bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuthPageState) async { + if (!isAuthenticatedSuccessfully) { + return; + } + if (appStore.wallet != null) { + authStore.allowed(); + return; + } + + totpAuthPageState.changeProcessText('Loading the wallet'); + + if (loginError != null) { + totpAuthPageState.changeProcessText('ERROR: ${loginError.toString()}'); + } + + ReactionDisposer? _reaction; + _reaction = reaction((_) => appStore.wallet, (Object? _) { + _reaction?.reaction.dispose(); + authStore.allowed(); + }); + }, + ), + ); + } else { + // wallet is already loaded: + if (appStore.wallet != null) { + // goes to the dashboard: + authStore.allowed(); + // trigger any deep links: + final linkViewModel = getIt.get(); + if (linkViewModel.currentLink != null) { + linkViewModel.handleLink(); + } + return; + } + + // load the wallet: + + authPageState.changeProcessText('Loading the wallet'); + + if (loginError != null) { + authPageState.changeProcessText('ERROR: ${loginError.toString()}'); + } + + ReactionDisposer? _reaction; + _reaction = reaction((_) => appStore.wallet, (Object? _) { + _reaction?.reaction.dispose(); + authStore.allowed(); + final linkViewModel = getIt.get(); + if (linkViewModel.currentLink != null) { + linkViewModel.handleLink(); + } + }); + } + }); + }); getIt.registerSingleton(BottomSheetServiceImpl()); @@ -851,8 +874,10 @@ Future setup({ tradesStore: getIt.get(), sendViewModel: getIt.get())); - getIt.registerFactory( - () => ExchangePage(getIt.get(), getIt.get())); + getIt.registerFactoryParam( + (PaymentRequest? paymentRequest, __) { + return ExchangePage(getIt.get(), getIt.get(), paymentRequest); + }); getIt.registerFactory(() => ExchangeConfirmPage(tradesStore: getIt.get())); diff --git a/lib/main.dart b/lib/main.dart index b2e32d7a9..2a4e12236 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/locales/locale.dart'; import 'package:cake_wallet/store/yat/yat_store.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; +import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cw_core/address_info.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/hive_type_ids.dart'; @@ -205,18 +206,20 @@ Future initialSetup( nodes: nodes, powNodes: powNodes); await setup( - walletInfoSource: walletInfoSource, - nodeSource: nodes, - powNodeSource: powNodes, - contactSource: contactSource, - tradesSource: tradesSource, - templates: templates, - exchangeTemplates: exchangeTemplates, - transactionDescriptionBox: transactionDescriptions, - ordersSource: ordersSource, - anonpayInvoiceInfoSource: anonpayInvoiceInfo, - unspentCoinsInfoSource: unspentCoinsInfoSource, - secureStorage: secureStorage); + walletInfoSource: walletInfoSource, + nodeSource: nodes, + powNodeSource: powNodes, + contactSource: contactSource, + tradesSource: tradesSource, + templates: templates, + exchangeTemplates: exchangeTemplates, + transactionDescriptionBox: transactionDescriptions, + ordersSource: ordersSource, + anonpayInvoiceInfoSource: anonpayInvoiceInfo, + unspentCoinsInfoSource: unspentCoinsInfoSource, + secureStorage: secureStorage, + navigatorKey: navigatorKey, + ); await bootstrap(navigatorKey); monero?.onStartup(); } @@ -287,6 +290,7 @@ class AppState extends State with SingleTickerProviderStateMixin { return Observer(builder: (BuildContext context) { final appStore = getIt.get(); final authService = getIt.get(); + final linkViewModel = getIt.get(); final settingsStore = appStore.settingsStore; final statusBarColor = Colors.transparent; final authenticationStore = getIt.get(); @@ -309,6 +313,7 @@ class AppState extends State with SingleTickerProviderStateMixin { authenticationStore: authenticationStore, navigatorKey: navigatorKey, authService: authService, + linkViewModel: linkViewModel, child: MaterialApp( navigatorObservers: [routeObserver], navigatorKey: navigatorKey, diff --git a/lib/router.dart b/lib/router.dart index 3032ceb6a..741597731 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -221,7 +221,8 @@ Route createRoute(RouteSettings settings) { return CupertinoPageRoute( builder: (_) => getIt.get( param1: (PinCodeState context, dynamic _) => - Navigator.of(context.context).pushNamed(Routes.restoreWalletFromHardwareWallet, arguments: false), + Navigator.of(context.context) + .pushNamed(Routes.restoreWalletFromHardwareWallet, arguments: false), ), fullscreenDialog: true, ); @@ -231,9 +232,9 @@ Route createRoute(RouteSettings settings) { builder: (_) => ConnectDevicePage( ConnectDevicePageParams( walletType: availableWalletTypes.first, - onConnectDevice: (BuildContext context, _) => - Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount, - arguments: [availableWalletTypes.first]), + onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed( + Routes.chooseHardwareWalletAccount, + arguments: [availableWalletTypes.first]), ), getIt.get(), )); @@ -243,9 +244,8 @@ Route createRoute(RouteSettings settings) { param1: (BuildContext context, WalletType type) { final arguments = ConnectDevicePageParams( walletType: type, - onConnectDevice: (BuildContext context, _) => - Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount, - arguments: [type]), + onConnectDevice: (BuildContext context, _) => Navigator.of(context) + .pushNamed(Routes.chooseHardwareWalletAccount, arguments: [type]), ); Navigator.of(context).pushNamed(Routes.connectDevices, arguments: arguments); @@ -308,8 +308,7 @@ Route createRoute(RouteSettings settings) { case Routes.bumpFeePage: return CupertinoPageRoute( fullscreenDialog: true, - builder: (_) => - getIt.get(param1: settings.arguments as TransactionInfo)); + builder: (_) => getIt.get(param1: settings.arguments as TransactionInfo)); case Routes.newSubaddress: return CupertinoPageRoute( @@ -461,7 +460,9 @@ Route createRoute(RouteSettings settings) { case Routes.exchange: return CupertinoPageRoute( - fullscreenDialog: true, builder: (_) => getIt.get()); + fullscreenDialog: true, + builder: (_) => getIt.get(param1: settings.arguments as PaymentRequest?), + ); case Routes.exchangeTemplate: return CupertinoPageRoute(builder: (_) => getIt.get()); diff --git a/lib/src/screens/dashboard/pages/cake_features_page.dart b/lib/src/screens/dashboard/pages/cake_features_page.dart index aa587a5f4..89c0435e1 100644 --- a/lib/src/screens/dashboard/pages/cake_features_page.dart +++ b/lib/src/screens/dashboard/pages/cake_features_page.dart @@ -78,6 +78,15 @@ class CakeFeaturesPage extends StatelessWidget { fit: BoxFit.cover, ), ), + const SizedBox(height: 20), + DashBoardRoundedCardWidget( + title: "NanoGPT", + subTitle: S.of(context).nanogpt_subtitle, + onTap: () => launchUrl( + Uri.https("cake.nano-gpt.com"), + mode: LaunchMode.externalApplication, + ), + ), if (dashboardViewModel.hasSilentPayments) ...[ SizedBox(height: 10), DashBoardRoundedCardWidget( diff --git a/lib/src/screens/dashboard/pages/market_place_page.dart b/lib/src/screens/dashboard/pages/market_place_page.dart new file mode 100644 index 000000000..d28048844 --- /dev/null +++ b/lib/src/screens/dashboard/pages/market_place_page.dart @@ -0,0 +1,119 @@ +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; + +class MarketPlacePage extends StatelessWidget { + MarketPlacePage({ + required this.dashboardViewModel, + required this.marketPlaceViewModel, + }); + + final DashboardViewModel dashboardViewModel; + final MarketPlaceViewModel marketPlaceViewModel; + final _scrollController = ScrollController(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: RawScrollbar( + thumbColor: Colors.white.withOpacity(0.15), + radius: Radius.circular(20), + thumbVisibility: true, + thickness: 2, + controller: _scrollController, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 50), + Text( + S.of(context).market_place, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.pageTitleTextColor, + ), + ), + Expanded( + child: ListView( + controller: _scrollController, + children: [ + // SizedBox(height: 20), + // DashBoardRoundedCardWidget( + // onTap: () => launchUrl( + // Uri.parse("https://cakelabs.com/news/cake-pay-mobile-to-shut-down/"), + // mode: LaunchMode.externalApplication, + // ), + // title: S.of(context).cake_pay_title, + // subTitle: S.of(context).cake_pay_subtitle, + // ), + SizedBox(height: 20), + DashBoardRoundedCardWidget( + title: S.of(context).cake_pay_web_cards_title, + subTitle: S.of(context).cake_pay_web_cards_subtitle, + onTap: () => _launchMarketPlaceUrl("buy.cakepay.com"), + ), + const SizedBox(height: 20), + DashBoardRoundedCardWidget( + title: "NanoGPT", + subTitle: S.of(context).nanogpt_subtitle, + onTap: () => _launchMarketPlaceUrl("cake.nano-gpt.com"), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } + + void _launchMarketPlaceUrl(String url) async { + try { + launchUrl( + Uri.https(url), + mode: LaunchMode.externalApplication, + ); + } catch (e) { + print(e); + } + } + + // TODO: Remove ionia flow/files if we will discard it + void _navigatorToGiftCardsPage(BuildContext context) { + final walletType = dashboardViewModel.type; + + switch (walletType) { + case WalletType.haven: + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).error, + alertContent: S.of(context).gift_cards_unavailable, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + break; + default: + marketPlaceViewModel.isIoniaUserAuthenticated().then((value) { + if (value) { + Navigator.pushNamed(context, Routes.ioniaManageCardsPage); + return; + } + Navigator.of(context).pushNamed(Routes.ioniaWelcomePage); + }); + } + } +} diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index c4e4aa199..e2d424fa0 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -10,6 +10,7 @@ import 'package:cake_wallet/src/widgets/add_template_button.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/debounce.dart'; +import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/wallet_type.dart'; @@ -43,7 +44,7 @@ import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart'; class ExchangePage extends BasePage { - ExchangePage(this.exchangeViewModel, this.authService) { + ExchangePage(this.exchangeViewModel, this.authService, this.initialPaymentRequest) { depositWalletName = exchangeViewModel.depositCurrency == CryptoCurrency.xmr ? exchangeViewModel.wallet.name : null; @@ -54,6 +55,7 @@ class ExchangePage extends BasePage { final ExchangeViewModel exchangeViewModel; final AuthService authService; + final PaymentRequest? initialPaymentRequest; final depositKey = GlobalKey(); final receiveKey = GlobalKey(); final _formKey = GlobalKey(); @@ -543,6 +545,12 @@ class ExchangePage extends BasePage { // amount: depositAmountController.text); }); + if (initialPaymentRequest != null) { + exchangeViewModel.receiveCurrency = CryptoCurrency.fromString(initialPaymentRequest!.scheme); + exchangeViewModel.depositAmount = initialPaymentRequest!.amount; + exchangeViewModel.receiveAddress = initialPaymentRequest!.address; + } + _isReactionsSet = true; } diff --git a/lib/src/screens/root/root.dart b/lib/src/screens/root/root.dart index e3472f510..afdd14865 100644 --- a/lib/src/screens/root/root.dart +++ b/lib/src/screens/root/root.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/payment_request.dart'; +import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; @@ -25,6 +26,7 @@ class Root extends StatefulWidget { required this.child, required this.navigatorKey, required this.authService, + required this.linkViewModel, }) : super(key: key); final AuthenticationStore authenticationStore; @@ -32,6 +34,7 @@ class Root extends StatefulWidget { final GlobalKey navigatorKey; final AuthService authService; final Widget child; + final LinkViewModel linkViewModel; @override RootState createState() => RootState(); @@ -53,7 +56,6 @@ class RootState extends State with WidgetsBindingObserver { StreamSubscription? stream; ReactionDisposer? _walletReactionDisposer; ReactionDisposer? _deepLinksReactionDisposer; - Uri? launchUri; @override void initState() { @@ -98,7 +100,7 @@ class RootState extends State with WidgetsBindingObserver { void handleDeepLinking(Uri? uri) async { if (uri == null || !mounted) return; - launchUri = uri; + widget.linkViewModel.currentLink = uri; bool requireAuth = await widget.authService.requireAuth(); @@ -112,7 +114,7 @@ class RootState extends State with WidgetsBindingObserver { (AuthenticationState state) { if (state == AuthenticationState.allowed) { if (widget.appStore.wallet == null) { - waitForWalletInstance(context, launchUri!); + waitForWalletInstance(context); } else { _navigateToDeepLinkScreen(); } @@ -150,6 +152,8 @@ class RootState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { + // this only happens when the app has been in the background for some time + // this does NOT trigger when the app is started from the "closed" state! if (_isInactive && !_postFrameCallback && _requestAuth) { _postFrameCallback = true; WidgetsBinding.instance.addPostFrameCallback((_) { @@ -158,40 +162,38 @@ class RootState extends State with WidgetsBindingObserver { arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { if (!isAuthenticatedSuccessfully) { return; + } + final useTotp = widget.appStore.settingsStore.useTOTP2FA; + final shouldUseTotp2FAToAccessWallets = + widget.appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet; + if (useTotp && shouldUseTotp2FAToAccessWallets) { + _reset(); + auth.close( + route: Routes.totpAuthCodePage, + arguments: TotpAuthArgumentsModel( + onTotpAuthenticationFinished: + (bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuth) { + if (!isAuthenticatedSuccessfully) { + return; + } + _reset(); + totpAuth.close( + route: widget.linkViewModel.getRouteToGo(), + arguments: widget.linkViewModel.getRouteArgs(), + ); + widget.linkViewModel.currentLink = null; + }, + isForSetup: false, + isClosable: false, + ), + ); } else { - final useTotp = widget.appStore.settingsStore.useTOTP2FA; - final shouldUseTotp2FAToAccessWallets = - widget.appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet; - if (useTotp && shouldUseTotp2FAToAccessWallets) { - _reset(); - auth.close( - route: Routes.totpAuthCodePage, - arguments: TotpAuthArgumentsModel( - onTotpAuthenticationFinished: - (bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuth) { - if (!isAuthenticatedSuccessfully) { - return; - } - _reset(); - totpAuth.close( - route: _getRouteToGo(), - arguments: - isWalletConnectLink ? launchUri : PaymentRequest.fromUri(launchUri), - ); - launchUri = null; - }, - isForSetup: false, - isClosable: false, - ), - ); - } else { - _reset(); - auth.close( - route: _getRouteToGo(), - arguments: isWalletConnectLink ? launchUri : PaymentRequest.fromUri(launchUri), - ); - launchUri = null; - } + _reset(); + auth.close( + route: widget.linkViewModel.getRouteToGo(), + arguments: widget.linkViewModel.getRouteArgs(), + ); + widget.linkViewModel.currentLink = null; } }, ); @@ -216,36 +218,7 @@ class RootState extends State with WidgetsBindingObserver { _isInactiveController.add(value); } - bool _isValidPaymentUri() => launchUri?.path.isNotEmpty ?? false; - - bool get isWalletConnectLink => launchUri?.authority == 'wc'; - - String? _getRouteToGo() { - if (isWalletConnectLink) { - if (isEVMCompatibleChain(widget.appStore.wallet!.type)) { - _nonETHWalletErrorToast(S.current.switchToEVMCompatibleWallet); - return null; - } - return Routes.walletConnectConnectionsListing; - } else if (_isValidPaymentUri()) { - return Routes.send; - } else { - return null; - } - } - - Future _nonETHWalletErrorToast(String message) async { - Fluttertoast.showToast( - msg: message, - toastLength: Toast.LENGTH_LONG, - gravity: ToastGravity.SNACKBAR, - backgroundColor: Colors.black, - textColor: Colors.white, - fontSize: 16.0, - ); - } - - void waitForWalletInstance(BuildContext context, Uri tempLaunchUri) { + void waitForWalletInstance(BuildContext context) { WidgetsBinding.instance.addPostFrameCallback((_) { if (context.mounted) { _walletReactionDisposer = reaction( @@ -263,14 +236,6 @@ class RootState extends State with WidgetsBindingObserver { } void _navigateToDeepLinkScreen() { - if (_getRouteToGo() != null) { - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.navigatorKey.currentState?.pushNamed( - _getRouteToGo()!, - arguments: isWalletConnectLink ? launchUri : PaymentRequest.fromUri(launchUri), - ); - launchUri = null; - }); - } + widget.linkViewModel.handleLink(); } } diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index c8f3aa320..bdfc0656f 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -36,6 +36,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:url_launcher/url_launcher.dart'; class SendPage extends BasePage { SendPage({ @@ -425,12 +427,10 @@ class SendPage extends BasePage { } reaction((_) => sendViewModel.state, (ExecutionState state) { - if (dialogContext != null && dialogContext?.mounted == true) { Navigator.of(dialogContext!).pop(); } - if (state is FailureState) { WidgetsBinding.instance.addPostFrameCallback((_) { showPopUp( @@ -465,10 +465,10 @@ class SendPage extends BasePage { outputs: sendViewModel.outputs, rightButtonText: S.of(_dialogContext).send, leftButtonText: S.of(_dialogContext).cancel, - actionRightButton: () { + actionRightButton: () async { Navigator.of(_dialogContext).pop(); sendViewModel.commitTransaction(); - showPopUp( + await showPopUp( context: context, builder: (BuildContext _dialogContext) { return Observer(builder: (_) { @@ -493,7 +493,7 @@ class SendPage extends BasePage { ? '\n${S.of(_dialogContext).add_contact_to_address_book}' : ''; - final alertContent = + String alertContent = "$successMessage$waitMessage$newContactMessage"; if (newContactAddress != null) { @@ -516,6 +516,10 @@ class SendPage extends BasePage { newContactAddress = null; }); } else { + if (initialPaymentRequest?.callbackMessage?.isNotEmpty ?? + false) { + alertContent = initialPaymentRequest!.callbackMessage!; + } return AlertWithOneAction( alertTitle: '', alertContent: alertContent, @@ -530,6 +534,20 @@ class SendPage extends BasePage { return Offstage(); }); }); + if (state is TransactionCommitted) { + if (initialPaymentRequest?.callbackUrl?.isNotEmpty ?? false) { + // wait a second so it's not as jarring: + await Future.delayed(Duration(seconds: 1)); + try { + launchUrl( + Uri.parse(initialPaymentRequest!.callbackUrl!), + mode: LaunchMode.externalApplication, + ); + } catch (e) { + print(e); + } + } + } }, actionLeftButton: () => Navigator.of(_dialogContext).pop()); }); diff --git a/lib/src/screens/subaddress/address_edit_or_create_page.dart b/lib/src/screens/subaddress/address_edit_or_create_page.dart index 750af846e..e067c78d0 100644 --- a/lib/src/screens/subaddress/address_edit_or_create_page.dart +++ b/lib/src/screens/subaddress/address_edit_or_create_page.dart @@ -80,4 +80,4 @@ class AddressEditOrCreatePage extends BasePage { _isEffectsInstalled = true; } -} +} \ No newline at end of file diff --git a/lib/utils/payment_request.dart b/lib/utils/payment_request.dart index 00093b413..fe0ecf605 100644 --- a/lib/utils/payment_request.dart +++ b/lib/utils/payment_request.dart @@ -1,19 +1,29 @@ +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/nano/nano.dart'; class PaymentRequest { - PaymentRequest(this.address, this.amount, this.note, this.scheme); + PaymentRequest(this.address, this.amount, this.note, this.scheme, {this.callbackUrl, this.callbackMessage}); factory PaymentRequest.fromUri(Uri? uri) { var address = ""; var amount = ""; var note = ""; var scheme = ""; + String? callbackUrl; + String? callbackMessage; if (uri != null) { - address = uri.path; + address = uri.queryParameters['address'] ?? uri.path; amount = uri.queryParameters['tx_amount'] ?? uri.queryParameters['amount'] ?? ""; note = uri.queryParameters['tx_description'] ?? uri.queryParameters['message'] ?? ""; scheme = uri.scheme; + callbackUrl = uri.queryParameters['callback']; + callbackMessage = uri.queryParameters['callbackMessage']; + } + + if (scheme == "nano-gpt") { + // treat as nano so filling out the address works: + scheme = "nano"; } if (nano != null) { @@ -26,11 +36,20 @@ class PaymentRequest { } } - return PaymentRequest(address, amount, note, scheme); + return PaymentRequest( + address, + amount, + note, + scheme, + callbackUrl: callbackUrl, + callbackMessage: callbackMessage, + ); } final String address; final String amount; final String note; final String scheme; + final String? callbackUrl; + final String? callbackMessage; } diff --git a/lib/view_model/link_view_model.dart b/lib/view_model/link_view_model.dart new file mode 100644 index 000000000..714b57e53 --- /dev/null +++ b/lib/view_model/link_view_model.dart @@ -0,0 +1,118 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:cake_wallet/store/authentication_store.dart'; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/utils/payment_request.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:mobx/mobx.dart'; + +part 'link_view_model.g.dart'; + +class LinkViewModel = LinkViewModelBase with _$LinkViewModel; + +abstract class LinkViewModelBase with Store { + LinkViewModelBase({ + required this.settingsStore, + required this.appStore, + required this.authenticationStore, + required this.navigatorKey, + }) {} + + final SettingsStore settingsStore; + final AppStore appStore; + final AuthenticationStore authenticationStore; + final GlobalKey navigatorKey; + Uri? currentLink; + + bool get _isValidPaymentUri => currentLink?.path.isNotEmpty ?? false; + bool get isWalletConnectLink => currentLink?.authority == 'wc'; + bool get isNanoGptLink => currentLink?.scheme == 'nano-gpt'; + + String? getRouteToGo() { + if (isWalletConnectLink) { + if (!isEVMCompatibleChain(appStore.wallet!.type)) { + _errorToast(S.current.switchToEVMCompatibleWallet); + return null; + } + return Routes.walletConnectConnectionsListing; + } + + if (authenticationStore.state == AuthenticationState.uninitialized) { + return null; + } + + if (isNanoGptLink) { + switch (currentLink?.authority ?? '') { + case "exchange": + return Routes.exchange; + case "send": + return Routes.send; + case "buy": + return Routes.buySellPage; + } + } + + if (_isValidPaymentUri) { + return Routes.send; + } + + return null; + } + + dynamic getRouteArgs() { + if (isWalletConnectLink) { + return currentLink; + } + + if (isNanoGptLink) { + switch (currentLink?.authority ?? '') { + case "exchange": + case "send": + return PaymentRequest.fromUri(currentLink); + case "buy": + return true; + } + } + + if (_isValidPaymentUri) { + return PaymentRequest.fromUri(currentLink); + } + + return null; + } + + Future _errorToast(String message, {double fontSize = 16}) async { + Fluttertoast.showToast( + msg: message, + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.SNACKBAR, + backgroundColor: Colors.black, + textColor: Colors.white, + fontSize: fontSize, + ); + } + + Future handleLink() async { + String? route = getRouteToGo(); + dynamic args = getRouteArgs(); + if (route != null) { + if (appStore.wallet == null) { + return; + } + + if (isNanoGptLink) { + if (route == Routes.buySellPage || route == Routes.exchange) { + await _errorToast(S.current.nano_gpt_thanks_message, fontSize: 14); + } + } + currentLink = null; + navigatorKey.currentState?.pushNamed( + route, + arguments: args, + ); + } + } +} diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 551e61706..8f6ae476a 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "يجب أن تكون قيمة المبلغ أكبر من أو تساوي ${minAmount} ${fiatCurrency}", "more_options": "المزيد من الخيارات", "name": "ﻢﺳﺍ", + "nano_gpt_thanks_message": "شكرا لاستخدام nanogpt! تذكر أن تعود إلى المتصفح بعد اكتمال معاملتك!", + "nanogpt_subtitle": "جميع النماذج الأحدث (GPT-4 ، Claude). \\ nno اشتراك ، ادفع مع Crypto.", "nano_current_rep": "الممثل الحالي", "nano_pick_new_rep": "اختر ممثلًا جديدًا", "narrow": "ضيق", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 01daa382e..c8cf62be2 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "Сумата трябва да бъде най-малко ${minAmount} ${fiatCurrency}", "more_options": "Още настройки", "name": "Име", + "nano_gpt_thanks_message": "Благодаря, че използвахте Nanogpt! Не забравяйте да се върнете обратно към браузъра, след като транзакцията ви приключи!", + "nanogpt_subtitle": "Всички най-нови модели (GPT-4, Claude). \\ Nno абонамент, платете с Crypto.", "nano_current_rep": "Настоящ представител", "nano_pick_new_rep": "Изберете нов представител", "narrow": "Тесен", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 4aed0eaea..524bc5913 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "Částka musí být větší nebo rovna ${minAmount} ${fiatCurrency}", "more_options": "Více možností", "name": "název", + "nano_gpt_thanks_message": "Děkujeme za používání Nanogpt! Nezapomeňte se po dokončení transakce vydat zpět do prohlížeče!", + "nanogpt_subtitle": "Všechny nejnovější modely (GPT-4, Claude). \\ Nno předplatné, plaťte krypto.", "nano_current_rep": "Současný zástupce", "nano_pick_new_rep": "Vyberte nového zástupce", "narrow": "Úzký", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 5ae922ca2..a9987f3a8 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "Der Wert des Betrags muss größer oder gleich ${minAmount} ${fiatCurrency} sein", "more_options": "Weitere Optionen", "name": "Name", + "nano_gpt_thanks_message": "Danke, dass du Nanogpt benutzt hast! Denken Sie daran, nach Abschluss Ihrer Transaktion zurück zum Browser zu gehen!", + "nanogpt_subtitle": "Alle neuesten Modelle (GPT-4, Claude).", "nano_current_rep": "Aktueller Vertreter", "nano_pick_new_rep": "Wählen Sie einen neuen Vertreter aus", "narrow": "Eng", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 8073310b0..290130a62 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "Value of the amount must be more or equal to ${minAmount} ${fiatCurrency}", "more_options": "More Options", "name": "Name", + "nano_gpt_thanks_message": "Thanks for using NanoGPT! Remember to head back to the browser after your transaction completes!", + "nanogpt_subtitle": "All the newest models (GPT-4, Claude).\\nNo subscription, pay with crypto.", "nano_current_rep": "Current Representative", "nano_pick_new_rep": "Pick a new representative", "narrow": "Narrow", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index abe596b69..56e496efb 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "El valor de la cantidad debe ser mayor o igual a ${minAmount} ${fiatCurrency}", "more_options": "Más Opciones", "name": "Nombre", + "nano_gpt_thanks_message": "¡Gracias por usar nanogpt! ¡Recuerde regresar al navegador después de que se complete su transacción!", + "nanogpt_subtitle": "Todos los modelos más nuevos (GPT-4, Claude). \\ Nno suscripción, pague con cripto.", "nano_current_rep": "Representante actual", "nano_pick_new_rep": "Elija un nuevo representante", "narrow": "Angosto", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 0650cd36a..2d538bcdb 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "Le montant doit être au moins égal à ${minAmount} ${fiatCurrency}", "more_options": "Plus d'options", "name": "Nom", + "nano_gpt_thanks_message": "Merci d'avoir utilisé Nanogpt! N'oubliez pas de retourner au navigateur une fois votre transaction terminée!", + "nanogpt_subtitle": "Tous les modèles les plus récents (GPT-4, Claude). \\ NNO abonnement, payez avec crypto.", "nano_current_rep": "Représentant actuel", "nano_pick_new_rep": "Choisissez un nouveau représentant", "narrow": "Étroit", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index ab67d8eb2..72d70d796 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "Darajar adadin dole ne ya zama fiye ko daidai da ${minAmount} ${fiatCurrency}", "more_options": "Ƙarin Zaɓuɓɓuka", "name": "Suna", + "nano_gpt_thanks_message": "Na gode da amfani da Nanogpt! Ka tuna da komawa zuwa mai bincike bayan ma'amalar ka ta cika!", + "nanogpt_subtitle": "Duk sabbin samfuran (GPT-4, CLODE). \\ NNO biyan kuɗi, biya tare da crypto.", "nano_current_rep": "Wakilin Yanzu", "nano_pick_new_rep": "Dauki sabon wakili", "narrow": "kunkuntar", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 13df4ab39..3bb8cd5b8 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "राशि का मूल्य अधिक है या करने के लिए बराबर होना चाहिए ${minAmount} ${fiatCurrency}", "more_options": "और विकल्प", "name": "नाम", + "nano_gpt_thanks_message": "Nanogpt का उपयोग करने के लिए धन्यवाद! अपने लेन -देन के पूरा होने के बाद ब्राउज़र पर वापस जाना याद रखें!", + "nanogpt_subtitle": "सभी नवीनतम मॉडल (GPT-4, क्लाउड)। \\ nno सदस्यता, क्रिप्टो के साथ भुगतान करें।", "nano_current_rep": "वर्तमान प्रतिनिधि", "nano_pick_new_rep": "एक नया प्रतिनिधि चुनें", "narrow": "सँकरा", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 3cc08e87d..7ee91495c 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "Vrijednost iznosa mora biti veća ili jednaka ${minAmount} ${fiatCurrency}", "more_options": "Više opcija", "name": "Ime", + "nano_gpt_thanks_message": "Hvala što ste koristili nanogpt! Ne zaboravite da se vratite u preglednik nakon što vam se transakcija završi!", + "nanogpt_subtitle": "Svi najnoviji modeli (GPT-4, Claude). \\ NNO pretplata, plaćajte kripto.", "nano_current_rep": "Trenutni predstavnik", "nano_pick_new_rep": "Odaberite novog predstavnika", "narrow": "Usko", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 778f0c0ec..d7a1ffece 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "Nilai jumlah harus lebih atau sama dengan ${minAmount} ${fiatCurrency}", "more_options": "Opsi Lainnya", "name": "Nama", + "nano_gpt_thanks_message": "Terima kasih telah menggunakan Nanogpt! Ingatlah untuk kembali ke browser setelah transaksi Anda selesai!", + "nanogpt_subtitle": "Semua model terbaru (GPT-4, Claude). \\ Nno langganan, bayar dengan crypto.", "nano_current_rep": "Perwakilan saat ini", "nano_pick_new_rep": "Pilih perwakilan baru", "narrow": "Sempit", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index d0fdfe4e1..190ae23dd 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -372,6 +372,8 @@ "moonpay_alert_text": "Il valore dell'importo deve essere maggiore o uguale a ${minAmount} ${fiatCurrency}", "more_options": "Altre opzioni", "name": "Nome", + "nano_gpt_thanks_message": "Grazie per aver usato il nanogpt! Ricorda di tornare al browser dopo il completamento della transazione!", + "nanogpt_subtitle": "Tutti i modelli più recenti (GPT-4, Claude). Abbonamento nno, paga con cripto.", "nano_current_rep": "Rappresentante attuale", "nano_pick_new_rep": "Scegli un nuovo rappresentante", "narrow": "Stretto", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 211e32cb5..2cfa71e31 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -372,6 +372,8 @@ "moonpay_alert_text": "金額の値は以上でなければなりません ${minAmount} ${fiatCurrency}", "more_options": "その他のオプション", "name": "名前", + "nano_gpt_thanks_message": "NanoGptを使用してくれてありがとう!トランザクションが完了したら、ブラウザに戻ることを忘れないでください!", + "nanogpt_subtitle": "すべての最新モデル(GPT-4、Claude)。\\ nnoサブスクリプション、暗号で支払います。", "nano_current_rep": "現在の代表", "nano_pick_new_rep": "新しい代表者を選びます", "narrow": "狭い", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index c305acd75..42b99189b 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "금액은 다음보다 크거나 같아야합니다 ${minAmount} ${fiatCurrency}", "more_options": "추가 옵션", "name": "이름", + "nano_gpt_thanks_message": "Nanogpt를 사용해 주셔서 감사합니다! 거래가 완료된 후 브라우저로 돌아가는 것을 잊지 마십시오!", + "nanogpt_subtitle": "모든 최신 모델 (GPT-4, Claude). \\ nno 구독, Crypto로 지불하십시오.", "nano_current_rep": "현재 대표", "nano_pick_new_rep": "새로운 담당자를 선택하십시오", "narrow": "좁은", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 9dd8b170f..f9163ce05 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "ပမာဏ၏တန်ဖိုးသည် ${minAmount} ${fiatCurrency} နှင့် ပိုနေရမည်", "more_options": "နောက်ထပ် ရွေးချယ်စရာများ", "name": "နာမည်", + "nano_gpt_thanks_message": "nanogpt ကိုသုံးပြီးကျေးဇူးတင်ပါတယ် သင်၏ငွေပေးငွေယူပြီးနောက် browser သို့ပြန်သွားရန်သတိရပါ။", + "nanogpt_subtitle": "အားလုံးနောက်ဆုံးပေါ်မော်ဒယ်များ (GPT-4, Claude) ။ \\ nno subscription, crypto နှင့်အတူပေးဆောင်။", "nano_current_rep": "လက်ရှိကိုယ်စားလှယ်", "nano_pick_new_rep": "အသစ်တစ်ခုကိုရွေးပါ", "narrow": "ကျဉ်းသော", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 1b7ab4217..6dd3b3573 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "Waarde van het bedrag moet meer of gelijk zijn aan ${minAmount} ${fiatCurrency}", "more_options": "Meer opties", "name": "Naam", + "nano_gpt_thanks_message": "Bedankt voor het gebruik van Nanogpt! Vergeet niet om terug te gaan naar de browser nadat uw transactie is voltooid!", + "nanogpt_subtitle": "Alle nieuwste modellen (GPT-4, Claude). \\ Nno-abonnement, betalen met crypto.", "nano_current_rep": "Huidige vertegenwoordiger", "nano_pick_new_rep": "Kies een nieuwe vertegenwoordiger", "narrow": "Smal", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 8c00c7209..7f50a204d 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "Wartość kwoty musi być większa lub równa ${minAmount} ${fiatCurrency}", "more_options": "Więcej opcji", "name": "Nazwa", + "nano_gpt_thanks_message": "Dzięki za użycie Nanogpt! Pamiętaj, aby wrócić do przeglądarki po zakończeniu transakcji!", + "nanogpt_subtitle": "Wszystkie najnowsze modele (GPT-4, Claude). \\ Nno subskrypcja, płacą za pomocą kryptografii.", "nano_current_rep": "Obecny przedstawiciel", "nano_pick_new_rep": "Wybierz nowego przedstawiciela", "narrow": "Wąski", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 70a9b706a..f71854b5d 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -372,6 +372,8 @@ "moonpay_alert_text": "O valor do montante deve ser maior ou igual a ${minAmount} ${fiatCurrency}", "more_options": "Mais opções", "name": "Nome", + "nano_gpt_thanks_message": "Obrigado por usar o Nanogpt! Lembre -se de voltar para o navegador após a conclusão da transação!", + "nanogpt_subtitle": "Todos os modelos mais recentes (GPT-4, Claude). \\ Nno assinatura, pagam com criptografia.", "nano_current_rep": "Representante atual", "nano_pick_new_rep": "Escolha um novo representante", "narrow": "Estreito", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 2d8a5da8c..990543476 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "Сумма должна быть больше или равна ${minAmount} ${fiatCurrency}", "more_options": "Дополнительные параметры", "name": "Имя", + "nano_gpt_thanks_message": "Спасибо за использование Nanogpt! Не забудьте вернуться в браузер после завершения транзакции!", + "nanogpt_subtitle": "Все новейшие модели (GPT-4, Claude). \\ Nno Подписка, платите с крипто.", "nano_current_rep": "Нынешний представитель", "nano_pick_new_rep": "Выберите нового представителя", "narrow": "Узкий", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index d8f20c357..06e2fae14 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "มูลค่าของจำนวนต้องมากกว่าหรือเท่ากับ ${minAmount} ${fiatCurrency}", "more_options": "ตัวเลือกเพิ่มเติม", "name": "ชื่อ", + "nano_gpt_thanks_message": "ขอบคุณที่ใช้ Nanogpt! อย่าลืมกลับไปที่เบราว์เซอร์หลังจากการทำธุรกรรมของคุณเสร็จสิ้น!", + "nanogpt_subtitle": "รุ่นใหม่ล่าสุดทั้งหมด (GPT-4, Claude). การสมัครสมาชิก \\ nno, จ่ายด้วย crypto", "nano_current_rep": "ตัวแทนปัจจุบัน", "nano_pick_new_rep": "เลือกตัวแทนใหม่", "narrow": "แคบ", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 068faa0f9..6fa6ed59c 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "Ang halaga ng halaga ay dapat na higit pa o katumbas ng ${minAmount} ${fiatCurrency}", "more_options": "Higit pang mga pagpipilian", "name": "Pangalan", + "nano_gpt_thanks_message": "Salamat sa paggamit ng nanogpt! Tandaan na bumalik sa browser matapos makumpleto ang iyong transaksyon!", + "nanogpt_subtitle": "Ang lahat ng mga pinakabagong modelo (GPT-4, Claude). \\ Nno subscription, magbayad gamit ang crypto.", "nano_current_rep": "Kasalukuyang kinatawan", "nano_pick_new_rep": "Pumili ng isang bagong kinatawan", "narrow": "Makitid", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index fc7941f4d..4a69a6aee 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "Tutar ${minAmount} ${fiatCurrency} miktarına eşit veya daha fazla olmalıdır", "more_options": "Daha Fazla Seçenek", "name": "İsim", + "nano_gpt_thanks_message": "Nanogpt kullandığınız için teşekkürler! İşleminiz tamamlandıktan sonra tarayıcıya geri dönmeyi unutmayın!", + "nanogpt_subtitle": "En yeni modeller (GPT-4, Claude). \\ Nno aboneliği, kripto ile ödeme yapın.", "nano_current_rep": "Mevcut temsilci", "nano_pick_new_rep": "Yeni bir temsilci seçin", "narrow": "Dar", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 13dbc2f30..73d6dd2f9 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "Значення суми має бути більшим або дорівнювати ${minAmount} ${fiatCurrency}", "more_options": "Більше параметрів", "name": "Ім'я", + "nano_gpt_thanks_message": "Дякуємо за використання наногпта! Не забудьте повернутися до браузера після завершення транзакції!", + "nanogpt_subtitle": "Усі найновіші моделі (GPT-4, Claude). \\ Nno підписка, оплата криптовалютою.", "nano_current_rep": "Поточний представник", "nano_pick_new_rep": "Виберіть нового представника", "narrow": "вузькі", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 6434fc25d..6839bff7c 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "رقم کی قدر ${minAmount} ${fiatCurrency} کے برابر یا زیادہ ہونی چاہیے۔", "more_options": "مزید زرائے", "name": "ﻡﺎﻧ", + "nano_gpt_thanks_message": "نانوگپٹ استعمال کرنے کا شکریہ! اپنے لین دین کی تکمیل کے بعد براؤزر کی طرف واپس جانا یاد رکھیں!", + "nanogpt_subtitle": "تمام تازہ ترین ماڈل (GPT-4 ، کلاڈ)۔ n n no سبسکرپشن ، کریپٹو کے ساتھ ادائیگی کریں۔", "nano_current_rep": "موجودہ نمائندہ", "nano_pick_new_rep": "ایک نیا نمائندہ منتخب کریں", "narrow": "تنگ", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index df7a5532f..e6e24a1f2 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -372,6 +372,8 @@ "moonpay_alert_text": "Iye owó kò gbọ́dọ̀ kéré ju ${minAmount} ${fiatCurrency}", "more_options": "Ìyàn àfikún", "name": "Oruko", + "nano_gpt_thanks_message": "O ṣeun fun lilo Nonnogt! Ranti lati tẹle pada si ẹrọ lilọ kiri ayelujara lẹhin iṣowo rẹ pari!", + "nanogpt_subtitle": "Gbogbo awọn awoṣe tuntun (GPT-4, Claude). \\ Nno alabapin kan, sanwo pẹlu Crypto.", "nano_current_rep": "Aṣoju lọwọlọwọ", "nano_pick_new_rep": "Mu aṣoju tuntun kan", "narrow": "Taara", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 590b4cadb..ecd0ae738 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -371,6 +371,8 @@ "moonpay_alert_text": "金额的价值必须大于或等于 ${minAmount} ${fiatCurrency}", "more_options": "更多选项", "name": "姓名", + "nano_gpt_thanks_message": "感谢您使用Nanogpt!事务完成后,请记住回到浏览器!", + "nanogpt_subtitle": "所有最新型号(GPT-4,Claude)。\\ nno订阅,用加密货币付款。", "nano_current_rep": "当前代表", "nano_pick_new_rep": "选择新代表", "narrow": "狭窄的", diff --git a/tool/generate_secrets_config.dart b/tool/generate_secrets_config.dart index 6aaa39b7c..cab41ca69 100644 --- a/tool/generate_secrets_config.dart +++ b/tool/generate_secrets_config.dart @@ -6,6 +6,7 @@ import 'utils/utils.dart'; const configPath = 'tool/.secrets-config.json'; const evmChainsConfigPath = 'tool/.evm-secrets-config.json'; const solanaConfigPath = 'tool/.solana-secrets-config.json'; +const nanoConfigPath = 'tool/.nano-secrets-config.json'; const tronConfigPath = 'tool/.tron-secrets-config.json'; Future main(List args) async => generateSecretsConfig(args); @@ -21,6 +22,7 @@ Future generateSecretsConfig(List args) async { final configFile = File(configPath); final evmChainsConfigFile = File(evmChainsConfigPath); final solanaConfigFile = File(solanaConfigPath); + final nanoConfigFile = File(nanoConfigPath); final tronConfigFile = File(tronConfigPath); final secrets = {}; @@ -42,45 +44,48 @@ Future generateSecretsConfig(List args) async { } } + // base: SecretKey.base.forEach((sec) { if (secrets[sec.name] != null) { return; } - secrets[sec.name] = sec.generate(); }); - var secretsJson = JsonEncoder.withIndent(' ').convert(secrets); await configFile.writeAsString(secretsJson); - secrets.clear(); + // evm chains: SecretKey.evmChainsSecrets.forEach((sec) { if (secrets[sec.name] != null) { return; } - secrets[sec.name] = sec.generate(); }); - secretsJson = JsonEncoder.withIndent(' ').convert(secrets); - await evmChainsConfigFile.writeAsString(secretsJson); - secrets.clear(); + // solana: SecretKey.solanaSecrets.forEach((sec) { if (secrets[sec.name] != null) { return; } - secrets[sec.name] = sec.generate(); }); - secretsJson = JsonEncoder.withIndent(' ').convert(secrets); - await solanaConfigFile.writeAsString(secretsJson); + secrets.clear(); + // nano: + SecretKey.nanoSecrets.forEach((sec) { + if (secrets[sec.name] != null) { + return; + } + secrets[sec.name] = sec.generate(); + }); + secretsJson = JsonEncoder.withIndent(' ').convert(secrets); + await nanoConfigFile.writeAsString(secretsJson); secrets.clear(); SecretKey.tronSecrets.forEach((sec) { @@ -90,8 +95,7 @@ Future generateSecretsConfig(List args) async { secrets[sec.name] = sec.generate(); }); - secretsJson = JsonEncoder.withIndent(' ').convert(secrets); - await tronConfigFile.writeAsString(secretsJson); + secrets.clear(); } diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 542e91b38..89e4de12d 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -50,6 +50,10 @@ class SecretKey { SecretKey('ankrApiKey', () => ''), ]; + static final nanoSecrets = [ + SecretKey('nano2ApiKey', () => ''), + ]; + static final tronSecrets = [ SecretKey('tronGridApiKey', () => ''), ];