Nano-GPT (#1336)

* init * updates * nano updates * updates * updates * [skipci] wip deep link changes * fix deep links * minor fix * add reminder message on buy and exchange routes * [skip ci] font fixes * review updates * [skip ci] minor fix * save * fixes * minor code cleanup * minor potential fix

Matthew Fosse committed May 7, 2024 at 17:00 UTC baad7f74696ed463d243d31b2236fc11f427e86b
46 files changed +480 -189
.github/workflows/pr_test_build.yml
+1
@@ -151,6 +151,7 @@ jobs:
151 echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
152 echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
153 echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
154 + echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
155 echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
156
157 - name: Rename app
.gitignore
+1
@@ -94,6 +94,7 @@ android/app/key.jks
94 **/tool/.evm-secrets-config.json
95 **/tool/.ethereum-secrets-config.json
96 **/tool/.solana-secrets-config.json
97 +**/tool/.nano-secrets-config.json
98 **/tool/.tron-secrets-config.json
99 **/lib/.secrets.g.dart
100 **/cw_evm/lib/.secrets.g.dart
android/app/src/main/AndroidManifestBase.xml
+7
@@ -91,6 +91,13 @@
91 <data android:scheme="tron-wallet" />
92 <data android:scheme="tron_wallet" />
93 </intent-filter>
94 + <!-- nano-gpt link scheme -->
95 + <intent-filter android:autoVerify="true">
96 + <action android:name="android.intent.action.VIEW" />
97 + <category android:name="android.intent.category.DEFAULT" />
98 + <category android:name="android.intent.category.BROWSABLE" />
99 + <data android:scheme="nano-gpt" />
100 + </intent-filter>
101 </activity>
102 <meta-data
103 android:name="flutterEmbedding"
assets/banano_node_list.yml new
+5
@@ -0,0 +1,5 @@
1 +-
2 + uri: kaliumapi.appditto.com
3 + path: /api
4 + useSSL: true
5 + is_default: true
\ No newline at end of file
cw_core/lib/crypto_currency.dart
+6
@@ -259,10 +259,16 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
259 element.tag == walletCurrency?.tag));
260 } catch (_) {}
261
262 + // search by fullName if not found by title:
263 + try {
264 + return CryptoCurrency.all.firstWhere((element) => element.fullName?.toLowerCase() == name);
265 + } catch (_) {}
266 +
267 if (CryptoCurrency._nameCurrencyMap[name.toLowerCase()] == null) {
268 final s = 'Unexpected token: $name for CryptoCurrency fromString';
269 throw ArgumentError.value(name, 'name', s);
270 }
271 +
272 return CryptoCurrency._nameCurrencyMap[name.toLowerCase()]!;
273 }
274
cw_nano/lib/banano_balance.dart
+16
@@ -1,12 +1,28 @@
1 import 'package:cw_core/balance.dart';
2 import 'package:nanoutil/nanoutil.dart';
3
4 +BigInt stringAmountToBigIntBanano(String amount) {
5 + return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerBanano));
6 +}
7 +
8 class BananoBalance extends Balance {
9 final BigInt currentBalance;
10 final BigInt receivableBalance;
11
12 BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0);
13
14 + BananoBalance.fromFormattedString(
15 + {required String formattedCurrentBalance, required String formattedReceivableBalance})
16 + : currentBalance = stringAmountToBigIntBanano(formattedCurrentBalance),
17 + receivableBalance = stringAmountToBigIntBanano(formattedReceivableBalance),
18 + super(0, 0);
19 +
20 + BananoBalance.fromRawString(
21 + {required String currentBalance, required String receivableBalance})
22 + : currentBalance = BigInt.parse(currentBalance),
23 + receivableBalance = BigInt.parse(receivableBalance),
24 + super(0, 0);
25 +
26 @override
27 String get formattedAvailableBalance {
28 return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerBanano);
cw_nano/lib/nano_balance.dart
+3 -3
@@ -1,7 +1,7 @@
1 import 'package:cw_core/balance.dart';
2 import 'package:nanoutil/nanoutil.dart';
3
4 -BigInt stringAmountToBigInt(String amount) {
4 +BigInt stringAmountToBigIntNano(String amount) {
5 return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerNano));
6 }
7
@@ -13,8 +13,8 @@ class NanoBalance extends Balance {
13
14 NanoBalance.fromFormattedString(
15 {required String formattedCurrentBalance, required String formattedReceivableBalance})
16 - : currentBalance = stringAmountToBigInt(formattedCurrentBalance),
17 - receivableBalance = stringAmountToBigInt(formattedReceivableBalance),
16 + : currentBalance = stringAmountToBigIntNano(formattedCurrentBalance),
17 + receivableBalance = stringAmountToBigIntNano(formattedReceivableBalance),
18 super(0, 0);
19
20 NanoBalance.fromRawString(
cw_nano/lib/nano_client.dart
+18 -8
@@ -10,6 +10,7 @@ import 'package:nanodart/nanodart.dart';
10 import 'package:cw_core/node.dart';
11 import 'package:nanoutil/nanoutil.dart';
12 import 'package:shared_preferences/shared_preferences.dart';
13 +import 'package:cw_nano/.secrets.g.dart' as secrets;
14
15 class NanoClient {
16 static const Map<String, String> CAKE_HEADERS = {
@@ -52,10 +53,19 @@ class NanoClient {
53 }
54 }
55
56 + Map<String, String> getHeaders() {
57 + if (_node!.uri == "https://rpc.nano.to") {
58 + return CAKE_HEADERS..addAll({
59 + "key": secrets.nano2ApiKey,
60 + });
61 + }
62 + return CAKE_HEADERS;
63 + }
64 +
65 Future<NanoBalance> getBalance(String address) async {
66 final response = await http.post(
67 _node!.uri,
58 - headers: CAKE_HEADERS,
68 + headers: getHeaders(),
69 body: jsonEncode(
70 {
71 "action": "account_balance",
@@ -82,7 +92,7 @@ class NanoClient {
92 try {
93 final response = await http.post(
94 _node!.uri,
85 - headers: CAKE_HEADERS,
95 + headers: getHeaders(),
96 body: jsonEncode(
97 {
98 "action": "account_info",
@@ -94,7 +104,7 @@ class NanoClient {
104 final data = await jsonDecode(response.body);
105 return AccountInfoResponse.fromJson(data as Map<String, dynamic>);
106 } catch (e) {
97 - print("error while getting account info");
107 + print("error while getting account info $e");
108 return null;
109 }
110 }
@@ -149,7 +159,7 @@ class NanoClient {
159 Future<String> requestWork(String hash) async {
160 final response = await http.post(
161 _powNode!.uri,
152 - headers: CAKE_HEADERS,
162 + headers: getHeaders(),
163 body: json.encode(
164 {
165 "action": "work_generate",
@@ -192,7 +202,7 @@ class NanoClient {
202
203 final processResponse = await http.post(
204 _node!.uri,
195 - headers: CAKE_HEADERS,
205 + headers: getHeaders(),
206 body: processBody,
207 );
208
@@ -351,7 +361,7 @@ class NanoClient {
361 });
362 final processResponse = await http.post(
363 _node!.uri,
354 - headers: CAKE_HEADERS,
364 + headers: getHeaders(),
365 body: processBody,
366 );
367
@@ -367,7 +377,7 @@ class NanoClient {
377 required String privateKey,
378 }) async {
379 final receivableResponse = await http.post(_node!.uri,
370 - headers: CAKE_HEADERS,
380 + headers: getHeaders(),
381 body: jsonEncode({
382 "action": "receivable",
383 "account": destinationAddress,
@@ -417,7 +427,7 @@ class NanoClient {
427 Future<List<NanoTransactionModel>> fetchTransactions(String address) async {
428 try {
429 final response = await http.post(_node!.uri,
420 - headers: CAKE_HEADERS,
430 + headers: getHeaders(),
431 body: jsonEncode({
432 "action": "account_history",
433 "account": address,
ios/Runner/InfoBase.plist
+10
@@ -140,6 +140,16 @@
140 <string>nano-wallet</string>
141 </array>
142 </dict>
143 + <dict>
144 + <key>CFBundleTypeRole</key>
145 + <string>Viewer</string>
146 + <key>CFBundleURLName</key>
147 + <string>nano-gpt</string>
148 + <key>CFBundleURLSchemes</key>
149 + <array>
150 + <string>nano-gpt</string>
151 + </array>
152 + </dict>
153 <dict>
154 <key>CFBundleTypeRole</key>
155 <string>Editor</string>
lib/di.dart
+79 -54
@@ -26,6 +26,7 @@ import 'package:cake_wallet/entities/contact.dart';
26 import 'package:cake_wallet/entities/contact_record.dart';
27 import 'package:cake_wallet/entities/exchange_api_mode.dart';
28 import 'package:cake_wallet/entities/parse_address_from_domain.dart';
29 +import 'package:cake_wallet/view_model/link_view_model.dart';
30 import 'package:cake_wallet/tron/tron.dart';
31 import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
32 import 'package:cw_core/receive_page_option.dart';
@@ -268,6 +269,7 @@ Future<void> setup({
269 required Box<UnspentCoinsInfo> unspentCoinsInfoSource,
270 required Box<AnonpayInvoiceInfo> anonpayInvoiceInfoSource,
271 required FlutterSecureStorage secureStorage,
272 + required GlobalKey<NavigatorState> navigatorKey,
273 }) async {
274 _walletInfoSource = walletInfoSource;
275 _nodeSource = nodeSource;
@@ -429,68 +431,89 @@ Future<void> setup({
431 ),
432 );
433
432 - getIt.registerFactory<AuthPage>(() {
433 - return AuthPage(getIt.get<AuthViewModel>(),
434 + getIt.registerLazySingleton<LinkViewModel>(() {
435 + return LinkViewModel(
436 + appStore: getIt.get<AppStore>(),
437 + settingsStore: getIt.get<SettingsStore>(),
438 + authenticationStore: getIt.get<AuthenticationStore>(),
439 + navigatorKey: navigatorKey,
440 + );
441 + });
442 +
443 + getIt.registerFactory<AuthPage>(instanceName: 'login', () {
444 + return AuthPage(getIt.get<AuthViewModel>(), closable: false,
445 onAuthenticationFinished: (isAuthenticated, AuthPageState authPageState) {
446 if (!isAuthenticated) {
447 return;
448 + }
449 + final authStore = getIt.get<AuthenticationStore>();
450 + final appStore = getIt.get<AppStore>();
451 + final useTotp = appStore.settingsStore.useTOTP2FA;
452 + final shouldUseTotp2FAToAccessWallets =
453 + appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
454 + if (useTotp && shouldUseTotp2FAToAccessWallets) {
455 + authPageState.close(
456 + route: Routes.totpAuthCodePage,
457 + arguments: TotpAuthArgumentsModel(
458 + isForSetup: false,
459 + isClosable: false,
460 + onTotpAuthenticationFinished:
461 + (bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuthPageState) async {
462 + if (!isAuthenticatedSuccessfully) {
463 + return;
464 + }
465 + if (appStore.wallet != null) {
466 + authStore.allowed();
467 + return;
468 + }
469 +
470 + totpAuthPageState.changeProcessText('Loading the wallet');
471 +
472 + if (loginError != null) {
473 + totpAuthPageState.changeProcessText('ERROR: ${loginError.toString()}');
474 + }
475 +
476 + ReactionDisposer? _reaction;
477 + _reaction = reaction((_) => appStore.wallet, (Object? _) {
478 + _reaction?.reaction.dispose();
479 + authStore.allowed();
480 + });
481 + },
482 + ),
483 + );
484 } else {
438 - final authStore = getIt.get<AuthenticationStore>();
439 - final appStore = getIt.get<AppStore>();
440 - final useTotp = appStore.settingsStore.useTOTP2FA;
441 - final shouldUseTotp2FAToAccessWallets =
442 - appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
443 - if (useTotp && shouldUseTotp2FAToAccessWallets) {
444 - authPageState.close(
445 - route: Routes.totpAuthCodePage,
446 - arguments: TotpAuthArgumentsModel(
447 - isForSetup: false,
448 - isClosable: false,
449 - onTotpAuthenticationFinished: (bool isAuthenticatedSuccessfully,
450 - TotpAuthCodePageState totpAuthPageState) async {
451 - if (!isAuthenticatedSuccessfully) {
452 - return;
453 - }
454 - if (appStore.wallet != null) {
455 - authStore.allowed();
456 - return;
457 - }
458 -
459 - totpAuthPageState.changeProcessText('Loading the wallet');
460 -
461 - if (loginError != null) {
462 - totpAuthPageState.changeProcessText('ERROR: ${loginError.toString()}');
463 - }
464 -
465 - ReactionDisposer? _reaction;
466 - _reaction = reaction((_) => appStore.wallet, (Object? _) {
467 - _reaction?.reaction.dispose();
468 - authStore.allowed();
469 - });
470 - },
471 - ),
472 - );
473 - } else {
474 - if (appStore.wallet != null) {
475 - authStore.allowed();
476 - return;
485 + // wallet is already loaded:
486 + if (appStore.wallet != null) {
487 + // goes to the dashboard:
488 + authStore.allowed();
489 + // trigger any deep links:
490 + final linkViewModel = getIt.get<LinkViewModel>();
491 + if (linkViewModel.currentLink != null) {
492 + linkViewModel.handleLink();
493 }
494 + return;
495 + }
496
479 - authPageState.changeProcessText('Loading the wallet');
497 + // load the wallet:
498
481 - if (loginError != null) {
482 - authPageState.changeProcessText('ERROR: ${loginError.toString()}');
483 - }
499 + authPageState.changeProcessText('Loading the wallet');
500
485 - ReactionDisposer? _reaction;
486 - _reaction = reaction((_) => appStore.wallet, (Object? _) {
487 - _reaction?.reaction.dispose();
488 - authStore.allowed();
489 - });
501 + if (loginError != null) {
502 + authPageState.changeProcessText('ERROR: ${loginError.toString()}');
503 }
504 +
505 + ReactionDisposer? _reaction;
506 + _reaction = reaction((_) => appStore.wallet, (Object? _) {
507 + _reaction?.reaction.dispose();
508 + authStore.allowed();
509 + final linkViewModel = getIt.get<LinkViewModel>();
510 + if (linkViewModel.currentLink != null) {
511 + linkViewModel.handleLink();
512 + }
513 + });
514 }
492 - }, closable: false);
493 - }, instanceName: 'login');
515 + });
516 + });
517
518 getIt.registerSingleton<BottomSheetService>(BottomSheetServiceImpl());
519
@@ -849,8 +872,10 @@ Future<void> setup({
872 tradesStore: getIt.get<TradesStore>(),
873 sendViewModel: getIt.get<SendViewModel>()));
874
852 - getIt.registerFactory(
853 - () => ExchangePage(getIt.get<ExchangeViewModel>(), getIt.get<AuthService>()));
875 + getIt.registerFactoryParam<ExchangePage, PaymentRequest?, void>(
876 + (PaymentRequest? paymentRequest, __) {
877 + return ExchangePage(getIt.get<ExchangeViewModel>(), getIt.get<AuthService>(), paymentRequest);
878 + });
879
880 getIt.registerFactory(() => ExchangeConfirmPage(tradesStore: getIt.get<TradesStore>()));
881
lib/main.dart
+17 -12
@@ -7,6 +7,7 @@ import 'package:cake_wallet/locales/locale.dart';
7 import 'package:cake_wallet/store/yat/yat_store.dart';
8 import 'package:cake_wallet/utils/device_info.dart';
9 import 'package:cake_wallet/utils/exception_handler.dart';
10 +import 'package:cake_wallet/view_model/link_view_model.dart';
11 import 'package:cw_core/address_info.dart';
12 import 'package:cake_wallet/utils/responsive_layout_util.dart';
13 import 'package:cw_core/hive_type_ids.dart';
@@ -205,18 +206,20 @@ Future<void> initialSetup(
206 nodes: nodes,
207 powNodes: powNodes);
208 await setup(
208 - walletInfoSource: walletInfoSource,
209 - nodeSource: nodes,
210 - powNodeSource: powNodes,
211 - contactSource: contactSource,
212 - tradesSource: tradesSource,
213 - templates: templates,
214 - exchangeTemplates: exchangeTemplates,
215 - transactionDescriptionBox: transactionDescriptions,
216 - ordersSource: ordersSource,
217 - anonpayInvoiceInfoSource: anonpayInvoiceInfo,
218 - unspentCoinsInfoSource: unspentCoinsInfoSource,
219 - secureStorage: secureStorage);
209 + walletInfoSource: walletInfoSource,
210 + nodeSource: nodes,
211 + powNodeSource: powNodes,
212 + contactSource: contactSource,
213 + tradesSource: tradesSource,
214 + templates: templates,
215 + exchangeTemplates: exchangeTemplates,
216 + transactionDescriptionBox: transactionDescriptions,
217 + ordersSource: ordersSource,
218 + anonpayInvoiceInfoSource: anonpayInvoiceInfo,
219 + unspentCoinsInfoSource: unspentCoinsInfoSource,
220 + secureStorage: secureStorage,
221 + navigatorKey: navigatorKey,
222 + );
223 await bootstrap(navigatorKey);
224 monero?.onStartup();
225 }
@@ -287,6 +290,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
290 return Observer(builder: (BuildContext context) {
291 final appStore = getIt.get<AppStore>();
292 final authService = getIt.get<AuthService>();
293 + final linkViewModel = getIt.get<LinkViewModel>();
294 final settingsStore = appStore.settingsStore;
295 final statusBarColor = Colors.transparent;
296 final authenticationStore = getIt.get<AuthenticationStore>();
@@ -309,6 +313,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
313 authenticationStore: authenticationStore,
314 navigatorKey: navigatorKey,
315 authService: authService,
316 + linkViewModel: linkViewModel,
317 child: MaterialApp(
318 navigatorObservers: [routeObserver],
319 navigatorKey: navigatorKey,
lib/router.dart
+11 -10
@@ -221,7 +221,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
221 return CupertinoPageRoute<void>(
222 builder: (_) => getIt.get<SetupPinCodePage>(
223 param1: (PinCodeState<PinCodeWidget> context, dynamic _) =>
224 - Navigator.of(context.context).pushNamed(Routes.restoreWalletFromHardwareWallet, arguments: false),
224 + Navigator.of(context.context)
225 + .pushNamed(Routes.restoreWalletFromHardwareWallet, arguments: false),
226 ),
227 fullscreenDialog: true,
228 );
@@ -231,9 +232,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
232 builder: (_) => ConnectDevicePage(
233 ConnectDevicePageParams(
234 walletType: availableWalletTypes.first,
234 - onConnectDevice: (BuildContext context, _) =>
235 - Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount,
236 - arguments: [availableWalletTypes.first]),
235 + onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed(
236 + Routes.chooseHardwareWalletAccount,
237 + arguments: [availableWalletTypes.first]),
238 ),
239 getIt.get<LedgerViewModel>(),
240 ));
@@ -243,9 +244,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
244 param1: (BuildContext context, WalletType type) {
245 final arguments = ConnectDevicePageParams(
246 walletType: type,
246 - onConnectDevice: (BuildContext context, _) =>
247 - Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount,
248 - arguments: [type]),
247 + onConnectDevice: (BuildContext context, _) => Navigator.of(context)
248 + .pushNamed(Routes.chooseHardwareWalletAccount, arguments: [type]),
249 );
250
251 Navigator.of(context).pushNamed(Routes.connectDevices, arguments: arguments);
@@ -308,8 +308,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
308 case Routes.bumpFeePage:
309 return CupertinoPageRoute<void>(
310 fullscreenDialog: true,
311 - builder: (_) =>
312 - getIt.get<RBFDetailsPage>(param1: settings.arguments as TransactionInfo));
311 + builder: (_) => getIt.get<RBFDetailsPage>(param1: settings.arguments as TransactionInfo));
312
313 case Routes.newSubaddress:
314 return CupertinoPageRoute<void>(
@@ -461,7 +460,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
460
461 case Routes.exchange:
462 return CupertinoPageRoute<void>(
464 - fullscreenDialog: true, builder: (_) => getIt.get<ExchangePage>());
463 + fullscreenDialog: true,
464 + builder: (_) => getIt.get<ExchangePage>(param1: settings.arguments as PaymentRequest?),
465 + );
466
467 case Routes.exchangeTemplate:
468 return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTemplatePage>());
lib/src/screens/dashboard/pages/market_place_page.dart
+18 -4
@@ -59,12 +59,15 @@ class MarketPlacePage extends StatelessWidget {
59 // ),
60 SizedBox(height: 20),
61 DashBoardRoundedCardWidget(
62 - onTap: () => launchUrl(
63 - Uri.https("buy.cakepay.com"),
64 - mode: LaunchMode.externalApplication,
65 - ),
62 title: S.of(context).cake_pay_web_cards_title,
63 subTitle: S.of(context).cake_pay_web_cards_subtitle,
64 + onTap: () => _launchMarketPlaceUrl("buy.cakepay.com"),
65 + ),
66 + const SizedBox(height: 20),
67 + DashBoardRoundedCardWidget(
68 + title: "NanoGPT",
69 + subTitle: S.of(context).nanogpt_subtitle,
70 + onTap: () => _launchMarketPlaceUrl("cake.nano-gpt.com"),
71 ),
72 ],
73 ),
@@ -76,6 +79,17 @@ class MarketPlacePage extends StatelessWidget {
79 );
80 }
81
82 + void _launchMarketPlaceUrl(String url) async {
83 + try {
84 + launchUrl(
85 + Uri.https(url),
86 + mode: LaunchMode.externalApplication,
87 + );
88 + } catch (e) {
89 + print(e);
90 + }
91 + }
92 +
93 // TODO: Remove ionia flow/files if we will discard it
94 void _navigatorToGiftCardsPage(BuildContext context) {
95 final walletType = dashboardViewModel.type;
lib/src/screens/exchange/exchange_page.dart
+9 -1
@@ -10,6 +10,7 @@ import 'package:cake_wallet/src/widgets/add_template_button.dart';
10 import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
11 import 'package:cake_wallet/themes/theme_base.dart';
12 import 'package:cake_wallet/utils/debounce.dart';
13 +import 'package:cake_wallet/utils/payment_request.dart';
14 import 'package:cake_wallet/utils/responsive_layout_util.dart';
15 import 'package:cw_core/sync_status.dart';
16 import 'package:cw_core/wallet_type.dart';
@@ -43,7 +44,7 @@ import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker
44 import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
45
46 class ExchangePage extends BasePage {
46 - ExchangePage(this.exchangeViewModel, this.authService) {
47 + ExchangePage(this.exchangeViewModel, this.authService, this.initialPaymentRequest) {
48 depositWalletName = exchangeViewModel.depositCurrency == CryptoCurrency.xmr
49 ? exchangeViewModel.wallet.name
50 : null;
@@ -54,6 +55,7 @@ class ExchangePage extends BasePage {
55
56 final ExchangeViewModel exchangeViewModel;
57 final AuthService authService;
58 + final PaymentRequest? initialPaymentRequest;
59 final depositKey = GlobalKey<ExchangeCardState>();
60 final receiveKey = GlobalKey<ExchangeCardState>();
61 final _formKey = GlobalKey<FormState>();
@@ -543,6 +545,12 @@ class ExchangePage extends BasePage {
545 // amount: depositAmountController.text);
546 });
547
548 + if (initialPaymentRequest != null) {
549 + exchangeViewModel.receiveCurrency = CryptoCurrency.fromString(initialPaymentRequest!.scheme);
550 + exchangeViewModel.depositAmount = initialPaymentRequest!.amount;
551 + exchangeViewModel.receiveAddress = initialPaymentRequest!.address;
552 + }
553 +
554 _isReactionsSet = true;
555 }
556
lib/src/screens/root/root.dart
+40 -75
@@ -5,6 +5,7 @@ import 'package:cake_wallet/generated/i18n.dart';
5 import 'package:cake_wallet/reactions/wallet_connect.dart';
6 import 'package:cake_wallet/utils/device_info.dart';
7 import 'package:cake_wallet/utils/payment_request.dart';
8 +import 'package:cake_wallet/view_model/link_view_model.dart';
9 import 'package:cw_core/wallet_base.dart';
10 import 'package:flutter/material.dart';
11 import 'package:cake_wallet/routes.dart';
@@ -25,6 +26,7 @@ class Root extends StatefulWidget {
26 required this.child,
27 required this.navigatorKey,
28 required this.authService,
29 + required this.linkViewModel,
30 }) : super(key: key);
31
32 final AuthenticationStore authenticationStore;
@@ -32,6 +34,7 @@ class Root extends StatefulWidget {
34 final GlobalKey<NavigatorState> navigatorKey;
35 final AuthService authService;
36 final Widget child;
37 + final LinkViewModel linkViewModel;
38
39 @override
40 RootState createState() => RootState();
@@ -53,7 +56,6 @@ class RootState extends State<Root> with WidgetsBindingObserver {
56 StreamSubscription<Uri?>? stream;
57 ReactionDisposer? _walletReactionDisposer;
58 ReactionDisposer? _deepLinksReactionDisposer;
56 - Uri? launchUri;
59
60 @override
61 void initState() {
@@ -98,7 +100,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
100 void handleDeepLinking(Uri? uri) async {
101 if (uri == null || !mounted) return;
102
101 - launchUri = uri;
103 + widget.linkViewModel.currentLink = uri;
104
105 bool requireAuth = await widget.authService.requireAuth();
106
@@ -112,7 +114,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
114 (AuthenticationState state) {
115 if (state == AuthenticationState.allowed) {
116 if (widget.appStore.wallet == null) {
115 - waitForWalletInstance(context, launchUri!);
117 + waitForWalletInstance(context);
118 } else {
119 _navigateToDeepLinkScreen();
120 }
@@ -150,6 +152,8 @@ class RootState extends State<Root> with WidgetsBindingObserver {
152
153 @override
154 Widget build(BuildContext context) {
155 + // this only happens when the app has been in the background for some time
156 + // this does NOT trigger when the app is started from the "closed" state!
157 if (_isInactive && !_postFrameCallback && _requestAuth) {
158 _postFrameCallback = true;
159 WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -158,40 +162,38 @@ class RootState extends State<Root> with WidgetsBindingObserver {
162 arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
163 if (!isAuthenticatedSuccessfully) {
164 return;
165 + }
166 + final useTotp = widget.appStore.settingsStore.useTOTP2FA;
167 + final shouldUseTotp2FAToAccessWallets =
168 + widget.appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
169 + if (useTotp && shouldUseTotp2FAToAccessWallets) {
170 + _reset();
171 + auth.close(
172 + route: Routes.totpAuthCodePage,
173 + arguments: TotpAuthArgumentsModel(
174 + onTotpAuthenticationFinished:
175 + (bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuth) {
176 + if (!isAuthenticatedSuccessfully) {
177 + return;
178 + }
179 + _reset();
180 + totpAuth.close(
181 + route: widget.linkViewModel.getRouteToGo(),
182 + arguments: widget.linkViewModel.getRouteArgs(),
183 + );
184 + widget.linkViewModel.currentLink = null;
185 + },
186 + isForSetup: false,
187 + isClosable: false,
188 + ),
189 + );
190 } else {
162 - final useTotp = widget.appStore.settingsStore.useTOTP2FA;
163 - final shouldUseTotp2FAToAccessWallets =
164 - widget.appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
165 - if (useTotp && shouldUseTotp2FAToAccessWallets) {
166 - _reset();
167 - auth.close(
168 - route: Routes.totpAuthCodePage,
169 - arguments: TotpAuthArgumentsModel(
170 - onTotpAuthenticationFinished:
171 - (bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuth) {
172 - if (!isAuthenticatedSuccessfully) {
173 - return;
174 - }
175 - _reset();
176 - totpAuth.close(
177 - route: _getRouteToGo(),
178 - arguments:
179 - isWalletConnectLink ? launchUri : PaymentRequest.fromUri(launchUri),
180 - );
181 - launchUri = null;
182 - },
183 - isForSetup: false,
184 - isClosable: false,
185 - ),
186 - );
187 - } else {
188 - _reset();
189 - auth.close(
190 - route: _getRouteToGo(),
191 - arguments: isWalletConnectLink ? launchUri : PaymentRequest.fromUri(launchUri),
192 - );
193 - launchUri = null;
194 - }
191 + _reset();
192 + auth.close(
193 + route: widget.linkViewModel.getRouteToGo(),
194 + arguments: widget.linkViewModel.getRouteArgs(),
195 + );
196 + widget.linkViewModel.currentLink = null;
197 }
198 },
199 );
@@ -216,36 +218,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
218 _isInactiveController.add(value);
219 }
220
219 - bool _isValidPaymentUri() => launchUri?.path.isNotEmpty ?? false;
220 -
221 - bool get isWalletConnectLink => launchUri?.authority == 'wc';
222 -
223 - String? _getRouteToGo() {
224 - if (isWalletConnectLink) {
225 - if (isEVMCompatibleChain(widget.appStore.wallet!.type)) {
226 - _nonETHWalletErrorToast(S.current.switchToEVMCompatibleWallet);
227 - return null;
228 - }
229 - return Routes.walletConnectConnectionsListing;
230 - } else if (_isValidPaymentUri()) {
231 - return Routes.send;
232 - } else {
233 - return null;
234 - }
235 - }
236 -
237 - Future<void> _nonETHWalletErrorToast(String message) async {
238 - Fluttertoast.showToast(
239 - msg: message,
240 - toastLength: Toast.LENGTH_LONG,
241 - gravity: ToastGravity.SNACKBAR,
242 - backgroundColor: Colors.black,
243 - textColor: Colors.white,
244 - fontSize: 16.0,
245 - );
246 - }
247 -
248 - void waitForWalletInstance(BuildContext context, Uri tempLaunchUri) {
221 + void waitForWalletInstance(BuildContext context) {
222 WidgetsBinding.instance.addPostFrameCallback((_) {
223 if (context.mounted) {
224 _walletReactionDisposer = reaction(
@@ -263,14 +236,6 @@ class RootState extends State<Root> with WidgetsBindingObserver {
236 }
237
238 void _navigateToDeepLinkScreen() {
266 - if (_getRouteToGo() != null) {
267 - WidgetsBinding.instance.addPostFrameCallback((_) {
268 - widget.navigatorKey.currentState?.pushNamed(
269 - _getRouteToGo()!,
270 - arguments: isWalletConnectLink ? launchUri : PaymentRequest.fromUri(launchUri),
271 - );
272 - launchUri = null;
273 - });
274 - }
239 + widget.linkViewModel.handleLink();
240 }
241 }
lib/src/screens/send/send_page.dart
+27 -7
@@ -35,6 +35,8 @@ import 'package:flutter/material.dart';
35 import 'package:flutter_mobx/flutter_mobx.dart';
36 import 'package:mobx/mobx.dart';
37 import 'package:smooth_page_indicator/smooth_page_indicator.dart';
38 +import 'package:cw_core/crypto_currency.dart';
39 +import 'package:url_launcher/url_launcher.dart';
40
41 class SendPage extends BasePage {
42 SendPage({
@@ -420,12 +422,10 @@ class SendPage extends BasePage {
422 }
423
424 reaction((_) => sendViewModel.state, (ExecutionState state) {
423 -
425 if (dialogContext != null && dialogContext?.mounted == true) {
426 Navigator.of(dialogContext!).pop();
427 }
428
428 -
429 if (state is FailureState) {
430 WidgetsBinding.instance.addPostFrameCallback((_) {
431 showPopUp<void>(
@@ -460,10 +460,10 @@ class SendPage extends BasePage {
460 outputs: sendViewModel.outputs,
461 rightButtonText: S.of(_dialogContext).send,
462 leftButtonText: S.of(_dialogContext).cancel,
463 - actionRightButton: () {
463 + actionRightButton: () async {
464 Navigator.of(_dialogContext).pop();
465 sendViewModel.commitTransaction();
466 - showPopUp<void>(
466 + await showPopUp<void>(
467 context: context,
468 builder: (BuildContext _dialogContext) {
469 return Observer(builder: (_) {
@@ -481,12 +481,14 @@ class SendPage extends BasePage {
481 sendViewModel.selectedCryptoCurrency.toString());
482
483 final waitMessage = sendViewModel.walletType == WalletType.solana
484 - ? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}' : '';
484 + ? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}'
485 + : '';
486
487 final newContactMessage = newContactAddress != null
487 - ? '\n${S.of(_dialogContext).add_contact_to_address_book}' : '';
488 + ? '\n${S.of(_dialogContext).add_contact_to_address_book}'
489 + : '';
490
489 - final alertContent =
491 + String alertContent =
492 "$successMessage$waitMessage$newContactMessage";
493
494 if (newContactAddress != null) {
@@ -509,6 +511,10 @@ class SendPage extends BasePage {
511 newContactAddress = null;
512 });
513 } else {
514 + if (initialPaymentRequest?.callbackMessage?.isNotEmpty ??
515 + false) {
516 + alertContent = initialPaymentRequest!.callbackMessage!;
517 + }
518 return AlertWithOneAction(
519 alertTitle: '',
520 alertContent: alertContent,
@@ -523,6 +529,20 @@ class SendPage extends BasePage {
529 return Offstage();
530 });
531 });
532 + if (state is TransactionCommitted) {
533 + if (initialPaymentRequest?.callbackUrl?.isNotEmpty ?? false) {
534 + // wait a second so it's not as jarring:
535 + await Future.delayed(Duration(seconds: 1));
536 + try {
537 + launchUrl(
538 + Uri.parse(initialPaymentRequest!.callbackUrl!),
539 + mode: LaunchMode.externalApplication,
540 + );
541 + } catch (e) {
542 + print(e);
543 + }
544 + }
545 + }
546 },
547 actionLeftButton: () => Navigator.of(_dialogContext).pop());
548 });
lib/utils/payment_request.dart
+22 -3
@@ -1,19 +1,29 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 import 'package:cake_wallet/nano/nano.dart';
3
4 class PaymentRequest {
4 - PaymentRequest(this.address, this.amount, this.note, this.scheme);
5 + PaymentRequest(this.address, this.amount, this.note, this.scheme, {this.callbackUrl, this.callbackMessage});
6
7 factory PaymentRequest.fromUri(Uri? uri) {
8 var address = "";
9 var amount = "";
10 var note = "";
11 var scheme = "";
12 + String? callbackUrl;
13 + String? callbackMessage;
14
15 if (uri != null) {
13 - address = uri.path;
16 + address = uri.queryParameters['address'] ?? uri.path;
17 amount = uri.queryParameters['tx_amount'] ?? uri.queryParameters['amount'] ?? "";
18 note = uri.queryParameters['tx_description'] ?? uri.queryParameters['message'] ?? "";
19 scheme = uri.scheme;
20 + callbackUrl = uri.queryParameters['callback'];
21 + callbackMessage = uri.queryParameters['callbackMessage'];
22 + }
23 +
24 + if (scheme == "nano-gpt") {
25 + // treat as nano so filling out the address works:
26 + scheme = "nano";
27 }
28
29 if (nano != null) {
@@ -26,11 +36,20 @@ class PaymentRequest {
36 }
37 }
38
29 - return PaymentRequest(address, amount, note, scheme);
39 + return PaymentRequest(
40 + address,
41 + amount,
42 + note,
43 + scheme,
44 + callbackUrl: callbackUrl,
45 + callbackMessage: callbackMessage,
46 + );
47 }
48
49 final String address;
50 final String amount;
51 final String note;
52 final String scheme;
53 + final String? callbackUrl;
54 + final String? callbackMessage;
55 }
lib/view_model/link_view_model.dart new
+118
@@ -0,0 +1,118 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:cake_wallet/reactions/wallet_connect.dart';
3 +import 'package:cake_wallet/routes.dart';
4 +import 'package:cake_wallet/store/app_store.dart';
5 +import 'package:cake_wallet/store/authentication_store.dart';
6 +import 'package:cake_wallet/store/settings_store.dart';
7 +import 'package:cake_wallet/utils/payment_request.dart';
8 +import 'package:flutter/material.dart';
9 +import 'package:fluttertoast/fluttertoast.dart';
10 +import 'package:mobx/mobx.dart';
11 +
12 +part 'link_view_model.g.dart';
13 +
14 +class LinkViewModel = LinkViewModelBase with _$LinkViewModel;
15 +
16 +abstract class LinkViewModelBase with Store {
17 + LinkViewModelBase({
18 + required this.settingsStore,
19 + required this.appStore,
20 + required this.authenticationStore,
21 + required this.navigatorKey,
22 + }) {}
23 +
24 + final SettingsStore settingsStore;
25 + final AppStore appStore;
26 + final AuthenticationStore authenticationStore;
27 + final GlobalKey<NavigatorState> navigatorKey;
28 + Uri? currentLink;
29 +
30 + bool get _isValidPaymentUri => currentLink?.path.isNotEmpty ?? false;
31 + bool get isWalletConnectLink => currentLink?.authority == 'wc';
32 + bool get isNanoGptLink => currentLink?.scheme == 'nano-gpt';
33 +
34 + String? getRouteToGo() {
35 + if (isWalletConnectLink) {
36 + if (!isEVMCompatibleChain(appStore.wallet!.type)) {
37 + _errorToast(S.current.switchToEVMCompatibleWallet);
38 + return null;
39 + }
40 + return Routes.walletConnectConnectionsListing;
41 + }
42 +
43 + if (authenticationStore.state == AuthenticationState.uninitialized) {
44 + return null;
45 + }
46 +
47 + if (isNanoGptLink) {
48 + switch (currentLink?.authority ?? '') {
49 + case "exchange":
50 + return Routes.exchange;
51 + case "send":
52 + return Routes.send;
53 + case "buy":
54 + return Routes.buySellPage;
55 + }
56 + }
57 +
58 + if (_isValidPaymentUri) {
59 + return Routes.send;
60 + }
61 +
62 + return null;
63 + }
64 +
65 + dynamic getRouteArgs() {
66 + if (isWalletConnectLink) {
67 + return currentLink;
68 + }
69 +
70 + if (isNanoGptLink) {
71 + switch (currentLink?.authority ?? '') {
72 + case "exchange":
73 + case "send":
74 + return PaymentRequest.fromUri(currentLink);
75 + case "buy":
76 + return true;
77 + }
78 + }
79 +
80 + if (_isValidPaymentUri) {
81 + return PaymentRequest.fromUri(currentLink);
82 + }
83 +
84 + return null;
85 + }
86 +
87 + Future<void> _errorToast(String message, {double fontSize = 16}) async {
88 + Fluttertoast.showToast(
89 + msg: message,
90 + toastLength: Toast.LENGTH_LONG,
91 + gravity: ToastGravity.SNACKBAR,
92 + backgroundColor: Colors.black,
93 + textColor: Colors.white,
94 + fontSize: fontSize,
95 + );
96 + }
97 +
98 + Future<void> handleLink() async {
99 + String? route = getRouteToGo();
100 + dynamic args = getRouteArgs();
101 + if (route != null) {
102 + if (appStore.wallet == null) {
103 + return;
104 + }
105 +
106 + if (isNanoGptLink) {
107 + if (route == Routes.buySellPage || route == Routes.exchange) {
108 + await _errorToast(S.current.nano_gpt_thanks_message, fontSize: 14);
109 + }
110 + }
111 + currentLink = null;
112 + navigatorKey.currentState?.pushNamed(
113 + route,
114 + arguments: args,
115 + );
116 + }
117 + }
118 +}
res/values/strings_ar.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "يجب أن تكون قيمة المبلغ أكبر من أو تساوي ${minAmount} ${fiatCurrency}",
367 "more_options": "المزيد من الخيارات",
368 "name": "ﻢﺳﺍ",
369 + "nano_gpt_thanks_message": "شكرا لاستخدام nanogpt! تذكر أن تعود إلى المتصفح بعد اكتمال معاملتك!",
370 + "nanogpt_subtitle": "جميع النماذج الأحدث (GPT-4 ، Claude). \\ nno اشتراك ، ادفع مع Crypto.",
371 "nano_current_rep": "الممثل الحالي",
372 "nano_pick_new_rep": "اختر ممثلًا جديدًا",
373 "narrow": "ضيق",
res/values/strings_bg.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "Сумата трябва да бъде най-малко ${minAmount} ${fiatCurrency}",
367 "more_options": "Още настройки",
368 "name": "Име",
369 + "nano_gpt_thanks_message": "Благодаря, че използвахте Nanogpt! Не забравяйте да се върнете обратно към браузъра, след като транзакцията ви приключи!",
370 + "nanogpt_subtitle": "Всички най-нови модели (GPT-4, Claude). \\ Nno абонамент, платете с Crypto.",
371 "nano_current_rep": "Настоящ представител",
372 "nano_pick_new_rep": "Изберете нов представител",
373 "narrow": "Тесен",
res/values/strings_cs.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "Částka musí být větší nebo rovna ${minAmount} ${fiatCurrency}",
367 "more_options": "Více možností",
368 "name": "název",
369 + "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!",
370 + "nanogpt_subtitle": "Všechny nejnovější modely (GPT-4, Claude). \\ Nno předplatné, plaťte krypto.",
371 "nano_current_rep": "Současný zástupce",
372 "nano_pick_new_rep": "Vyberte nového zástupce",
373 "narrow": "Úzký",
res/values/strings_de.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "Der Wert des Betrags muss größer oder gleich ${minAmount} ${fiatCurrency} sein",
367 "more_options": "Weitere Optionen",
368 "name": "Name",
369 + "nano_gpt_thanks_message": "Danke, dass du Nanogpt benutzt hast! Denken Sie daran, nach Abschluss Ihrer Transaktion zurück zum Browser zu gehen!",
370 + "nanogpt_subtitle": "Alle neuesten Modelle (GPT-4, Claude).",
371 "nano_current_rep": "Aktueller Vertreter",
372 "nano_pick_new_rep": "Wählen Sie einen neuen Vertreter aus",
373 "narrow": "Eng",
res/values/strings_en.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "Value of the amount must be more or equal to ${minAmount} ${fiatCurrency}",
367 "more_options": "More Options",
368 "name": "Name",
369 + "nano_gpt_thanks_message": "Thanks for using NanoGPT! Remember to head back to the browser after your transaction completes!",
370 + "nanogpt_subtitle": "All the newest models (GPT-4, Claude).\\nNo subscription, pay with crypto.",
371 "nano_current_rep": "Current Representative",
372 "nano_pick_new_rep": "Pick a new representative",
373 "narrow": "Narrow",
res/values/strings_es.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "El valor de la cantidad debe ser mayor o igual a ${minAmount} ${fiatCurrency}",
367 "more_options": "Más Opciones",
368 "name": "Nombre",
369 + "nano_gpt_thanks_message": "¡Gracias por usar nanogpt! ¡Recuerde regresar al navegador después de que se complete su transacción!",
370 + "nanogpt_subtitle": "Todos los modelos más nuevos (GPT-4, Claude). \\ Nno suscripción, pague con cripto.",
371 "nano_current_rep": "Representante actual",
372 "nano_pick_new_rep": "Elija un nuevo representante",
373 "narrow": "Angosto",
res/values/strings_fr.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "Le montant doit être au moins égal à ${minAmount} ${fiatCurrency}",
367 "more_options": "Plus d'options",
368 "name": "Nom",
369 + "nano_gpt_thanks_message": "Merci d'avoir utilisé Nanogpt! N'oubliez pas de retourner au navigateur une fois votre transaction terminée!",
370 + "nanogpt_subtitle": "Tous les modèles les plus récents (GPT-4, Claude). \\ NNO abonnement, payez avec crypto.",
371 "nano_current_rep": "Représentant actuel",
372 "nano_pick_new_rep": "Choisissez un nouveau représentant",
373 "narrow": "Étroit",
res/values/strings_ha.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "Darajar adadin dole ne ya zama fiye ko daidai da ${minAmount} ${fiatCurrency}",
367 "more_options": "Ƙarin Zaɓuɓɓuka",
368 "name": "Suna",
369 + "nano_gpt_thanks_message": "Na gode da amfani da Nanogpt! Ka tuna da komawa zuwa mai bincike bayan ma'amalar ka ta cika!",
370 + "nanogpt_subtitle": "Duk sabbin samfuran (GPT-4, CLODE). \\ NNO biyan kuɗi, biya tare da crypto.",
371 "nano_current_rep": "Wakilin Yanzu",
372 "nano_pick_new_rep": "Dauki sabon wakili",
373 "narrow": "kunkuntar",
res/values/strings_hi.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "राशि का मूल्य अधिक है या करने के लिए बराबर होना चाहिए ${minAmount} ${fiatCurrency}",
367 "more_options": "और विकल्प",
368 "name": "नाम",
369 + "nano_gpt_thanks_message": "Nanogpt का उपयोग करने के लिए धन्यवाद! अपने लेन -देन के पूरा होने के बाद ब्राउज़र पर वापस जाना याद रखें!",
370 + "nanogpt_subtitle": "सभी नवीनतम मॉडल (GPT-4, क्लाउड)। \\ nno सदस्यता, क्रिप्टो के साथ भुगतान करें।",
371 "nano_current_rep": "वर्तमान प्रतिनिधि",
372 "nano_pick_new_rep": "एक नया प्रतिनिधि चुनें",
373 "narrow": "सँकरा",
res/values/strings_hr.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "Vrijednost iznosa mora biti veća ili jednaka ${minAmount} ${fiatCurrency}",
367 "more_options": "Više opcija",
368 "name": "Ime",
369 + "nano_gpt_thanks_message": "Hvala što ste koristili nanogpt! Ne zaboravite da se vratite u preglednik nakon što vam se transakcija završi!",
370 + "nanogpt_subtitle": "Svi najnoviji modeli (GPT-4, Claude). \\ NNO pretplata, plaćajte kripto.",
371 "nano_current_rep": "Trenutni predstavnik",
372 "nano_pick_new_rep": "Odaberite novog predstavnika",
373 "narrow": "Usko",
res/values/strings_id.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "Nilai jumlah harus lebih atau sama dengan ${minAmount} ${fiatCurrency}",
367 "more_options": "Opsi Lainnya",
368 "name": "Nama",
369 + "nano_gpt_thanks_message": "Terima kasih telah menggunakan Nanogpt! Ingatlah untuk kembali ke browser setelah transaksi Anda selesai!",
370 + "nanogpt_subtitle": "Semua model terbaru (GPT-4, Claude). \\ Nno langganan, bayar dengan crypto.",
371 "nano_current_rep": "Perwakilan saat ini",
372 "nano_pick_new_rep": "Pilih perwakilan baru",
373 "narrow": "Sempit",
res/values/strings_it.arb
+2
@@ -367,6 +367,8 @@
367 "moonpay_alert_text": "Il valore dell'importo deve essere maggiore o uguale a ${minAmount} ${fiatCurrency}",
368 "more_options": "Altre opzioni",
369 "name": "Nome",
370 + "nano_gpt_thanks_message": "Grazie per aver usato il nanogpt! Ricorda di tornare al browser dopo il completamento della transazione!",
371 + "nanogpt_subtitle": "Tutti i modelli più recenti (GPT-4, Claude). Abbonamento nno, paga con cripto.",
372 "nano_current_rep": "Rappresentante attuale",
373 "nano_pick_new_rep": "Scegli un nuovo rappresentante",
374 "narrow": "Stretto",
res/values/strings_ja.arb
+2
@@ -367,6 +367,8 @@
367 "moonpay_alert_text": "金額の値は以上でなければなりません ${minAmount} ${fiatCurrency}",
368 "more_options": "その他のオプション",
369 "name": "名前",
370 + "nano_gpt_thanks_message": "NanoGptを使用してくれてありがとう!トランザクションが完了したら、ブラウザに戻ることを忘れないでください!",
371 + "nanogpt_subtitle": "すべての最新モデル(GPT-4、Claude)。\\ nnoサブスクリプション、暗号で支払います。",
372 "nano_current_rep": "現在の代表",
373 "nano_pick_new_rep": "新しい代表者を選びます",
374 "narrow": "狭い",
res/values/strings_ko.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "금액은 다음보다 크거나 같아야합니다 ${minAmount} ${fiatCurrency}",
367 "more_options": "추가 옵션",
368 "name": "이름",
369 + "nano_gpt_thanks_message": "Nanogpt를 사용해 주셔서 감사합니다! 거래가 완료된 후 브라우저로 돌아가는 것을 잊지 마십시오!",
370 + "nanogpt_subtitle": "모든 최신 모델 (GPT-4, Claude). \\ nno 구독, Crypto로 지불하십시오.",
371 "nano_current_rep": "현재 대표",
372 "nano_pick_new_rep": "새로운 담당자를 선택하십시오",
373 "narrow": "좁은",
res/values/strings_my.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "ပမာဏ၏တန်ဖိုးသည် ${minAmount} ${fiatCurrency} နှင့် ပိုနေရမည်",
367 "more_options": "နောက်ထပ် ရွေးချယ်စရာများ",
368 "name": "နာမည်",
369 + "nano_gpt_thanks_message": "nanogpt ကိုသုံးပြီးကျေးဇူးတင်ပါတယ် သင်၏ငွေပေးငွေယူပြီးနောက် browser သို့ပြန်သွားရန်သတိရပါ။",
370 + "nanogpt_subtitle": "အားလုံးနောက်ဆုံးပေါ်မော်ဒယ်များ (GPT-4, Claude) ။ \\ nno subscription, crypto နှင့်အတူပေးဆောင်။",
371 "nano_current_rep": "လက်ရှိကိုယ်စားလှယ်",
372 "nano_pick_new_rep": "အသစ်တစ်ခုကိုရွေးပါ",
373 "narrow": "ကျဉ်းသော",
res/values/strings_nl.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "Waarde van het bedrag moet meer of gelijk zijn aan ${minAmount} ${fiatCurrency}",
367 "more_options": "Meer opties",
368 "name": "Naam",
369 + "nano_gpt_thanks_message": "Bedankt voor het gebruik van Nanogpt! Vergeet niet om terug te gaan naar de browser nadat uw transactie is voltooid!",
370 + "nanogpt_subtitle": "Alle nieuwste modellen (GPT-4, Claude). \\ Nno-abonnement, betalen met crypto.",
371 "nano_current_rep": "Huidige vertegenwoordiger",
372 "nano_pick_new_rep": "Kies een nieuwe vertegenwoordiger",
373 "narrow": "Smal",
res/values/strings_pl.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "Wartość kwoty musi być większa lub równa ${minAmount} ${fiatCurrency}",
367 "more_options": "Więcej opcji",
368 "name": "Nazwa",
369 + "nano_gpt_thanks_message": "Dzięki za użycie Nanogpt! Pamiętaj, aby wrócić do przeglądarki po zakończeniu transakcji!",
370 + "nanogpt_subtitle": "Wszystkie najnowsze modele (GPT-4, Claude). \\ Nno subskrypcja, płacą za pomocą kryptografii.",
371 "nano_current_rep": "Obecny przedstawiciel",
372 "nano_pick_new_rep": "Wybierz nowego przedstawiciela",
373 "narrow": "Wąski",
res/values/strings_pt.arb
+2
@@ -367,6 +367,8 @@
367 "moonpay_alert_text": "O valor do montante deve ser maior ou igual a ${minAmount} ${fiatCurrency}",
368 "more_options": "Mais opções",
369 "name": "Nome",
370 + "nano_gpt_thanks_message": "Obrigado por usar o Nanogpt! Lembre -se de voltar para o navegador após a conclusão da transação!",
371 + "nanogpt_subtitle": "Todos os modelos mais recentes (GPT-4, Claude). \\ Nno assinatura, pagam com criptografia.",
372 "nano_current_rep": "Representante atual",
373 "nano_pick_new_rep": "Escolha um novo representante",
374 "narrow": "Estreito",
res/values/strings_ru.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "Сумма должна быть больше или равна ${minAmount} ${fiatCurrency}",
367 "more_options": "Дополнительные параметры",
368 "name": "Имя",
369 + "nano_gpt_thanks_message": "Спасибо за использование Nanogpt! Не забудьте вернуться в браузер после завершения транзакции!",
370 + "nanogpt_subtitle": "Все новейшие модели (GPT-4, Claude). \\ Nno Подписка, платите с крипто.",
371 "nano_current_rep": "Нынешний представитель",
372 "nano_pick_new_rep": "Выберите нового представителя",
373 "narrow": "Узкий",
res/values/strings_th.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "มูลค่าของจำนวนต้องมากกว่าหรือเท่ากับ ${minAmount} ${fiatCurrency}",
367 "more_options": "ตัวเลือกเพิ่มเติม",
368 "name": "ชื่อ",
369 + "nano_gpt_thanks_message": "ขอบคุณที่ใช้ Nanogpt! อย่าลืมกลับไปที่เบราว์เซอร์หลังจากการทำธุรกรรมของคุณเสร็จสิ้น!",
370 + "nanogpt_subtitle": "รุ่นใหม่ล่าสุดทั้งหมด (GPT-4, Claude). การสมัครสมาชิก \\ nno, จ่ายด้วย crypto",
371 "nano_current_rep": "ตัวแทนปัจจุบัน",
372 "nano_pick_new_rep": "เลือกตัวแทนใหม่",
373 "narrow": "แคบ",
res/values/strings_tl.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "Ang halaga ng halaga ay dapat na higit pa o katumbas ng ${minAmount} ${fiatCurrency}",
367 "more_options": "Higit pang mga pagpipilian",
368 "name": "Pangalan",
369 + "nano_gpt_thanks_message": "Salamat sa paggamit ng nanogpt! Tandaan na bumalik sa browser matapos makumpleto ang iyong transaksyon!",
370 + "nanogpt_subtitle": "Ang lahat ng mga pinakabagong modelo (GPT-4, Claude). \\ Nno subscription, magbayad gamit ang crypto.",
371 "nano_current_rep": "Kasalukuyang kinatawan",
372 "nano_pick_new_rep": "Pumili ng isang bagong kinatawan",
373 "narrow": "Makitid",
res/values/strings_tr.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "Tutar ${minAmount} ${fiatCurrency} miktarına eşit veya daha fazla olmalıdır",
367 "more_options": "Daha Fazla Seçenek",
368 "name": "İsim",
369 + "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!",
370 + "nanogpt_subtitle": "En yeni modeller (GPT-4, Claude). \\ Nno aboneliği, kripto ile ödeme yapın.",
371 "nano_current_rep": "Mevcut temsilci",
372 "nano_pick_new_rep": "Yeni bir temsilci seçin",
373 "narrow": "Dar",
res/values/strings_uk.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "Значення суми має бути більшим або дорівнювати ${minAmount} ${fiatCurrency}",
367 "more_options": "Більше параметрів",
368 "name": "Ім'я",
369 + "nano_gpt_thanks_message": "Дякуємо за використання наногпта! Не забудьте повернутися до браузера після завершення транзакції!",
370 + "nanogpt_subtitle": "Усі найновіші моделі (GPT-4, Claude). \\ Nno підписка, оплата криптовалютою.",
371 "nano_current_rep": "Поточний представник",
372 "nano_pick_new_rep": "Виберіть нового представника",
373 "narrow": "вузькі",
res/values/strings_ur.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "رقم کی قدر ${minAmount} ${fiatCurrency} کے برابر یا زیادہ ہونی چاہیے۔",
367 "more_options": "مزید زرائے",
368 "name": "ﻡﺎﻧ",
369 + "nano_gpt_thanks_message": "نانوگپٹ استعمال کرنے کا شکریہ! اپنے لین دین کی تکمیل کے بعد براؤزر کی طرف واپس جانا یاد رکھیں!",
370 + "nanogpt_subtitle": "تمام تازہ ترین ماڈل (GPT-4 ، کلاڈ)۔ n n no سبسکرپشن ، کریپٹو کے ساتھ ادائیگی کریں۔",
371 "nano_current_rep": "موجودہ نمائندہ",
372 "nano_pick_new_rep": "ایک نیا نمائندہ منتخب کریں",
373 "narrow": "تنگ",
res/values/strings_yo.arb
+2
@@ -367,6 +367,8 @@
367 "moonpay_alert_text": "Iye owó kò gbọ́dọ̀ kéré ju ${minAmount} ${fiatCurrency}",
368 "more_options": "Ìyàn àfikún",
369 "name": "Oruko",
370 + "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!",
371 + "nanogpt_subtitle": "Gbogbo awọn awoṣe tuntun (GPT-4, Claude). \\ Nno alabapin kan, sanwo pẹlu Crypto.",
372 "nano_current_rep": "Aṣoju lọwọlọwọ",
373 "nano_pick_new_rep": "Mu aṣoju tuntun kan",
374 "narrow": "Taara",
res/values/strings_zh.arb
+2
@@ -366,6 +366,8 @@
366 "moonpay_alert_text": "金额的价值必须大于或等于 ${minAmount} ${fiatCurrency}",
367 "more_options": "更多选项",
368 "name": "姓名",
369 + "nano_gpt_thanks_message": "感谢您使用Nanogpt!事务完成后,请记住回到浏览器!",
370 + "nanogpt_subtitle": "所有最新型号(GPT-4,Claude)。\\ nno订阅,用加密货币付款。",
371 "nano_current_rep": "当前代表",
372 "nano_pick_new_rep": "选择新代表",
373 "narrow": "狭窄的",
tool/generate_secrets_config.dart
+16 -12
@@ -6,6 +6,7 @@ import 'utils/utils.dart';
6 const configPath = 'tool/.secrets-config.json';
7 const evmChainsConfigPath = 'tool/.evm-secrets-config.json';
8 const solanaConfigPath = 'tool/.solana-secrets-config.json';
9 +const nanoConfigPath = 'tool/.nano-secrets-config.json';
10 const tronConfigPath = 'tool/.tron-secrets-config.json';
11
12 Future<void> main(List<String> args) async => generateSecretsConfig(args);
@@ -21,6 +22,7 @@ Future<void> generateSecretsConfig(List<String> args) async {
22 final configFile = File(configPath);
23 final evmChainsConfigFile = File(evmChainsConfigPath);
24 final solanaConfigFile = File(solanaConfigPath);
25 + final nanoConfigFile = File(nanoConfigPath);
26 final tronConfigFile = File(tronConfigPath);
27
28 final secrets = <String, dynamic>{};
@@ -42,45 +44,48 @@ Future<void> generateSecretsConfig(List<String> args) async {
44 }
45 }
46
47 + // base:
48 SecretKey.base.forEach((sec) {
49 if (secrets[sec.name] != null) {
50 return;
51 }
49 -
52 secrets[sec.name] = sec.generate();
53 });
52 -
54 var secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
55 await configFile.writeAsString(secretsJson);
55 -
56 secrets.clear();
57
58 + // evm chains:
59 SecretKey.evmChainsSecrets.forEach((sec) {
60 if (secrets[sec.name] != null) {
61 return;
62 }
62 -
63 secrets[sec.name] = sec.generate();
64 });
65 -
65 secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
67 -
66 await evmChainsConfigFile.writeAsString(secretsJson);
69 -
67 secrets.clear();
68
69 + // solana:
70 SecretKey.solanaSecrets.forEach((sec) {
71 if (secrets[sec.name] != null) {
72 return;
73 }
76 -
74 secrets[sec.name] = sec.generate();
75 });
79 -
76 secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
81 -
77 await solanaConfigFile.writeAsString(secretsJson);
78 + secrets.clear();
79
80 + // nano:
81 + SecretKey.nanoSecrets.forEach((sec) {
82 + if (secrets[sec.name] != null) {
83 + return;
84 + }
85 + secrets[sec.name] = sec.generate();
86 + });
87 + secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
88 + await nanoConfigFile.writeAsString(secretsJson);
89 secrets.clear();
90
91 SecretKey.tronSecrets.forEach((sec) {
@@ -90,8 +95,7 @@ Future<void> generateSecretsConfig(List<String> args) async {
95
96 secrets[sec.name] = sec.generate();
97 });
93 -
98 secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
95 -
99 await tronConfigFile.writeAsString(secretsJson);
100 + secrets.clear();
101 }
tool/utils/secret_key.dart
+4
@@ -50,6 +50,10 @@ class SecretKey {
50 SecretKey('ankrApiKey', () => ''),
51 ];
52
53 + static final nanoSecrets = [
54 + SecretKey('nano2ApiKey', () => ''),
55 + ];
56 +
57 static final tronSecrets = [
58 SecretKey('tronGridApiKey', () => ''),
59 ];