diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index 23902f110..69c632967 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -151,6 +151,7 @@ jobs: echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart + echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - name: Rename app diff --git a/.gitignore b/.gitignore index f1e5b6da3..24b7291f8 100644 --- a/.gitignore +++ b/.gitignore @@ -94,6 +94,7 @@ android/app/key.jks **/tool/.evm-secrets-config.json **/tool/.ethereum-secrets-config.json **/tool/.solana-secrets-config.json +**/tool/.nano-secrets-config.json **/tool/.tron-secrets-config.json **/lib/.secrets.g.dart **/cw_evm/lib/.secrets.g.dart diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index 23207d629..57462099c 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -91,6 +91,13 @@ + + + + + + + with Serializable implemen element.tag == walletCurrency?.tag)); } catch (_) {} + // search by fullName if not found by title: + try { + return CryptoCurrency.all.firstWhere((element) => element.fullName?.toLowerCase() == name); + } catch (_) {} + if (CryptoCurrency._nameCurrencyMap[name.toLowerCase()] == null) { final s = 'Unexpected token: $name for CryptoCurrency fromString'; throw ArgumentError.value(name, 'name', s); } + return CryptoCurrency._nameCurrencyMap[name.toLowerCase()]!; } diff --git a/cw_core/lib/window_size.dart b/cw_core/lib/window_size.dart new file mode 100644 index 000000000..a0f192f66 --- /dev/null +++ b/cw_core/lib/window_size.dart @@ -0,0 +1,22 @@ +import 'dart:io'; + +import 'package:flutter/services.dart'; + +const MethodChannel _channel = MethodChannel('com.cake_wallet/native_utils'); + +Future setDefaultMinimumWindowSize() async { + if (!Platform.isMacOS) return; + + try { + final result = await _channel.invokeMethod( + 'setMinWindowSize', + {'width': 500, 'height': 700}, + ) as bool; + + if (!result) { + print("Failed to set minimum window size."); + } + } on PlatformException catch (e) { + print("Failed to set minimum window size: '${e.message}'."); + } +} diff --git a/cw_nano/lib/banano_balance.dart b/cw_nano/lib/banano_balance.dart index b904a35cb..d766077fc 100644 --- a/cw_nano/lib/banano_balance.dart +++ b/cw_nano/lib/banano_balance.dart @@ -1,12 +1,28 @@ import 'package:cw_core/balance.dart'; import 'package:nanoutil/nanoutil.dart'; +BigInt stringAmountToBigIntBanano(String amount) { + return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerBanano)); +} + class BananoBalance extends Balance { final BigInt currentBalance; final BigInt receivableBalance; BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0); + BananoBalance.fromFormattedString( + {required String formattedCurrentBalance, required String formattedReceivableBalance}) + : currentBalance = stringAmountToBigIntBanano(formattedCurrentBalance), + receivableBalance = stringAmountToBigIntBanano(formattedReceivableBalance), + super(0, 0); + + BananoBalance.fromRawString( + {required String currentBalance, required String receivableBalance}) + : currentBalance = BigInt.parse(currentBalance), + receivableBalance = BigInt.parse(receivableBalance), + super(0, 0); + @override String get formattedAvailableBalance { return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerBanano); diff --git a/cw_nano/lib/nano_balance.dart b/cw_nano/lib/nano_balance.dart index 8b8c93b33..691b3a32d 100644 --- a/cw_nano/lib/nano_balance.dart +++ b/cw_nano/lib/nano_balance.dart @@ -1,7 +1,7 @@ import 'package:cw_core/balance.dart'; import 'package:nanoutil/nanoutil.dart'; -BigInt stringAmountToBigInt(String amount) { +BigInt stringAmountToBigIntNano(String amount) { return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerNano)); } @@ -13,8 +13,8 @@ class NanoBalance extends Balance { NanoBalance.fromFormattedString( {required String formattedCurrentBalance, required String formattedReceivableBalance}) - : currentBalance = stringAmountToBigInt(formattedCurrentBalance), - receivableBalance = stringAmountToBigInt(formattedReceivableBalance), + : currentBalance = stringAmountToBigIntNano(formattedCurrentBalance), + receivableBalance = stringAmountToBigIntNano(formattedReceivableBalance), super(0, 0); NanoBalance.fromRawString( diff --git a/cw_nano/lib/nano_client.dart b/cw_nano/lib/nano_client.dart index 267ac888f..99782e47c 100644 --- a/cw_nano/lib/nano_client.dart +++ b/cw_nano/lib/nano_client.dart @@ -10,6 +10,7 @@ import 'package:http/http.dart' as http; import 'package:cw_core/node.dart'; import 'package:nanoutil/nanoutil.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:cw_nano/.secrets.g.dart' as secrets; class NanoClient { static const Map CAKE_HEADERS = { @@ -52,10 +53,19 @@ class NanoClient { } } + Map getHeaders() { + if (_node!.uri == "https://rpc.nano.to") { + return CAKE_HEADERS..addAll({ + "key": secrets.nano2ApiKey, + }); + } + return CAKE_HEADERS; + } + Future getBalance(String address) async { final response = await http.post( _node!.uri, - headers: CAKE_HEADERS, + headers: getHeaders(), body: jsonEncode( { "action": "account_balance", @@ -82,7 +92,7 @@ class NanoClient { try { final response = await http.post( _node!.uri, - headers: CAKE_HEADERS, + headers: getHeaders(), body: jsonEncode( { "action": "account_info", @@ -94,7 +104,7 @@ class NanoClient { final data = await jsonDecode(response.body); return AccountInfoResponse.fromJson(data as Map); } catch (e) { - print("error while getting account info"); + print("error while getting account info $e"); return null; } } @@ -170,7 +180,7 @@ class NanoClient { Future requestWork(String hash) async { final response = await http.post( _powNode!.uri, - headers: CAKE_HEADERS, + headers: getHeaders(), body: json.encode( { "action": "work_generate", @@ -213,7 +223,7 @@ class NanoClient { final processResponse = await http.post( _node!.uri, - headers: CAKE_HEADERS, + headers: getHeaders(), body: processBody, ); @@ -412,7 +422,7 @@ class NanoClient { }); final processResponse = await http.post( _node!.uri, - headers: CAKE_HEADERS, + headers: getHeaders(), body: processBody, ); @@ -428,7 +438,7 @@ class NanoClient { required String privateKey, }) async { final receivableResponse = await http.post(_node!.uri, - headers: CAKE_HEADERS, + headers: getHeaders(), body: jsonEncode({ "action": "receivable", "account": destinationAddress, @@ -476,7 +486,7 @@ class NanoClient { Future> fetchTransactions(String address) async { try { final response = await http.post(_node!.uri, - headers: CAKE_HEADERS, + headers: getHeaders(), body: jsonEncode({ "action": "account_history", "account": address, diff --git a/cw_tron/lib/tron_client.dart b/cw_tron/lib/tron_client.dart index f03a8abce..73812f14c 100644 --- a/cw_tron/lib/tron_client.dart +++ b/cw_tron/lib/tron_client.dart @@ -367,7 +367,7 @@ class TronClient { ) async { // This is introduce to server as a limit in cases where feeLimit is 0 // The transaction signing will fail if the feeLimit is explicitly 0. - int defaultFeeLimit = 100000; + int defaultFeeLimit = 269000; final block = await _provider!.request(TronRequestGetNowBlock()); // Create the transfer contract @@ -401,8 +401,9 @@ class TronClient { final tronBalanceInt = tronBalance.toInt(); if (feeLimit > tronBalanceInt) { + final feeInTrx = TronHelper.fromSun(BigInt.parse(feeLimit.toString())); throw Exception( - 'You don\'t have enough TRX to cover the transaction fee for this transaction. Kindly top up.', + 'You don\'t have enough TRX to cover the transaction fee for this transaction. Kindly top up.\nTransaction fee: $feeInTrx TRX', ); } @@ -442,6 +443,9 @@ class TronClient { if (!request.isSuccess) { log("Tron TRC20 error: ${request.error} \n ${request.respose}"); + throw Exception( + 'An error occured while creating the transfer request. Please try again.', + ); } final feeLimit = await getFeeLimit( @@ -454,8 +458,9 @@ class TronClient { final tronBalanceInt = tronBalance.toInt(); if (feeLimit > tronBalanceInt) { + final feeInTrx = TronHelper.fromSun(BigInt.parse(feeLimit.toString())); throw Exception( - 'You don\'t have enough TRX to cover the transaction fee for this transaction. Kindly top up.', + 'You don\'t have enough TRX to cover the transaction fee for this transaction. Kindly top up. Transaction fee: $feeInTrx TRX', ); } diff --git a/ios/Runner/InfoBase.plist b/ios/Runner/InfoBase.plist index 02365bda7..83e60b542 100644 --- a/ios/Runner/InfoBase.plist +++ b/ios/Runner/InfoBase.plist @@ -140,6 +140,16 @@ nano-wallet + + CFBundleTypeRole + Viewer + CFBundleURLName + nano-gpt + CFBundleURLSchemes + + nano-gpt + + CFBundleTypeRole Editor diff --git a/lib/di.dart b/lib/di.dart index 4a8539e7d..a013f72a2 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -28,6 +28,7 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/src/screens/dashboard/sign_page.dart'; import 'package:cake_wallet/src/screens/receive/address_list_page.dart'; +import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart'; import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart'; @@ -271,6 +272,7 @@ Future setup({ required Box unspentCoinsInfoSource, required Box anonpayInvoiceInfoSource, required FlutterSecureStorage secureStorage, + required GlobalKey navigatorKey, }) async { _walletInfoSource = walletInfoSource; _nodeSource = nodeSource; @@ -432,68 +434,89 @@ Future setup({ ), ); - getIt.registerFactory(() { - return AuthPage(getIt.get(), + getIt.registerLazySingleton(() { + return LinkViewModel( + appStore: getIt.get(), + settingsStore: getIt.get(), + authenticationStore: getIt.get(), + navigatorKey: navigatorKey, + ); + }); + + getIt.registerFactory(instanceName: 'login', () { + return AuthPage(getIt.get(), closable: false, onAuthenticationFinished: (isAuthenticated, AuthPageState authPageState) { if (!isAuthenticated) { return; - } else { - final authStore = getIt.get(); - final appStore = getIt.get(); - final useTotp = appStore.settingsStore.useTOTP2FA; - final shouldUseTotp2FAToAccessWallets = - appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet; - if (useTotp && shouldUseTotp2FAToAccessWallets) { - authPageState.close( - route: Routes.totpAuthCodePage, - arguments: TotpAuthArgumentsModel( - isForSetup: false, - isClosable: false, - onTotpAuthenticationFinished: (bool isAuthenticatedSuccessfully, - TotpAuthCodePageState totpAuthPageState) async { - if (!isAuthenticatedSuccessfully) { - return; - } - if (appStore.wallet != null) { - authStore.allowed(); - return; - } - - totpAuthPageState.changeProcessText('Loading the wallet'); - - if (loginError != null) { - totpAuthPageState.changeProcessText('ERROR: ${loginError.toString()}'); - } - - ReactionDisposer? _reaction; - _reaction = reaction((_) => appStore.wallet, (Object? _) { - _reaction?.reaction.dispose(); - authStore.allowed(); - }); - }, - ), - ); - } else { - if (appStore.wallet != null) { - authStore.allowed(); - return; - } - - authPageState.changeProcessText('Loading the wallet'); - - if (loginError != null) { - authPageState.changeProcessText('ERROR: ${loginError.toString()}'); - } - - ReactionDisposer? _reaction; - _reaction = reaction((_) => appStore.wallet, (Object? _) { - _reaction?.reaction.dispose(); - authStore.allowed(); - }); - } } - }, closable: false); - }, instanceName: 'login'); + final authStore = getIt.get(); + final appStore = getIt.get(); + final useTotp = appStore.settingsStore.useTOTP2FA; + final shouldUseTotp2FAToAccessWallets = + appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet; + if (useTotp && shouldUseTotp2FAToAccessWallets) { + authPageState.close( + route: Routes.totpAuthCodePage, + arguments: TotpAuthArgumentsModel( + isForSetup: false, + isClosable: false, + onTotpAuthenticationFinished: + (bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuthPageState) async { + if (!isAuthenticatedSuccessfully) { + return; + } + if (appStore.wallet != null) { + authStore.allowed(); + return; + } + + totpAuthPageState.changeProcessText('Loading the wallet'); + + if (loginError != null) { + totpAuthPageState.changeProcessText('ERROR: ${loginError.toString()}'); + } + + ReactionDisposer? _reaction; + _reaction = reaction((_) => appStore.wallet, (Object? _) { + _reaction?.reaction.dispose(); + authStore.allowed(); + }); + }, + ), + ); + } else { + // wallet is already loaded: + if (appStore.wallet != null) { + // goes to the dashboard: + authStore.allowed(); + // trigger any deep links: + final linkViewModel = getIt.get(); + if (linkViewModel.currentLink != null) { + linkViewModel.handleLink(); + } + return; + } + + // load the wallet: + + authPageState.changeProcessText('Loading the wallet'); + + if (loginError != null) { + authPageState.changeProcessText('ERROR: ${loginError.toString()}'); + } + + ReactionDisposer? _reaction; + _reaction = reaction((_) => appStore.wallet, (Object? _) { + _reaction?.reaction.dispose(); + authStore.allowed(); + final linkViewModel = getIt.get(); + if (linkViewModel.currentLink != null) { + linkViewModel.handleLink(); + } + }); + } + }); + }); getIt.registerSingleton(BottomSheetServiceImpl()); @@ -854,8 +877,10 @@ Future setup({ tradesStore: getIt.get(), sendViewModel: getIt.get())); - getIt.registerFactory( - () => ExchangePage(getIt.get(), getIt.get())); + getIt.registerFactoryParam( + (PaymentRequest? paymentRequest, __) { + return ExchangePage(getIt.get(), getIt.get(), paymentRequest); + }); getIt.registerFactory(() => ExchangeConfirmPage(tradesStore: getIt.get())); diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index f729e6392..409724c6e 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/entities/openalias_record.dart'; import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/entities/unstoppable_domain_address.dart'; import 'package:cake_wallet/entities/emoji_string_extension.dart'; +import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; import 'package:cake_wallet/mastodon/mastodon_api.dart'; import 'package:cake_wallet/nostr/nostr_api.dart'; import 'package:cake_wallet/store/settings_store.dart'; @@ -71,8 +72,8 @@ class AddressResolver { return emailRegex.hasMatch(address); } - // TODO: refactor this to take Crypto currency instead of ticker, or at least pass in the tag as well - Future resolve(BuildContext context, String text, String ticker) async { + Future resolve(BuildContext context, String text, CryptoCurrency currency) async { + final ticker = currency.title; try { if (text.startsWith('@') && !text.substring(1).contains('@')) { if (settingsStore.lookupsTwitter) { @@ -116,8 +117,7 @@ class AddressResolver { await MastodonAPI.lookupUserByUserName(userName: userName, apiHost: hostName); if (mastodonUser != null) { - String? addressFromBio = extractAddressByType( - raw: mastodonUser.note, type: CryptoCurrency.fromString(ticker)); + String? addressFromBio = extractAddressByType(raw: mastodonUser.note, type: currency); if (addressFromBio != null) { return ParsedAddress.fetchMastodonAddress( @@ -131,8 +131,8 @@ class AddressResolver { if (pinnedPosts.isNotEmpty) { final userPinnedPostsText = pinnedPosts.map((item) => item.content).join('\n'); - String? addressFromPinnedPost = extractAddressByType( - raw: userPinnedPostsText, type: CryptoCurrency.fromString(ticker)); + String? addressFromPinnedPost = + extractAddressByType(raw: userPinnedPostsText, type: currency); if (addressFromPinnedPost != null) { return ParsedAddress.fetchMastodonAddress( @@ -162,6 +162,16 @@ class AddressResolver { } } } + + final thorChainAddress = await ThorChainExchangeProvider.lookupAddressByName(text); + if (thorChainAddress != null) { + String? address = + thorChainAddress[ticker] ?? (ticker == 'RUNE' ? thorChainAddress['THOR'] : null); + if (address != null) { + return ParsedAddress.thorChainAddress(address: address, name: text); + } + } + final formattedName = OpenaliasRecord.formatDomainName(text); final domainParts = formattedName.split('.'); final name = domainParts.last; @@ -204,7 +214,7 @@ class AddressResolver { if (nostrUserData != null) { String? addressFromBio = extractAddressByType( - raw: nostrUserData.about, type: CryptoCurrency.fromString(ticker)); + raw: nostrUserData.about, type: currency); if (addressFromBio != null) { return ParsedAddress.nostrAddress( address: addressFromBio, diff --git a/lib/entities/parsed_address.dart b/lib/entities/parsed_address.dart index fc8ab2440..cfd69acbe 100644 --- a/lib/entities/parsed_address.dart +++ b/lib/entities/parsed_address.dart @@ -11,7 +11,8 @@ enum ParseFrom { ens, contact, mastodon, - nostr + nostr, + thorChain } class ParsedAddress { @@ -133,6 +134,14 @@ class ParsedAddress { ); } + factory ParsedAddress.thorChainAddress({required String address, required String name}) { + return ParsedAddress( + addresses: [address], + name: name, + parseFrom: ParseFrom.thorChain, + ); + } + final List addresses; final String name; final String description; diff --git a/lib/exchange/provider/thorchain_exchange.provider.dart b/lib/exchange/provider/thorchain_exchange.provider.dart index 32dce7db8..826e203f3 100644 --- a/lib/exchange/provider/thorchain_exchange.provider.dart +++ b/lib/exchange/provider/thorchain_exchange.provider.dart @@ -34,11 +34,13 @@ class ThorChainExchangeProvider extends ExchangeProvider { static final isRefundAddressSupported = [CryptoCurrency.eth]; - static const _baseURL = 'thornode.ninerealms.com'; + static const _baseNodeURL = 'thornode.ninerealms.com'; + static const _baseURL = 'midgard.ninerealms.com'; static const _quotePath = '/thorchain/quote/swap'; static const _txInfoPath = '/thorchain/tx/status/'; static const _affiliateName = 'cakewallet'; static const _affiliateBps = '175'; + static const _nameLookUpPath= 'v2/thorname/lookup/'; final Box tradesStore; @@ -154,7 +156,7 @@ class ThorChainExchangeProvider extends ExchangeProvider { Future findTradeById({required String id}) async { if (id.isEmpty) throw Exception('Trade id is empty'); final formattedId = id.startsWith('0x') ? id.substring(2) : id; - final uri = Uri.https(_baseURL, '$_txInfoPath$formattedId'); + final uri = Uri.https(_baseNodeURL, '$_txInfoPath$formattedId'); final response = await http.get(uri); if (response.statusCode == 404) { @@ -206,8 +208,35 @@ class ThorChainExchangeProvider extends ExchangeProvider { ); } + static Future?>? lookupAddressByName(String name) async { + final uri = Uri.https(_baseURL, '$_nameLookUpPath$name'); + final response = await http.get(uri); + + if (response.statusCode != 200) { + return null; + } + + final body = json.decode(response.body) as Map; + final entries = body['entries'] as List?; + + if (entries == null || entries.isEmpty) { + return null; + } + + Map chainToAddressMap = {}; + + for (final entry in entries) { + final chain = entry['chain'] as String; + final address = entry['address'] as String; + chainToAddressMap[chain] = address; + } + + return chainToAddressMap; + } + + Future> _getSwapQuote(Map params) async { - Uri uri = Uri.https(_baseURL, _quotePath, params); + Uri uri = Uri.https(_baseNodeURL, _quotePath, params); final response = await http.get(uri); diff --git a/lib/main.dart b/lib/main.dart index b274c7a84..2a4e12236 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/locales/locale.dart'; import 'package:cake_wallet/store/yat/yat_store.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; +import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cw_core/address_info.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/hive_type_ids.dart'; @@ -41,6 +42,7 @@ import 'package:uni_links/uni_links.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/window_size.dart'; final navigatorKey = GlobalKey(); final rootKey = GlobalKey(); @@ -60,6 +62,8 @@ Future main() async { return true; }; + await setDefaultMinimumWindowSize(); + await CakeHive.close(); await initializeAppConfigs(); @@ -202,18 +206,20 @@ Future initialSetup( nodes: nodes, powNodes: powNodes); await setup( - walletInfoSource: walletInfoSource, - nodeSource: nodes, - powNodeSource: powNodes, - contactSource: contactSource, - tradesSource: tradesSource, - templates: templates, - exchangeTemplates: exchangeTemplates, - transactionDescriptionBox: transactionDescriptions, - ordersSource: ordersSource, - anonpayInvoiceInfoSource: anonpayInvoiceInfo, - unspentCoinsInfoSource: unspentCoinsInfoSource, - secureStorage: secureStorage); + walletInfoSource: walletInfoSource, + nodeSource: nodes, + powNodeSource: powNodes, + contactSource: contactSource, + tradesSource: tradesSource, + templates: templates, + exchangeTemplates: exchangeTemplates, + transactionDescriptionBox: transactionDescriptions, + ordersSource: ordersSource, + anonpayInvoiceInfoSource: anonpayInvoiceInfo, + unspentCoinsInfoSource: unspentCoinsInfoSource, + secureStorage: secureStorage, + navigatorKey: navigatorKey, + ); await bootstrap(navigatorKey); monero?.onStartup(); } @@ -284,6 +290,7 @@ class AppState extends State with SingleTickerProviderStateMixin { return Observer(builder: (BuildContext context) { final appStore = getIt.get(); final authService = getIt.get(); + final linkViewModel = getIt.get(); final settingsStore = appStore.settingsStore; final statusBarColor = Colors.transparent; final authenticationStore = getIt.get(); @@ -306,6 +313,7 @@ class AppState extends State with SingleTickerProviderStateMixin { authenticationStore: authenticationStore, navigatorKey: navigatorKey, authService: authService, + linkViewModel: linkViewModel, child: MaterialApp( navigatorObservers: [routeObserver], navigatorKey: navigatorKey, diff --git a/lib/router.dart b/lib/router.dart index 820f3392b..b030214c4 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -225,7 +225,8 @@ Route createRoute(RouteSettings settings) { return CupertinoPageRoute( builder: (_) => getIt.get( param1: (PinCodeState context, dynamic _) => - Navigator.of(context.context).pushNamed(Routes.restoreWalletFromHardwareWallet, arguments: false), + Navigator.of(context.context) + .pushNamed(Routes.restoreWalletFromHardwareWallet, arguments: false), ), fullscreenDialog: true, ); @@ -235,9 +236,9 @@ Route createRoute(RouteSettings settings) { builder: (_) => ConnectDevicePage( ConnectDevicePageParams( walletType: availableWalletTypes.first, - onConnectDevice: (BuildContext context, _) => - Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount, - arguments: [availableWalletTypes.first]), + onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed( + Routes.chooseHardwareWalletAccount, + arguments: [availableWalletTypes.first]), ), getIt.get(), )); @@ -247,9 +248,8 @@ Route createRoute(RouteSettings settings) { param1: (BuildContext context, WalletType type) { final arguments = ConnectDevicePageParams( walletType: type, - onConnectDevice: (BuildContext context, _) => - Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount, - arguments: [type]), + onConnectDevice: (BuildContext context, _) => Navigator.of(context) + .pushNamed(Routes.chooseHardwareWalletAccount, arguments: [type]), ); Navigator.of(context).pushNamed(Routes.connectDevices, arguments: arguments); @@ -467,7 +467,9 @@ Route createRoute(RouteSettings settings) { case Routes.exchange: return CupertinoPageRoute( - fullscreenDialog: true, builder: (_) => getIt.get()); + fullscreenDialog: true, + builder: (_) => getIt.get(param1: settings.arguments as PaymentRequest?), + ); case Routes.exchangeTemplate: return CupertinoPageRoute(builder: (_) => getIt.get()); diff --git a/lib/src/screens/dashboard/desktop_widgets/dropdown_item_widget.dart b/lib/src/screens/dashboard/desktop_widgets/dropdown_item_widget.dart index aa7012ae5..f8f92dc08 100644 --- a/lib/src/screens/dashboard/desktop_widgets/dropdown_item_widget.dart +++ b/lib/src/screens/dashboard/desktop_widgets/dropdown_item_widget.dart @@ -22,7 +22,7 @@ class DropDownItemWidget extends StatelessWidget { child: Text( title, style: TextStyle( - fontSize: 22, + fontSize: 18, fontWeight: FontWeight.w500, color: Theme.of(context).extension()!.titleColor, ), diff --git a/lib/src/screens/dashboard/pages/market_place_page.dart b/lib/src/screens/dashboard/pages/market_place_page.dart index 0cd9312ea..72d8a0e70 100644 --- a/lib/src/screens/dashboard/pages/market_place_page.dart +++ b/lib/src/screens/dashboard/pages/market_place_page.dart @@ -60,12 +60,15 @@ class MarketPlacePage extends StatelessWidget { // ), SizedBox(height: 20), DashBoardRoundedCardWidget( - onTap: () => launchUrl( - Uri.https("buy.cakepay.com"), - mode: LaunchMode.externalApplication, - ), title: S.of(context).cake_pay_web_cards_title, subTitle: S.of(context).cake_pay_web_cards_subtitle, + onTap: () => _launchMarketPlaceUrl("buy.cakepay.com"), + ), + const SizedBox(height: 20), + DashBoardRoundedCardWidget( + title: "NanoGPT", + subTitle: S.of(context).nanogpt_subtitle, + onTap: () => _launchMarketPlaceUrl("cake.nano-gpt.com"), ), SizedBox(height: 20), @@ -92,6 +95,17 @@ class MarketPlacePage extends StatelessWidget { ); } + void _launchMarketPlaceUrl(String url) async { + try { + launchUrl( + Uri.https(url), + mode: LaunchMode.externalApplication, + ); + } catch (e) { + print(e); + } + } + // TODO: Remove ionia flow/files if we will discard it void _navigatorToGiftCardsPage(BuildContext context) { final walletType = dashboardViewModel.type; diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index d9e119038..e2d424fa0 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -10,6 +10,7 @@ import 'package:cake_wallet/src/widgets/add_template_button.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/debounce.dart'; +import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/wallet_type.dart'; @@ -43,7 +44,7 @@ import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart'; class ExchangePage extends BasePage { - ExchangePage(this.exchangeViewModel, this.authService) { + ExchangePage(this.exchangeViewModel, this.authService, this.initialPaymentRequest) { depositWalletName = exchangeViewModel.depositCurrency == CryptoCurrency.xmr ? exchangeViewModel.wallet.name : null; @@ -54,6 +55,7 @@ class ExchangePage extends BasePage { final ExchangeViewModel exchangeViewModel; final AuthService authService; + final PaymentRequest? initialPaymentRequest; final depositKey = GlobalKey(); final receiveKey = GlobalKey(); final _formKey = GlobalKey(); @@ -330,10 +332,12 @@ class ExchangePage extends BasePage { void applyTemplate( BuildContext context, ExchangeViewModel exchangeViewModel, ExchangeTemplate template) async { - exchangeViewModel.changeDepositCurrency( - currency: CryptoCurrency.fromString(template.depositCurrency)); - exchangeViewModel.changeReceiveCurrency( - currency: CryptoCurrency.fromString(template.receiveCurrency)); + + final depositCryptoCurrency = CryptoCurrency.fromString(template.depositCurrency); + final receiveCryptoCurrency = CryptoCurrency.fromString(template.receiveCurrency); + + exchangeViewModel.changeDepositCurrency(currency: depositCryptoCurrency); + exchangeViewModel.changeReceiveCurrency(currency: receiveCryptoCurrency); exchangeViewModel.changeDepositAmount(amount: template.amount); exchangeViewModel.depositAddress = template.depositAddress; @@ -342,12 +346,10 @@ class ExchangePage extends BasePage { exchangeViewModel.isFixedRateMode = false; var domain = template.depositAddress; - var ticker = template.depositCurrency.toLowerCase(); - exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, ticker); + exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, depositCryptoCurrency); domain = template.receiveAddress; - ticker = template.receiveCurrency.toLowerCase(); - exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, ticker); + exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, receiveCryptoCurrency); } void _setReactions(BuildContext context, ExchangeViewModel exchangeViewModel) { @@ -519,16 +521,14 @@ class ExchangePage extends BasePage { _depositAddressFocus.addListener(() async { if (!_depositAddressFocus.hasFocus && depositAddressController.text.isNotEmpty) { final domain = depositAddressController.text; - final ticker = exchangeViewModel.depositCurrency.title.toLowerCase(); - exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, ticker); + exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency); } }); _receiveAddressFocus.addListener(() async { if (!_receiveAddressFocus.hasFocus && receiveAddressController.text.isNotEmpty) { final domain = receiveAddressController.text; - final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase(); - exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, ticker); + exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency); } }); @@ -545,6 +545,12 @@ class ExchangePage extends BasePage { // amount: depositAmountController.text); }); + if (initialPaymentRequest != null) { + exchangeViewModel.receiveCurrency = CryptoCurrency.fromString(initialPaymentRequest!.scheme); + exchangeViewModel.depositAmount = initialPaymentRequest!.amount; + exchangeViewModel.receiveAddress = initialPaymentRequest!.address; + } + _isReactionsSet = true; } @@ -575,8 +581,8 @@ class ExchangePage extends BasePage { } } - Future fetchParsedAddress(BuildContext context, String domain, String ticker) async { - final parsedAddress = await getIt.get().resolve(context, domain, ticker); + Future fetchParsedAddress(BuildContext context, String domain, CryptoCurrency currency) async { + final parsedAddress = await getIt.get().resolve(context, domain, currency); final address = await extractAddressFromParsed(context, parsedAddress); return address; } @@ -663,15 +669,13 @@ class ExchangePage extends BasePage { addressTextFieldValidator: AddressValidator(type: exchangeViewModel.depositCurrency), onPushPasteButton: (context) async { final domain = exchangeViewModel.depositAddress; - final ticker = exchangeViewModel.depositCurrency.title.toLowerCase(); exchangeViewModel.depositAddress = - await fetchParsedAddress(context, domain, ticker); + await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency); }, onPushAddressBookButton: (context) async { final domain = exchangeViewModel.depositAddress; - final ticker = exchangeViewModel.depositCurrency.title.toLowerCase(); exchangeViewModel.depositAddress = - await fetchParsedAddress(context, domain, ticker); + await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency); }, )); @@ -712,15 +716,13 @@ class ExchangePage extends BasePage { addressTextFieldValidator: AddressValidator(type: exchangeViewModel.receiveCurrency), onPushPasteButton: (context) async { final domain = exchangeViewModel.receiveAddress; - final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase(); exchangeViewModel.receiveAddress = - await fetchParsedAddress(context, domain, ticker); + await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency); }, onPushAddressBookButton: (context) async { final domain = exchangeViewModel.receiveAddress; - final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase(); exchangeViewModel.receiveAddress = - await fetchParsedAddress(context, domain, ticker); + await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency); }, )); diff --git a/lib/src/screens/root/root.dart b/lib/src/screens/root/root.dart index e3472f510..afdd14865 100644 --- a/lib/src/screens/root/root.dart +++ b/lib/src/screens/root/root.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/payment_request.dart'; +import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; @@ -25,6 +26,7 @@ class Root extends StatefulWidget { required this.child, required this.navigatorKey, required this.authService, + required this.linkViewModel, }) : super(key: key); final AuthenticationStore authenticationStore; @@ -32,6 +34,7 @@ class Root extends StatefulWidget { final GlobalKey navigatorKey; final AuthService authService; final Widget child; + final LinkViewModel linkViewModel; @override RootState createState() => RootState(); @@ -53,7 +56,6 @@ class RootState extends State with WidgetsBindingObserver { StreamSubscription? stream; ReactionDisposer? _walletReactionDisposer; ReactionDisposer? _deepLinksReactionDisposer; - Uri? launchUri; @override void initState() { @@ -98,7 +100,7 @@ class RootState extends State with WidgetsBindingObserver { void handleDeepLinking(Uri? uri) async { if (uri == null || !mounted) return; - launchUri = uri; + widget.linkViewModel.currentLink = uri; bool requireAuth = await widget.authService.requireAuth(); @@ -112,7 +114,7 @@ class RootState extends State with WidgetsBindingObserver { (AuthenticationState state) { if (state == AuthenticationState.allowed) { if (widget.appStore.wallet == null) { - waitForWalletInstance(context, launchUri!); + waitForWalletInstance(context); } else { _navigateToDeepLinkScreen(); } @@ -150,6 +152,8 @@ class RootState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { + // this only happens when the app has been in the background for some time + // this does NOT trigger when the app is started from the "closed" state! if (_isInactive && !_postFrameCallback && _requestAuth) { _postFrameCallback = true; WidgetsBinding.instance.addPostFrameCallback((_) { @@ -158,40 +162,38 @@ class RootState extends State with WidgetsBindingObserver { arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { if (!isAuthenticatedSuccessfully) { return; + } + final useTotp = widget.appStore.settingsStore.useTOTP2FA; + final shouldUseTotp2FAToAccessWallets = + widget.appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet; + if (useTotp && shouldUseTotp2FAToAccessWallets) { + _reset(); + auth.close( + route: Routes.totpAuthCodePage, + arguments: TotpAuthArgumentsModel( + onTotpAuthenticationFinished: + (bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuth) { + if (!isAuthenticatedSuccessfully) { + return; + } + _reset(); + totpAuth.close( + route: widget.linkViewModel.getRouteToGo(), + arguments: widget.linkViewModel.getRouteArgs(), + ); + widget.linkViewModel.currentLink = null; + }, + isForSetup: false, + isClosable: false, + ), + ); } else { - final useTotp = widget.appStore.settingsStore.useTOTP2FA; - final shouldUseTotp2FAToAccessWallets = - widget.appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet; - if (useTotp && shouldUseTotp2FAToAccessWallets) { - _reset(); - auth.close( - route: Routes.totpAuthCodePage, - arguments: TotpAuthArgumentsModel( - onTotpAuthenticationFinished: - (bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuth) { - if (!isAuthenticatedSuccessfully) { - return; - } - _reset(); - totpAuth.close( - route: _getRouteToGo(), - arguments: - isWalletConnectLink ? launchUri : PaymentRequest.fromUri(launchUri), - ); - launchUri = null; - }, - isForSetup: false, - isClosable: false, - ), - ); - } else { - _reset(); - auth.close( - route: _getRouteToGo(), - arguments: isWalletConnectLink ? launchUri : PaymentRequest.fromUri(launchUri), - ); - launchUri = null; - } + _reset(); + auth.close( + route: widget.linkViewModel.getRouteToGo(), + arguments: widget.linkViewModel.getRouteArgs(), + ); + widget.linkViewModel.currentLink = null; } }, ); @@ -216,36 +218,7 @@ class RootState extends State with WidgetsBindingObserver { _isInactiveController.add(value); } - bool _isValidPaymentUri() => launchUri?.path.isNotEmpty ?? false; - - bool get isWalletConnectLink => launchUri?.authority == 'wc'; - - String? _getRouteToGo() { - if (isWalletConnectLink) { - if (isEVMCompatibleChain(widget.appStore.wallet!.type)) { - _nonETHWalletErrorToast(S.current.switchToEVMCompatibleWallet); - return null; - } - return Routes.walletConnectConnectionsListing; - } else if (_isValidPaymentUri()) { - return Routes.send; - } else { - return null; - } - } - - Future _nonETHWalletErrorToast(String message) async { - Fluttertoast.showToast( - msg: message, - toastLength: Toast.LENGTH_LONG, - gravity: ToastGravity.SNACKBAR, - backgroundColor: Colors.black, - textColor: Colors.white, - fontSize: 16.0, - ); - } - - void waitForWalletInstance(BuildContext context, Uri tempLaunchUri) { + void waitForWalletInstance(BuildContext context) { WidgetsBinding.instance.addPostFrameCallback((_) { if (context.mounted) { _walletReactionDisposer = reaction( @@ -263,14 +236,6 @@ class RootState extends State with WidgetsBindingObserver { } void _navigateToDeepLinkScreen() { - if (_getRouteToGo() != null) { - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.navigatorKey.currentState?.pushNamed( - _getRouteToGo()!, - arguments: isWalletConnectLink ? launchUri : PaymentRequest.fromUri(launchUri), - ); - launchUri = null; - }); - } + widget.linkViewModel.handleLink(); } } diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 648133391..d9b74869f 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -35,6 +35,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:url_launcher/url_launcher.dart'; class SendPage extends BasePage { SendPage({ @@ -420,12 +422,10 @@ class SendPage extends BasePage { } reaction((_) => sendViewModel.state, (ExecutionState state) { - if (dialogContext != null && dialogContext?.mounted == true) { Navigator.of(dialogContext!).pop(); } - if (state is FailureState) { WidgetsBinding.instance.addPostFrameCallback((_) { showPopUp( @@ -460,10 +460,10 @@ class SendPage extends BasePage { outputs: sendViewModel.outputs, rightButtonText: S.of(_dialogContext).send, leftButtonText: S.of(_dialogContext).cancel, - actionRightButton: () { + actionRightButton: () async { Navigator.of(_dialogContext).pop(); sendViewModel.commitTransaction(); - showPopUp( + await showPopUp( context: context, builder: (BuildContext _dialogContext) { return Observer(builder: (_) { @@ -481,12 +481,14 @@ class SendPage extends BasePage { sendViewModel.selectedCryptoCurrency.toString()); final waitMessage = sendViewModel.walletType == WalletType.solana - ? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}' : ''; + ? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}' + : ''; final newContactMessage = newContactAddress != null - ? '\n${S.of(_dialogContext).add_contact_to_address_book}' : ''; + ? '\n${S.of(_dialogContext).add_contact_to_address_book}' + : ''; - final alertContent = + String alertContent = "$successMessage$waitMessage$newContactMessage"; if (newContactAddress != null) { @@ -509,6 +511,10 @@ class SendPage extends BasePage { newContactAddress = null; }); } else { + if (initialPaymentRequest?.callbackMessage?.isNotEmpty ?? + false) { + alertContent = initialPaymentRequest!.callbackMessage!; + } return AlertWithOneAction( alertTitle: '', alertContent: alertContent, @@ -523,6 +529,20 @@ class SendPage extends BasePage { return Offstage(); }); }); + if (state is TransactionCommitted) { + if (initialPaymentRequest?.callbackUrl?.isNotEmpty ?? false) { + // wait a second so it's not as jarring: + await Future.delayed(Duration(seconds: 1)); + try { + launchUrl( + Uri.parse(initialPaymentRequest!.callbackUrl!), + mode: LaunchMode.externalApplication, + ); + } catch (e) { + print(e); + } + } + } }, actionLeftButton: () => Navigator.of(_dialogContext).pop()); }); diff --git a/lib/src/screens/send/widgets/extract_address_from_parsed.dart b/lib/src/screens/send/widgets/extract_address_from_parsed.dart index eb997c11b..9ce3ca2b1 100644 --- a/lib/src/screens/send/widgets/extract_address_from_parsed.dart +++ b/lib/src/screens/send/widgets/extract_address_from_parsed.dart @@ -56,6 +56,11 @@ Future extractAddressFromParsed( profileImageUrl = parsedAddress.profileImageUrl; profileName = parsedAddress.profileName; break; + case ParseFrom.thorChain: + title = S.of(context).address_detected; + content = S.of(context).extracted_address_content('${parsedAddress.name} (ThorChain)'); + address = parsedAddress.addresses.first; + break; case ParseFrom.yatRecord: if (parsedAddress.name.isEmpty) { title = S.of(context).yat_error; diff --git a/lib/src/screens/settings/desktop_settings/desktop_settings_page.dart b/lib/src/screens/settings/desktop_settings/desktop_settings_page.dart index 1d6168e4a..5355b7bb8 100644 --- a/lib/src/screens/settings/desktop_settings/desktop_settings_page.dart +++ b/lib/src/screens/settings/desktop_settings/desktop_settings_page.dart @@ -12,7 +12,6 @@ final _settingsNavigatorKey = GlobalKey(); class DesktopSettingsPage extends StatefulWidget { const DesktopSettingsPage({super.key}); - @override State createState() => _DesktopSettingsPageState(); } @@ -33,22 +32,21 @@ class _DesktopSettingsPageState extends State { return Scaffold( body: Container( height: MediaQuery.of(context).size.height, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + child: Row( children: [ - Padding( - padding: const EdgeInsets.all(24), - child: Text( - S.current.settings, - style: textXLarge(), - ), - ), Expanded( - child: Row( + flex: 1, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ + Padding( + padding: const EdgeInsets.fromLTRB(24, 24, 24, 4), + child: Text( + S.current.settings, + style: textXLarge(), + ), + ), Expanded( - flex: 1, child: ListView.separated( padding: EdgeInsets.only(top: 0), itemBuilder: (_, index) { @@ -78,27 +76,27 @@ class _DesktopSettingsPageState extends State { itemCount: itemCount, ), ), - Flexible( - flex: 2, - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: 500), - child: Navigator( - key: _settingsNavigatorKey, - initialRoute: Routes.empty_no_route, - onGenerateRoute: (settings) => Router.createRoute(settings), - onGenerateInitialRoutes: - (NavigatorState navigator, String initialRouteName) { - return [ - navigator - .widget.onGenerateRoute!(RouteSettings(name: initialRouteName))! - ]; - }, - ), - ), - ) ], ), ), + Flexible( + flex: 2, + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 500), + child: Navigator( + key: _settingsNavigatorKey, + initialRoute: Routes.empty_no_route, + onGenerateRoute: (settings) => Router.createRoute(settings), + onGenerateInitialRoutes: + (NavigatorState navigator, String initialRouteName) { + return [ + navigator + .widget.onGenerateRoute!(RouteSettings(name: initialRouteName))! + ]; + }, + ), + ), + ) ], ), ), diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 601f5d878..9a0c29564 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -171,7 +171,7 @@ class WalletListBodyState extends State { maxLines: null, softWrap: true, style: TextStyle( - fontSize: 20, + fontSize: DeviceInfo.instance.isDesktop ? 18 : 20, fontWeight: FontWeight.w500, color: Theme.of(context) .extension()! diff --git a/lib/utils/payment_request.dart b/lib/utils/payment_request.dart index 00093b413..fe0ecf605 100644 --- a/lib/utils/payment_request.dart +++ b/lib/utils/payment_request.dart @@ -1,19 +1,29 @@ +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/nano/nano.dart'; class PaymentRequest { - PaymentRequest(this.address, this.amount, this.note, this.scheme); + PaymentRequest(this.address, this.amount, this.note, this.scheme, {this.callbackUrl, this.callbackMessage}); factory PaymentRequest.fromUri(Uri? uri) { var address = ""; var amount = ""; var note = ""; var scheme = ""; + String? callbackUrl; + String? callbackMessage; if (uri != null) { - address = uri.path; + address = uri.queryParameters['address'] ?? uri.path; amount = uri.queryParameters['tx_amount'] ?? uri.queryParameters['amount'] ?? ""; note = uri.queryParameters['tx_description'] ?? uri.queryParameters['message'] ?? ""; scheme = uri.scheme; + callbackUrl = uri.queryParameters['callback']; + callbackMessage = uri.queryParameters['callbackMessage']; + } + + if (scheme == "nano-gpt") { + // treat as nano so filling out the address works: + scheme = "nano"; } if (nano != null) { @@ -26,11 +36,20 @@ class PaymentRequest { } } - return PaymentRequest(address, amount, note, scheme); + return PaymentRequest( + address, + amount, + note, + scheme, + callbackUrl: callbackUrl, + callbackMessage: callbackMessage, + ); } final String address; final String amount; final String note; final String scheme; + final String? callbackUrl; + final String? callbackMessage; } diff --git a/lib/view_model/link_view_model.dart b/lib/view_model/link_view_model.dart new file mode 100644 index 000000000..714b57e53 --- /dev/null +++ b/lib/view_model/link_view_model.dart @@ -0,0 +1,118 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:cake_wallet/store/authentication_store.dart'; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/utils/payment_request.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:mobx/mobx.dart'; + +part 'link_view_model.g.dart'; + +class LinkViewModel = LinkViewModelBase with _$LinkViewModel; + +abstract class LinkViewModelBase with Store { + LinkViewModelBase({ + required this.settingsStore, + required this.appStore, + required this.authenticationStore, + required this.navigatorKey, + }) {} + + final SettingsStore settingsStore; + final AppStore appStore; + final AuthenticationStore authenticationStore; + final GlobalKey navigatorKey; + Uri? currentLink; + + bool get _isValidPaymentUri => currentLink?.path.isNotEmpty ?? false; + bool get isWalletConnectLink => currentLink?.authority == 'wc'; + bool get isNanoGptLink => currentLink?.scheme == 'nano-gpt'; + + String? getRouteToGo() { + if (isWalletConnectLink) { + if (!isEVMCompatibleChain(appStore.wallet!.type)) { + _errorToast(S.current.switchToEVMCompatibleWallet); + return null; + } + return Routes.walletConnectConnectionsListing; + } + + if (authenticationStore.state == AuthenticationState.uninitialized) { + return null; + } + + if (isNanoGptLink) { + switch (currentLink?.authority ?? '') { + case "exchange": + return Routes.exchange; + case "send": + return Routes.send; + case "buy": + return Routes.buySellPage; + } + } + + if (_isValidPaymentUri) { + return Routes.send; + } + + return null; + } + + dynamic getRouteArgs() { + if (isWalletConnectLink) { + return currentLink; + } + + if (isNanoGptLink) { + switch (currentLink?.authority ?? '') { + case "exchange": + case "send": + return PaymentRequest.fromUri(currentLink); + case "buy": + return true; + } + } + + if (_isValidPaymentUri) { + return PaymentRequest.fromUri(currentLink); + } + + return null; + } + + Future _errorToast(String message, {double fontSize = 16}) async { + Fluttertoast.showToast( + msg: message, + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.SNACKBAR, + backgroundColor: Colors.black, + textColor: Colors.white, + fontSize: fontSize, + ); + } + + Future handleLink() async { + String? route = getRouteToGo(); + dynamic args = getRouteArgs(); + if (route != null) { + if (appStore.wallet == null) { + return; + } + + if (isNanoGptLink) { + if (route == Routes.buySellPage || route == Routes.exchange) { + await _errorToast(S.current.nano_gpt_thanks_message, fontSize: 14); + } + } + currentLink = null; + navigatorKey.currentState?.pushNamed( + route, + arguments: args, + ); + } + } +} diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index a79baea48..d6f2589c1 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -296,8 +296,8 @@ abstract class OutputBase with Store { Future fetchParsedAddress(BuildContext context) async { final domain = address; - final ticker = cryptoCurrencyHandler().title.toLowerCase(); - parsedAddress = await getIt.get().resolve(context, domain, ticker); + final currency = cryptoCurrencyHandler(); + parsedAddress = await getIt.get().resolve(context, domain, currency); extractedAddress = await extractAddressFromParsed(context, parsedAddress); note = parsedAddress.description; } diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 2a6bf553b..0d53c59cc 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -247,7 +247,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor wallet.type != WalletType.banano && wallet.type != WalletType.solana && wallet.type != WalletType.tron; - + @observable CryptoCurrency selectedCryptoCurrency; @@ -363,7 +363,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } catch (e) { if (e is LedgerException) { final errorCode = e.errorCode.toRadixString(16); - final fallbackMsg = e.message.isNotEmpty ? e.message : "Unexpected Ledger Error Code: $errorCode"; + final fallbackMsg = + e.message.isNotEmpty ? e.message : "Unexpected Ledger Error Code: $errorCode"; final errorMsg = ledgerViewModel.interpretErrorCode(errorCode) ?? fallbackMsg; state = FailureState(errorMsg); @@ -444,7 +445,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor Object _credentials() { final priority = _settingsStore.priority[wallet.type]; - if (priority == null && wallet.type != WalletType.nano && wallet.type != WalletType.banano && wallet.type != WalletType.solana && + if (priority == null && + wallet.type != WalletType.nano && + wallet.type != WalletType.banano && + wallet.type != WalletType.solana && wallet.type != WalletType.tron) { throw Exception('Priority is null for wallet type: ${wallet.type}'); } @@ -570,6 +574,16 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor return errorMessage; } + if (walletType == WalletType.tron) { + if (errorMessage.contains('balance is not sufficient')) { + return S.current.do_not_have_enough_gas_asset(currency.toString()); + } + + if (errorMessage.contains('Transaction expired')) { + return 'An error occurred while processing the transaction. Kindly retry the transaction'; + } + } + if (walletType == WalletType.bitcoin || walletType == WalletType.litecoin || walletType == WalletType.bitcoinCash) { diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 75a78404f..51f61d9e3 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -10,6 +10,7 @@ import cw_monero import device_info_plus import devicelocale import flutter_inappwebview_macos +import flutter_local_authentication import flutter_secure_storage_macos import in_app_review import package_info @@ -26,6 +27,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) + FlutterLocalAuthenticationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalAuthenticationPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index b82513de2..f1f72a818 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -26,6 +26,8 @@ PODS: - flutter_inappwebview_macos (0.0.1): - FlutterMacOS - OrderedSet (~> 5.0) + - flutter_local_authentication (1.2.0): + - FlutterMacOS - flutter_secure_storage_macos (6.1.1): - FlutterMacOS - FlutterMacOS (1.0.0) @@ -56,6 +58,7 @@ DEPENDENCIES: - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - devicelocale (from `Flutter/ephemeral/.symlinks/plugins/devicelocale/macos`) - flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`) + - flutter_local_authentication (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_authentication/macos`) - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - in_app_review (from `Flutter/ephemeral/.symlinks/plugins/in_app_review/macos`) @@ -83,6 +86,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/devicelocale/macos flutter_inappwebview_macos: :path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos + flutter_local_authentication: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_local_authentication/macos flutter_secure_storage_macos: :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos FlutterMacOS: @@ -110,6 +115,7 @@ SPEC CHECKSUMS: device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225 flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d + flutter_local_authentication: 85674893931e1c9cfa7c9e4f5973cb8c56b018b0 flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 in_app_review: a850789fad746e89bce03d4aeee8078b45a53fd0 diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index 0c8973175..cd7f006e6 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -24,7 +24,21 @@ class AppDelegate: FlutterAppDelegate { } result(secRandom(count: count)) - + case "setMinWindowSize": + guard let self = self else { + result(false) + return + } + if let arguments = call.arguments as? [String: Any], + let width = arguments["width"] as? Double, + let height = arguments["height"] as? Double { + DispatchQueue.main.async { + self.mainFlutterWindow?.minSize = CGSize(width: width, height: height) + } + result(true) + } else { + result(false) + } default: result(FlutterMethodNotImplemented) } diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 7025a4f40..ffa899bae 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "يجب أن تكون قيمة المبلغ أكبر من أو تساوي ${minAmount} ${fiatCurrency}", "more_options": "المزيد من الخيارات", "name": "ﻢﺳﺍ", + "nano_gpt_thanks_message": "شكرا لاستخدام nanogpt! تذكر أن تعود إلى المتصفح بعد اكتمال معاملتك!", + "nanogpt_subtitle": "جميع النماذج الأحدث (GPT-4 ، Claude). \\ nno اشتراك ، ادفع مع Crypto.", "nano_current_rep": "الممثل الحالي", "nano_pick_new_rep": "اختر ممثلًا جديدًا", "narrow": "ضيق", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index f8d39ff53..1cde25188 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "Сумата трябва да бъде най-малко ${minAmount} ${fiatCurrency}", "more_options": "Още настройки", "name": "Име", + "nano_gpt_thanks_message": "Благодаря, че използвахте Nanogpt! Не забравяйте да се върнете обратно към браузъра, след като транзакцията ви приключи!", + "nanogpt_subtitle": "Всички най-нови модели (GPT-4, Claude). \\ Nno абонамент, платете с Crypto.", "nano_current_rep": "Настоящ представител", "nano_pick_new_rep": "Изберете нов представител", "narrow": "Тесен", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index cb51a8318..1a2e2a7e4 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "Částka musí být větší nebo rovna ${minAmount} ${fiatCurrency}", "more_options": "Více možností", "name": "název", + "nano_gpt_thanks_message": "Děkujeme za používání Nanogpt! Nezapomeňte se po dokončení transakce vydat zpět do prohlížeče!", + "nanogpt_subtitle": "Všechny nejnovější modely (GPT-4, Claude). \\ Nno předplatné, plaťte krypto.", "nano_current_rep": "Současný zástupce", "nano_pick_new_rep": "Vyberte nového zástupce", "narrow": "Úzký", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 77a6af45f..2cae68a81 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "Der Wert des Betrags muss größer oder gleich ${minAmount} ${fiatCurrency} sein", "more_options": "Weitere Optionen", "name": "Name", + "nano_gpt_thanks_message": "Danke, dass du Nanogpt benutzt hast! Denken Sie daran, nach Abschluss Ihrer Transaktion zurück zum Browser zu gehen!", + "nanogpt_subtitle": "Alle neuesten Modelle (GPT-4, Claude).", "nano_current_rep": "Aktueller Vertreter", "nano_pick_new_rep": "Wählen Sie einen neuen Vertreter aus", "narrow": "Eng", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 618319aa5..835aa156b 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "Value of the amount must be more or equal to ${minAmount} ${fiatCurrency}", "more_options": "More Options", "name": "Name", + "nano_gpt_thanks_message": "Thanks for using NanoGPT! Remember to head back to the browser after your transaction completes!", + "nanogpt_subtitle": "All the newest models (GPT-4, Claude).\\nNo subscription, pay with crypto.", "nano_current_rep": "Current Representative", "nano_pick_new_rep": "Pick a new representative", "narrow": "Narrow", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index ebc39df78..5722712b1 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "El valor de la cantidad debe ser mayor o igual a ${minAmount} ${fiatCurrency}", "more_options": "Más Opciones", "name": "Nombre", + "nano_gpt_thanks_message": "¡Gracias por usar nanogpt! ¡Recuerde regresar al navegador después de que se complete su transacción!", + "nanogpt_subtitle": "Todos los modelos más nuevos (GPT-4, Claude). \\ Nno suscripción, pague con cripto.", "nano_current_rep": "Representante actual", "nano_pick_new_rep": "Elija un nuevo representante", "narrow": "Angosto", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index ab994ea98..33bcaa12c 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "Le montant doit être au moins égal à ${minAmount} ${fiatCurrency}", "more_options": "Plus d'options", "name": "Nom", + "nano_gpt_thanks_message": "Merci d'avoir utilisé Nanogpt! N'oubliez pas de retourner au navigateur une fois votre transaction terminée!", + "nanogpt_subtitle": "Tous les modèles les plus récents (GPT-4, Claude). \\ NNO abonnement, payez avec crypto.", "nano_current_rep": "Représentant actuel", "nano_pick_new_rep": "Choisissez un nouveau représentant", "narrow": "Étroit", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 10aea40f0..957f1c07e 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "Darajar adadin dole ne ya zama fiye ko daidai da ${minAmount} ${fiatCurrency}", "more_options": "Ƙarin Zaɓuɓɓuka", "name": "Suna", + "nano_gpt_thanks_message": "Na gode da amfani da Nanogpt! Ka tuna da komawa zuwa mai bincike bayan ma'amalar ka ta cika!", + "nanogpt_subtitle": "Duk sabbin samfuran (GPT-4, CLODE). \\ NNO biyan kuɗi, biya tare da crypto.", "nano_current_rep": "Wakilin Yanzu", "nano_pick_new_rep": "Dauki sabon wakili", "narrow": "kunkuntar", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 6ba27ea70..d6a3f2d25 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "राशि का मूल्य अधिक है या करने के लिए बराबर होना चाहिए ${minAmount} ${fiatCurrency}", "more_options": "और विकल्प", "name": "नाम", + "nano_gpt_thanks_message": "Nanogpt का उपयोग करने के लिए धन्यवाद! अपने लेन -देन के पूरा होने के बाद ब्राउज़र पर वापस जाना याद रखें!", + "nanogpt_subtitle": "सभी नवीनतम मॉडल (GPT-4, क्लाउड)। \\ nno सदस्यता, क्रिप्टो के साथ भुगतान करें।", "nano_current_rep": "वर्तमान प्रतिनिधि", "nano_pick_new_rep": "एक नया प्रतिनिधि चुनें", "narrow": "सँकरा", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 9c4606186..5e0b18806 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "Vrijednost iznosa mora biti veća ili jednaka ${minAmount} ${fiatCurrency}", "more_options": "Više opcija", "name": "Ime", + "nano_gpt_thanks_message": "Hvala što ste koristili nanogpt! Ne zaboravite da se vratite u preglednik nakon što vam se transakcija završi!", + "nanogpt_subtitle": "Svi najnoviji modeli (GPT-4, Claude). \\ NNO pretplata, plaćajte kripto.", "nano_current_rep": "Trenutni predstavnik", "nano_pick_new_rep": "Odaberite novog predstavnika", "narrow": "Usko", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 54daf7203..03a2288af 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "Nilai jumlah harus lebih atau sama dengan ${minAmount} ${fiatCurrency}", "more_options": "Opsi Lainnya", "name": "Nama", + "nano_gpt_thanks_message": "Terima kasih telah menggunakan Nanogpt! Ingatlah untuk kembali ke browser setelah transaksi Anda selesai!", + "nanogpt_subtitle": "Semua model terbaru (GPT-4, Claude). \\ Nno langganan, bayar dengan crypto.", "nano_current_rep": "Perwakilan saat ini", "nano_pick_new_rep": "Pilih perwakilan baru", "narrow": "Sempit", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 44713aa0d..15702120f 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -368,6 +368,8 @@ "moonpay_alert_text": "Il valore dell'importo deve essere maggiore o uguale a ${minAmount} ${fiatCurrency}", "more_options": "Altre opzioni", "name": "Nome", + "nano_gpt_thanks_message": "Grazie per aver usato il nanogpt! Ricorda di tornare al browser dopo il completamento della transazione!", + "nanogpt_subtitle": "Tutti i modelli più recenti (GPT-4, Claude). Abbonamento nno, paga con cripto.", "nano_current_rep": "Rappresentante attuale", "nano_pick_new_rep": "Scegli un nuovo rappresentante", "narrow": "Stretto", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 4938b7a46..b9340ca01 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -368,6 +368,8 @@ "moonpay_alert_text": "金額の値は以上でなければなりません ${minAmount} ${fiatCurrency}", "more_options": "その他のオプション", "name": "名前", + "nano_gpt_thanks_message": "NanoGptを使用してくれてありがとう!トランザクションが完了したら、ブラウザに戻ることを忘れないでください!", + "nanogpt_subtitle": "すべての最新モデル(GPT-4、Claude)。\\ nnoサブスクリプション、暗号で支払います。", "nano_current_rep": "現在の代表", "nano_pick_new_rep": "新しい代表者を選びます", "narrow": "狭い", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index a37973b23..d69a6f58d 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "금액은 다음보다 크거나 같아야합니다 ${minAmount} ${fiatCurrency}", "more_options": "추가 옵션", "name": "이름", + "nano_gpt_thanks_message": "Nanogpt를 사용해 주셔서 감사합니다! 거래가 완료된 후 브라우저로 돌아가는 것을 잊지 마십시오!", + "nanogpt_subtitle": "모든 최신 모델 (GPT-4, Claude). \\ nno 구독, Crypto로 지불하십시오.", "nano_current_rep": "현재 대표", "nano_pick_new_rep": "새로운 담당자를 선택하십시오", "narrow": "좁은", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 1b0063f71..9e49ed59c 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "ပမာဏ၏တန်ဖိုးသည် ${minAmount} ${fiatCurrency} နှင့် ပိုနေရမည်", "more_options": "နောက်ထပ် ရွေးချယ်စရာများ", "name": "နာမည်", + "nano_gpt_thanks_message": "nanogpt ကိုသုံးပြီးကျေးဇူးတင်ပါတယ် သင်၏ငွေပေးငွေယူပြီးနောက် browser သို့ပြန်သွားရန်သတိရပါ။", + "nanogpt_subtitle": "အားလုံးနောက်ဆုံးပေါ်မော်ဒယ်များ (GPT-4, Claude) ။ \\ nno subscription, crypto နှင့်အတူပေးဆောင်။", "nano_current_rep": "လက်ရှိကိုယ်စားလှယ်", "nano_pick_new_rep": "အသစ်တစ်ခုကိုရွေးပါ", "narrow": "ကျဉ်းသော", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index f0fe390f1..300f70c8b 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "Waarde van het bedrag moet meer of gelijk zijn aan ${minAmount} ${fiatCurrency}", "more_options": "Meer opties", "name": "Naam", + "nano_gpt_thanks_message": "Bedankt voor het gebruik van Nanogpt! Vergeet niet om terug te gaan naar de browser nadat uw transactie is voltooid!", + "nanogpt_subtitle": "Alle nieuwste modellen (GPT-4, Claude). \\ Nno-abonnement, betalen met crypto.", "nano_current_rep": "Huidige vertegenwoordiger", "nano_pick_new_rep": "Kies een nieuwe vertegenwoordiger", "narrow": "Smal", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index ee08a5c3f..f2ade7d64 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "Wartość kwoty musi być większa lub równa ${minAmount} ${fiatCurrency}", "more_options": "Więcej opcji", "name": "Nazwa", + "nano_gpt_thanks_message": "Dzięki za użycie Nanogpt! Pamiętaj, aby wrócić do przeglądarki po zakończeniu transakcji!", + "nanogpt_subtitle": "Wszystkie najnowsze modele (GPT-4, Claude). \\ Nno subskrypcja, płacą za pomocą kryptografii.", "nano_current_rep": "Obecny przedstawiciel", "nano_pick_new_rep": "Wybierz nowego przedstawiciela", "narrow": "Wąski", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 5d9c44d83..224640cc3 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -368,6 +368,8 @@ "moonpay_alert_text": "O valor do montante deve ser maior ou igual a ${minAmount} ${fiatCurrency}", "more_options": "Mais opções", "name": "Nome", + "nano_gpt_thanks_message": "Obrigado por usar o Nanogpt! Lembre -se de voltar para o navegador após a conclusão da transação!", + "nanogpt_subtitle": "Todos os modelos mais recentes (GPT-4, Claude). \\ Nno assinatura, pagam com criptografia.", "nano_current_rep": "Representante atual", "nano_pick_new_rep": "Escolha um novo representante", "narrow": "Estreito", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index ccf1776a4..25d3704c4 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "Сумма должна быть больше или равна ${minAmount} ${fiatCurrency}", "more_options": "Дополнительные параметры", "name": "Имя", + "nano_gpt_thanks_message": "Спасибо за использование Nanogpt! Не забудьте вернуться в браузер после завершения транзакции!", + "nanogpt_subtitle": "Все новейшие модели (GPT-4, Claude). \\ Nno Подписка, платите с крипто.", "nano_current_rep": "Нынешний представитель", "nano_pick_new_rep": "Выберите нового представителя", "narrow": "Узкий", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 1d0f4508c..0cf2305ab 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "มูลค่าของจำนวนต้องมากกว่าหรือเท่ากับ ${minAmount} ${fiatCurrency}", "more_options": "ตัวเลือกเพิ่มเติม", "name": "ชื่อ", + "nano_gpt_thanks_message": "ขอบคุณที่ใช้ Nanogpt! อย่าลืมกลับไปที่เบราว์เซอร์หลังจากการทำธุรกรรมของคุณเสร็จสิ้น!", + "nanogpt_subtitle": "รุ่นใหม่ล่าสุดทั้งหมด (GPT-4, Claude). การสมัครสมาชิก \\ nno, จ่ายด้วย crypto", "nano_current_rep": "ตัวแทนปัจจุบัน", "nano_pick_new_rep": "เลือกตัวแทนใหม่", "narrow": "แคบ", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 628a03b97..4d6b332aa 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "Ang halaga ng halaga ay dapat na higit pa o katumbas ng ${minAmount} ${fiatCurrency}", "more_options": "Higit pang mga pagpipilian", "name": "Pangalan", + "nano_gpt_thanks_message": "Salamat sa paggamit ng nanogpt! Tandaan na bumalik sa browser matapos makumpleto ang iyong transaksyon!", + "nanogpt_subtitle": "Ang lahat ng mga pinakabagong modelo (GPT-4, Claude). \\ Nno subscription, magbayad gamit ang crypto.", "nano_current_rep": "Kasalukuyang kinatawan", "nano_pick_new_rep": "Pumili ng isang bagong kinatawan", "narrow": "Makitid", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 9bb5c2cca..efc695c81 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "Tutar ${minAmount} ${fiatCurrency} miktarına eşit veya daha fazla olmalıdır", "more_options": "Daha Fazla Seçenek", "name": "İsim", + "nano_gpt_thanks_message": "Nanogpt kullandığınız için teşekkürler! İşleminiz tamamlandıktan sonra tarayıcıya geri dönmeyi unutmayın!", + "nanogpt_subtitle": "En yeni modeller (GPT-4, Claude). \\ Nno aboneliği, kripto ile ödeme yapın.", "nano_current_rep": "Mevcut temsilci", "nano_pick_new_rep": "Yeni bir temsilci seçin", "narrow": "Dar", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 0f076ce82..09f9eb604 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "Значення суми має бути більшим або дорівнювати ${minAmount} ${fiatCurrency}", "more_options": "Більше параметрів", "name": "Ім'я", + "nano_gpt_thanks_message": "Дякуємо за використання наногпта! Не забудьте повернутися до браузера після завершення транзакції!", + "nanogpt_subtitle": "Усі найновіші моделі (GPT-4, Claude). \\ Nno підписка, оплата криптовалютою.", "nano_current_rep": "Поточний представник", "nano_pick_new_rep": "Виберіть нового представника", "narrow": "вузькі", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 38bb14d2a..f2728e4cb 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "رقم کی قدر ${minAmount} ${fiatCurrency} کے برابر یا زیادہ ہونی چاہیے۔", "more_options": "مزید زرائے", "name": "ﻡﺎﻧ", + "nano_gpt_thanks_message": "نانوگپٹ استعمال کرنے کا شکریہ! اپنے لین دین کی تکمیل کے بعد براؤزر کی طرف واپس جانا یاد رکھیں!", + "nanogpt_subtitle": "تمام تازہ ترین ماڈل (GPT-4 ، کلاڈ)۔ n n no سبسکرپشن ، کریپٹو کے ساتھ ادائیگی کریں۔", "nano_current_rep": "موجودہ نمائندہ", "nano_pick_new_rep": "ایک نیا نمائندہ منتخب کریں", "narrow": "تنگ", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 58be0fabd..7b4ec3fd5 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -368,6 +368,8 @@ "moonpay_alert_text": "Iye owó kò gbọ́dọ̀ kéré ju ${minAmount} ${fiatCurrency}", "more_options": "Ìyàn àfikún", "name": "Oruko", + "nano_gpt_thanks_message": "O ṣeun fun lilo Nonnogt! Ranti lati tẹle pada si ẹrọ lilọ kiri ayelujara lẹhin iṣowo rẹ pari!", + "nanogpt_subtitle": "Gbogbo awọn awoṣe tuntun (GPT-4, Claude). \\ Nno alabapin kan, sanwo pẹlu Crypto.", "nano_current_rep": "Aṣoju lọwọlọwọ", "nano_pick_new_rep": "Mu aṣoju tuntun kan", "narrow": "Taara", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 3c83942bc..69e0bb85d 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -367,6 +367,8 @@ "moonpay_alert_text": "金额的价值必须大于或等于 ${minAmount} ${fiatCurrency}", "more_options": "更多选项", "name": "姓名", + "nano_gpt_thanks_message": "感谢您使用Nanogpt!事务完成后,请记住回到浏览器!", + "nanogpt_subtitle": "所有最新型号(GPT-4,Claude)。\\ nno订阅,用加密货币付款。", "nano_current_rep": "当前代表", "nano_pick_new_rep": "选择新代表", "narrow": "狭窄的", diff --git a/tool/generate_secrets_config.dart b/tool/generate_secrets_config.dart index 6aaa39b7c..cab41ca69 100644 --- a/tool/generate_secrets_config.dart +++ b/tool/generate_secrets_config.dart @@ -6,6 +6,7 @@ import 'utils/utils.dart'; const configPath = 'tool/.secrets-config.json'; const evmChainsConfigPath = 'tool/.evm-secrets-config.json'; const solanaConfigPath = 'tool/.solana-secrets-config.json'; +const nanoConfigPath = 'tool/.nano-secrets-config.json'; const tronConfigPath = 'tool/.tron-secrets-config.json'; Future main(List args) async => generateSecretsConfig(args); @@ -21,6 +22,7 @@ Future generateSecretsConfig(List args) async { final configFile = File(configPath); final evmChainsConfigFile = File(evmChainsConfigPath); final solanaConfigFile = File(solanaConfigPath); + final nanoConfigFile = File(nanoConfigPath); final tronConfigFile = File(tronConfigPath); final secrets = {}; @@ -42,45 +44,48 @@ Future generateSecretsConfig(List args) async { } } + // base: SecretKey.base.forEach((sec) { if (secrets[sec.name] != null) { return; } - secrets[sec.name] = sec.generate(); }); - var secretsJson = JsonEncoder.withIndent(' ').convert(secrets); await configFile.writeAsString(secretsJson); - secrets.clear(); + // evm chains: SecretKey.evmChainsSecrets.forEach((sec) { if (secrets[sec.name] != null) { return; } - secrets[sec.name] = sec.generate(); }); - secretsJson = JsonEncoder.withIndent(' ').convert(secrets); - await evmChainsConfigFile.writeAsString(secretsJson); - secrets.clear(); + // solana: SecretKey.solanaSecrets.forEach((sec) { if (secrets[sec.name] != null) { return; } - secrets[sec.name] = sec.generate(); }); - secretsJson = JsonEncoder.withIndent(' ').convert(secrets); - await solanaConfigFile.writeAsString(secretsJson); + secrets.clear(); + // nano: + SecretKey.nanoSecrets.forEach((sec) { + if (secrets[sec.name] != null) { + return; + } + secrets[sec.name] = sec.generate(); + }); + secretsJson = JsonEncoder.withIndent(' ').convert(secrets); + await nanoConfigFile.writeAsString(secretsJson); secrets.clear(); SecretKey.tronSecrets.forEach((sec) { @@ -90,8 +95,7 @@ Future generateSecretsConfig(List args) async { secrets[sec.name] = sec.generate(); }); - secretsJson = JsonEncoder.withIndent(' ').convert(secrets); - await tronConfigFile.writeAsString(secretsJson); + secrets.clear(); } diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 542e91b38..89e4de12d 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -50,6 +50,10 @@ class SecretKey { SecretKey('ankrApiKey', () => ''), ]; + static final nanoSecrets = [ + SecretKey('nano2ApiKey', () => ''), + ]; + static final tronSecrets = [ SecretKey('tronGridApiKey', () => ''), ];