Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-565-sign-messages [skip ci]

This commit is contained in:
Matthew Fosse 2024-05-07 18:52:36 -07:00
commit 11455a8ba2
62 changed files with 678 additions and 268 deletions

View file

@ -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

1
.gitignore vendored
View file

@ -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

View file

@ -91,6 +91,13 @@
<data android:scheme="tron-wallet" />
<data android:scheme="tron_wallet" />
</intent-filter>
<!-- nano-gpt link scheme -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="nano-gpt" />
</intent-filter>
</activity>
<meta-data
android:name="flutterEmbedding"

View file

@ -0,0 +1,5 @@
-
uri: kaliumapi.appditto.com
path: /api
useSSL: true
is_default: true

View file

@ -2,3 +2,7 @@
uri: api.trongrid.io
is_default: true
useSSL: true
-
uri: tron-rpc.publicnode.com:443
is_default: false
useSSL: true

View file

@ -49,6 +49,9 @@ dev_dependencies:
mobx_codegen: ^2.0.7
hive_generator: ^1.1.3
dependency_overrides:
watcher: ^1.1.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View file

@ -259,10 +259,16 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> 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()]!;
}

View file

@ -0,0 +1,22 @@
import 'dart:io';
import 'package:flutter/services.dart';
const MethodChannel _channel = MethodChannel('com.cake_wallet/native_utils');
Future<void> 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}'.");
}
}

View file

@ -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);

View file

@ -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(

View file

@ -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<String, String> CAKE_HEADERS = {
@ -52,10 +53,19 @@ class NanoClient {
}
}
Map<String, String> getHeaders() {
if (_node!.uri == "https://rpc.nano.to") {
return CAKE_HEADERS..addAll({
"key": secrets.nano2ApiKey,
});
}
return CAKE_HEADERS;
}
Future<NanoBalance> 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<String, dynamic>);
} catch (e) {
print("error while getting account info");
print("error while getting account info $e");
return null;
}
}
@ -170,7 +180,7 @@ class NanoClient {
Future<String> 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<List<NanoTransactionModel>> fetchTransactions(String address) async {
try {
final response = await http.post(_node!.uri,
headers: CAKE_HEADERS,
headers: getHeaders(),
body: jsonEncode({
"action": "account_history",
"account": address,

View file

@ -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',
);
}

View file

@ -140,6 +140,16 @@
<string>nano-wallet</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>nano-gpt</string>
<key>CFBundleURLSchemes</key>
<array>
<string>nano-gpt</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>

View file

@ -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<void> setup({
required Box<UnspentCoinsInfo> unspentCoinsInfoSource,
required Box<AnonpayInvoiceInfo> anonpayInvoiceInfoSource,
required FlutterSecureStorage secureStorage,
required GlobalKey<NavigatorState> navigatorKey,
}) async {
_walletInfoSource = walletInfoSource;
_nodeSource = nodeSource;
@ -432,12 +434,21 @@ Future<void> setup({
),
);
getIt.registerFactory<AuthPage>(() {
return AuthPage(getIt.get<AuthViewModel>(),
getIt.registerLazySingleton<LinkViewModel>(() {
return LinkViewModel(
appStore: getIt.get<AppStore>(),
settingsStore: getIt.get<SettingsStore>(),
authenticationStore: getIt.get<AuthenticationStore>(),
navigatorKey: navigatorKey,
);
});
getIt.registerFactory<AuthPage>(instanceName: 'login', () {
return AuthPage(getIt.get<AuthViewModel>(), closable: false,
onAuthenticationFinished: (isAuthenticated, AuthPageState authPageState) {
if (!isAuthenticated) {
return;
} else {
}
final authStore = getIt.get<AuthenticationStore>();
final appStore = getIt.get<AppStore>();
final useTotp = appStore.settingsStore.useTOTP2FA;
@ -449,8 +460,8 @@ Future<void> setup({
arguments: TotpAuthArgumentsModel(
isForSetup: false,
isClosable: false,
onTotpAuthenticationFinished: (bool isAuthenticatedSuccessfully,
TotpAuthCodePageState totpAuthPageState) async {
onTotpAuthenticationFinished:
(bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuthPageState) async {
if (!isAuthenticatedSuccessfully) {
return;
}
@ -474,11 +485,20 @@ Future<void> setup({
),
);
} else {
// wallet is already loaded:
if (appStore.wallet != null) {
// goes to the dashboard:
authStore.allowed();
// trigger any deep links:
final linkViewModel = getIt.get<LinkViewModel>();
if (linkViewModel.currentLink != null) {
linkViewModel.handleLink();
}
return;
}
// load the wallet:
authPageState.changeProcessText('Loading the wallet');
if (loginError != null) {
@ -489,11 +509,14 @@ Future<void> setup({
_reaction = reaction((_) => appStore.wallet, (Object? _) {
_reaction?.reaction.dispose();
authStore.allowed();
final linkViewModel = getIt.get<LinkViewModel>();
if (linkViewModel.currentLink != null) {
linkViewModel.handleLink();
}
});
}
}
}, closable: false);
}, instanceName: 'login');
});
});
getIt.registerSingleton<BottomSheetService>(BottomSheetServiceImpl());
@ -854,8 +877,10 @@ Future<void> setup({
tradesStore: getIt.get<TradesStore>(),
sendViewModel: getIt.get<SendViewModel>()));
getIt.registerFactory(
() => ExchangePage(getIt.get<ExchangeViewModel>(), getIt.get<AuthService>()));
getIt.registerFactoryParam<ExchangePage, PaymentRequest?, void>(
(PaymentRequest? paymentRequest, __) {
return ExchangePage(getIt.get<ExchangeViewModel>(), getIt.get<AuthService>(), paymentRequest);
});
getIt.registerFactory(() => ExchangeConfirmPage(tradesStore: getIt.get<TradesStore>()));

View file

@ -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<ParsedAddress> resolve(BuildContext context, String text, String ticker) async {
Future<ParsedAddress> 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,

View file

@ -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<String> addresses;
final String name;
final String description;

View file

@ -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<Trade> tradesStore;
@ -154,7 +156,7 @@ class ThorChainExchangeProvider extends ExchangeProvider {
Future<Trade> 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<Map<String, String>?>? 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<String, dynamic>;
final entries = body['entries'] as List<dynamic>?;
if (entries == null || entries.isEmpty) {
return null;
}
Map<String, String> chainToAddressMap = {};
for (final entry in entries) {
final chain = entry['chain'] as String;
final address = entry['address'] as String;
chainToAddressMap[chain] = address;
}
return chainToAddressMap;
}
Future<Map<String, dynamic>> _getSwapQuote(Map<String, String> params) async {
Uri uri = Uri.https(_baseURL, _quotePath, params);
Uri uri = Uri.https(_baseNodeURL, _quotePath, params);
final response = await http.get(uri);

View file

@ -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<NavigatorState>();
final rootKey = GlobalKey<RootState>();
@ -60,6 +62,8 @@ Future<void> main() async {
return true;
};
await setDefaultMinimumWindowSize();
await CakeHive.close();
await initializeAppConfigs();
@ -213,7 +217,9 @@ Future<void> initialSetup(
ordersSource: ordersSource,
anonpayInvoiceInfoSource: anonpayInvoiceInfo,
unspentCoinsInfoSource: unspentCoinsInfoSource,
secureStorage: secureStorage);
secureStorage: secureStorage,
navigatorKey: navigatorKey,
);
await bootstrap(navigatorKey);
monero?.onStartup();
}
@ -284,6 +290,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
return Observer(builder: (BuildContext context) {
final appStore = getIt.get<AppStore>();
final authService = getIt.get<AuthService>();
final linkViewModel = getIt.get<LinkViewModel>();
final settingsStore = appStore.settingsStore;
final statusBarColor = Colors.transparent;
final authenticationStore = getIt.get<AuthenticationStore>();
@ -306,6 +313,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
authenticationStore: authenticationStore,
navigatorKey: navigatorKey,
authService: authService,
linkViewModel: linkViewModel,
child: MaterialApp(
navigatorObservers: [routeObserver],
navigatorKey: navigatorKey,

View file

@ -225,7 +225,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SetupPinCodePage>(
param1: (PinCodeState<PinCodeWidget> context, dynamic _) =>
Navigator.of(context.context).pushNamed(Routes.restoreWalletFromHardwareWallet, arguments: false),
Navigator.of(context.context)
.pushNamed(Routes.restoreWalletFromHardwareWallet, arguments: false),
),
fullscreenDialog: true,
);
@ -235,8 +236,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => ConnectDevicePage(
ConnectDevicePageParams(
walletType: availableWalletTypes.first,
onConnectDevice: (BuildContext context, _) =>
Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount,
onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed(
Routes.chooseHardwareWalletAccount,
arguments: [availableWalletTypes.first]),
),
getIt.get<LedgerViewModel>(),
@ -247,9 +248,8 @@ Route<dynamic> 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<dynamic> createRoute(RouteSettings settings) {
case Routes.exchange:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<ExchangePage>());
fullscreenDialog: true,
builder: (_) => getIt.get<ExchangePage>(param1: settings.arguments as PaymentRequest?),
);
case Routes.exchangeTemplate:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTemplatePage>());

View file

@ -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<CakeTextTheme>()!.titleColor,
),

View file

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

View file

@ -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<ExchangeCardState>();
final receiveKey = GlobalKey<ExchangeCardState>();
final _formKey = GlobalKey<FormState>();
@ -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<String> fetchParsedAddress(BuildContext context, String domain, String ticker) async {
final parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, ticker);
Future<String> fetchParsedAddress(BuildContext context, String domain, CryptoCurrency currency) async {
final parsedAddress = await getIt.get<AddressResolver>().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);
},
));

View file

@ -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<NavigatorState> navigatorKey;
final AuthService authService;
final Widget child;
final LinkViewModel linkViewModel;
@override
RootState createState() => RootState();
@ -53,7 +56,6 @@ class RootState extends State<Root> with WidgetsBindingObserver {
StreamSubscription<Uri?>? stream;
ReactionDisposer? _walletReactionDisposer;
ReactionDisposer? _deepLinksReactionDisposer;
Uri? launchUri;
@override
void initState() {
@ -98,7 +100,7 @@ class RootState extends State<Root> 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<Root> 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<Root> 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,7 +162,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
if (!isAuthenticatedSuccessfully) {
return;
} else {
}
final useTotp = widget.appStore.settingsStore.useTOTP2FA;
final shouldUseTotp2FAToAccessWallets =
widget.appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
@ -174,11 +178,10 @@ class RootState extends State<Root> with WidgetsBindingObserver {
}
_reset();
totpAuth.close(
route: _getRouteToGo(),
arguments:
isWalletConnectLink ? launchUri : PaymentRequest.fromUri(launchUri),
route: widget.linkViewModel.getRouteToGo(),
arguments: widget.linkViewModel.getRouteArgs(),
);
launchUri = null;
widget.linkViewModel.currentLink = null;
},
isForSetup: false,
isClosable: false,
@ -187,11 +190,10 @@ class RootState extends State<Root> with WidgetsBindingObserver {
} else {
_reset();
auth.close(
route: _getRouteToGo(),
arguments: isWalletConnectLink ? launchUri : PaymentRequest.fromUri(launchUri),
route: widget.linkViewModel.getRouteToGo(),
arguments: widget.linkViewModel.getRouteArgs(),
);
launchUri = null;
}
widget.linkViewModel.currentLink = null;
}
},
);
@ -216,36 +218,7 @@ class RootState extends State<Root> 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<void> _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<Root> 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();
}
}

View file

@ -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<void>(
@ -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<void>(
await showPopUp<void>(
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());
});

View file

@ -56,6 +56,11 @@ Future<String> 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;

View file

@ -12,7 +12,6 @@ final _settingsNavigatorKey = GlobalKey<NavigatorState>();
class DesktopSettingsPage extends StatefulWidget {
const DesktopSettingsPage({super.key});
@override
State<DesktopSettingsPage> createState() => _DesktopSettingsPageState();
}
@ -33,22 +32,21 @@ class _DesktopSettingsPageState extends State<DesktopSettingsPage> {
return Scaffold(
body: Container(
height: MediaQuery.of(context).size.height,
child: Row(
children: [
Expanded(
flex: 1,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.fromLTRB(24, 24, 24, 4),
child: Text(
S.current.settings,
style: textXLarge(),
),
),
Expanded(
child: Row(
children: [
Expanded(
flex: 1,
child: ListView.separated(
padding: EdgeInsets.only(top: 0),
itemBuilder: (_, index) {
@ -78,6 +76,9 @@ class _DesktopSettingsPageState extends State<DesktopSettingsPage> {
itemCount: itemCount,
),
),
],
),
),
Flexible(
flex: 2,
child: ConstrainedBox(
@ -99,9 +100,6 @@ class _DesktopSettingsPageState extends State<DesktopSettingsPage> {
],
),
),
],
),
),
);
}
}

View file

@ -171,7 +171,7 @@ class WalletListBodyState extends State<WalletListBody> {
maxLines: null,
softWrap: true,
style: TextStyle(
fontSize: 20,
fontSize: DeviceInfo.instance.isDesktop ? 18 : 20,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<CakeTextTheme>()!

View file

@ -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;
}

View file

@ -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<NavigatorState> 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<void> _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<void> 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,
);
}
}
}

View file

@ -296,8 +296,8 @@ abstract class OutputBase with Store {
Future<void> fetchParsedAddress(BuildContext context) async {
final domain = address;
final ticker = cryptoCurrencyHandler().title.toLowerCase();
parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, ticker);
final currency = cryptoCurrencyHandler();
parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, currency);
extractedAddress = await extractAddressFromParsed(context, parsedAddress);
note = parsedAddress.description;
}

View file

@ -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) {

View file

@ -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"))

View file

@ -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

View file

@ -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)
}

View file

@ -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": "ضيق",

View file

@ -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": "Тесен",

View file

@ -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ý",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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": "सँकरा",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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": "狭い",

View file

@ -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": "좁은",

View file

@ -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": "ကျဉ်းသော",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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": "Узкий",

View file

@ -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": "แคบ",

View file

@ -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",

View file

@ -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",

View file

@ -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": "вузькі",

View file

@ -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": "تنگ",

View file

@ -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",

View file

@ -367,6 +367,8 @@
"moonpay_alert_text": "金额的价值必须大于或等于 ${minAmount} ${fiatCurrency}",
"more_options": "更多选项",
"name": "姓名",
"nano_gpt_thanks_message": "感谢您使用Nanogpt事务完成后请记住回到浏览器",
"nanogpt_subtitle": "所有最新型号GPT-4Claude。\\ nno订阅用加密货币付款。",
"nano_current_rep": "当前代表",
"nano_pick_new_rep": "选择新代表",
"narrow": "狭窄的",

View file

@ -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<void> main(List<String> args) async => generateSecretsConfig(args);
@ -21,6 +22,7 @@ Future<void> generateSecretsConfig(List<String> args) async {
final configFile = File(configPath);
final evmChainsConfigFile = File(evmChainsConfigPath);
final solanaConfigFile = File(solanaConfigPath);
final nanoConfigFile = File(nanoConfigPath);
final tronConfigFile = File(tronConfigPath);
final secrets = <String, dynamic>{};
@ -42,45 +44,48 @@ Future<void> generateSecretsConfig(List<String> 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<void> generateSecretsConfig(List<String> args) async {
secrets[sec.name] = sec.generate();
});
secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
await tronConfigFile.writeAsString(secretsJson);
secrets.clear();
}

View file

@ -50,6 +50,10 @@ class SecretKey {
SecretKey('ankrApiKey', () => ''),
];
static final nanoSecrets = [
SecretKey('nano2ApiKey', () => ''),
];
static final tronSecrets = [
SecretKey('tronGridApiKey', () => ''),
];