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
];