Merge branch 'main' into CW-292-Save-historical-fiat-API-rate

This commit is contained in:
Serhii 2024-05-08 16:53:45 +03:00
commit b787810f6a
89 changed files with 1369 additions and 794 deletions

View file

@ -42,7 +42,7 @@ jobs:
- name: Flutter action - name: Flutter action
uses: subosito/flutter-action@v1 uses: subosito/flutter-action@v1
with: with:
flutter-version: "3.10.x" flutter-version: "3.19.5"
channel: stable channel: stable
- name: Install package dependencies - name: Install package dependencies
@ -151,6 +151,7 @@ jobs:
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart 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 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 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 echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
- name: Rename app - name: Rename app

1
.gitignore vendored
View file

@ -94,6 +94,7 @@ android/app/key.jks
**/tool/.evm-secrets-config.json **/tool/.evm-secrets-config.json
**/tool/.ethereum-secrets-config.json **/tool/.ethereum-secrets-config.json
**/tool/.solana-secrets-config.json **/tool/.solana-secrets-config.json
**/tool/.nano-secrets-config.json
**/tool/.tron-secrets-config.json **/tool/.tron-secrets-config.json
**/lib/.secrets.g.dart **/lib/.secrets.g.dart
**/cw_evm/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" />
<data android:scheme="tron_wallet" /> <data android:scheme="tron_wallet" />
</intent-filter> </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> </activity>
<meta-data <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"

View file

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

View file

@ -1,4 +1,8 @@
- -
uri: api.trongrid.io uri: api.trongrid.io
is_default: true is_default: true
useSSL: true
-
uri: tron-rpc.publicnode.com:443
is_default: false
useSSL: true useSSL: true

View file

@ -43,11 +43,14 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.1.11 build_runner: ^2.4.7
build_resolvers: ^2.0.9 build_resolvers: ^2.0.9
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
hive_generator: ^1.1.3 hive_generator: ^1.1.3
dependency_overrides:
watcher: ^1.1.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

View file

@ -39,10 +39,13 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.1.11 build_runner: ^2.4.7
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
hive_generator: ^1.1.3 hive_generator: ^1.1.3
dependency_overrides:
watcher: ^1.1.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # 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)); element.tag == walletCurrency?.tag));
} catch (_) {} } 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) { if (CryptoCurrency._nameCurrencyMap[name.toLowerCase()] == null) {
final s = 'Unexpected token: $name for CryptoCurrency fromString'; final s = 'Unexpected token: $name for CryptoCurrency fromString';
throw ArgumentError.value(name, 'name', s); throw ArgumentError.value(name, 'name', s);
} }
return CryptoCurrency._nameCurrencyMap[name.toLowerCase()]!; 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

@ -28,11 +28,13 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.1.11 build_runner: ^2.4.7
build_resolvers: ^2.0.9 build_resolvers: ^2.0.9
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
hive_generator: ^2.0.1 hive_generator: ^2.0.1
dependency_overrides:
watcher: ^1.1.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

View file

@ -24,11 +24,13 @@ dependency_overrides:
git: git:
url: https://github.com/cake-tech/web3dart.git url: https://github.com/cake-tech/web3dart.git
ref: cake ref: cake
watcher: ^1.1.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.1.11 build_runner: ^2.4.7
flutter: flutter:
# assets: # assets:
# - images/a_dot_burr.jpeg # - images/a_dot_burr.jpeg

View file

@ -36,11 +36,12 @@ dependency_overrides:
git: git:
url: https://github.com/cake-tech/ledger-flutter.git url: https://github.com/cake-tech/ledger-flutter.git
ref: cake ref: cake
watcher: ^1.1.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.1.11 build_runner: ^2.4.7
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
hive_generator: ^1.1.3 hive_generator: ^1.1.3
flutter_lints: ^2.0.0 flutter_lints: ^2.0.0

View file

@ -24,11 +24,14 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.1.11 build_runner: ^2.4.7
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
build_resolvers: ^2.0.9 build_resolvers: ^2.0.9
hive_generator: ^1.1.3 hive_generator: ^1.1.3
dependency_overrides:
watcher: ^1.1.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

View file

@ -26,11 +26,14 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.1.11 build_runner: ^2.4.7
build_resolvers: ^2.0.9 build_resolvers: ^2.0.9
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
hive_generator: ^1.1.3 hive_generator: ^1.1.3
dependency_overrides:
watcher: ^1.1.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

View file

@ -1,12 +1,28 @@
import 'package:cw_core/balance.dart'; import 'package:cw_core/balance.dart';
import 'package:nanoutil/nanoutil.dart'; import 'package:nanoutil/nanoutil.dart';
BigInt stringAmountToBigIntBanano(String amount) {
return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerBanano));
}
class BananoBalance extends Balance { class BananoBalance extends Balance {
final BigInt currentBalance; final BigInt currentBalance;
final BigInt receivableBalance; final BigInt receivableBalance;
BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0); 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 @override
String get formattedAvailableBalance { String get formattedAvailableBalance {
return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerBanano); return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerBanano);

View file

@ -1,7 +1,7 @@
import 'package:cw_core/balance.dart'; import 'package:cw_core/balance.dart';
import 'package:nanoutil/nanoutil.dart'; import 'package:nanoutil/nanoutil.dart';
BigInt stringAmountToBigInt(String amount) { BigInt stringAmountToBigIntNano(String amount) {
return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerNano)); return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerNano));
} }
@ -13,8 +13,8 @@ class NanoBalance extends Balance {
NanoBalance.fromFormattedString( NanoBalance.fromFormattedString(
{required String formattedCurrentBalance, required String formattedReceivableBalance}) {required String formattedCurrentBalance, required String formattedReceivableBalance})
: currentBalance = stringAmountToBigInt(formattedCurrentBalance), : currentBalance = stringAmountToBigIntNano(formattedCurrentBalance),
receivableBalance = stringAmountToBigInt(formattedReceivableBalance), receivableBalance = stringAmountToBigIntNano(formattedReceivableBalance),
super(0, 0); super(0, 0);
NanoBalance.fromRawString( NanoBalance.fromRawString(

View file

@ -10,6 +10,7 @@ import 'package:nanodart/nanodart.dart';
import 'package:cw_core/node.dart'; import 'package:cw_core/node.dart';
import 'package:nanoutil/nanoutil.dart'; import 'package:nanoutil/nanoutil.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:cw_nano/.secrets.g.dart' as secrets;
class NanoClient { class NanoClient {
static const Map<String, String> CAKE_HEADERS = { 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 { Future<NanoBalance> getBalance(String address) async {
final response = await http.post( final response = await http.post(
_node!.uri, _node!.uri,
headers: CAKE_HEADERS, headers: getHeaders(),
body: jsonEncode( body: jsonEncode(
{ {
"action": "account_balance", "action": "account_balance",
@ -82,7 +92,7 @@ class NanoClient {
try { try {
final response = await http.post( final response = await http.post(
_node!.uri, _node!.uri,
headers: CAKE_HEADERS, headers: getHeaders(),
body: jsonEncode( body: jsonEncode(
{ {
"action": "account_info", "action": "account_info",
@ -94,7 +104,7 @@ class NanoClient {
final data = await jsonDecode(response.body); final data = await jsonDecode(response.body);
return AccountInfoResponse.fromJson(data as Map<String, dynamic>); return AccountInfoResponse.fromJson(data as Map<String, dynamic>);
} catch (e) { } catch (e) {
print("error while getting account info"); print("error while getting account info $e");
return null; return null;
} }
} }
@ -149,7 +159,7 @@ class NanoClient {
Future<String> requestWork(String hash) async { Future<String> requestWork(String hash) async {
final response = await http.post( final response = await http.post(
_powNode!.uri, _powNode!.uri,
headers: CAKE_HEADERS, headers: getHeaders(),
body: json.encode( body: json.encode(
{ {
"action": "work_generate", "action": "work_generate",
@ -192,7 +202,7 @@ class NanoClient {
final processResponse = await http.post( final processResponse = await http.post(
_node!.uri, _node!.uri,
headers: CAKE_HEADERS, headers: getHeaders(),
body: processBody, body: processBody,
); );
@ -351,7 +361,7 @@ class NanoClient {
}); });
final processResponse = await http.post( final processResponse = await http.post(
_node!.uri, _node!.uri,
headers: CAKE_HEADERS, headers: getHeaders(),
body: processBody, body: processBody,
); );
@ -367,7 +377,7 @@ class NanoClient {
required String privateKey, required String privateKey,
}) async { }) async {
final receivableResponse = await http.post(_node!.uri, final receivableResponse = await http.post(_node!.uri,
headers: CAKE_HEADERS, headers: getHeaders(),
body: jsonEncode({ body: jsonEncode({
"action": "receivable", "action": "receivable",
"account": destinationAddress, "account": destinationAddress,
@ -417,7 +427,7 @@ class NanoClient {
Future<List<NanoTransactionModel>> fetchTransactions(String address) async { Future<List<NanoTransactionModel>> fetchTransactions(String address) async {
try { try {
final response = await http.post(_node!.uri, final response = await http.post(_node!.uri,
headers: CAKE_HEADERS, headers: getHeaders(),
body: jsonEncode({ body: jsonEncode({
"action": "account_history", "action": "account_history",
"account": address, "account": address,

View file

@ -32,10 +32,13 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.1.11 build_runner: ^2.4.7
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
hive_generator: ^1.1.3 hive_generator: ^1.1.3
dependency_overrides:
watcher: ^1.1.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

View file

@ -28,12 +28,14 @@ dependency_overrides:
git: git:
url: https://github.com/cake-tech/web3dart.git url: https://github.com/cake-tech/web3dart.git
ref: cake ref: cake
watcher: ^1.1.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^2.0.0 flutter_lints: ^2.0.0
build_runner: ^2.1.11 build_runner: ^2.4.7
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

View file

@ -26,10 +26,13 @@ dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^2.0.0 flutter_lints: ^2.0.0
build_runner: ^2.1.11 build_runner: ^2.4.7
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
hive_generator: ^1.1.3 hive_generator: ^1.1.3
dependency_overrides:
watcher: ^1.1.0
flutter: flutter:
# assets: # assets:
# - images/a_dot_burr.jpeg # - images/a_dot_burr.jpeg

View file

@ -367,7 +367,7 @@ class TronClient {
) async { ) async {
// This is introduce to server as a limit in cases where feeLimit is 0 // 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. // The transaction signing will fail if the feeLimit is explicitly 0.
int defaultFeeLimit = 100000; int defaultFeeLimit = 269000;
final block = await _provider!.request(TronRequestGetNowBlock()); final block = await _provider!.request(TronRequestGetNowBlock());
// Create the transfer contract // Create the transfer contract
@ -401,8 +401,9 @@ class TronClient {
final tronBalanceInt = tronBalance.toInt(); final tronBalanceInt = tronBalance.toInt();
if (feeLimit > tronBalanceInt) { if (feeLimit > tronBalanceInt) {
final feeInTrx = TronHelper.fromSun(BigInt.parse(feeLimit.toString()));
throw Exception( 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) { if (!request.isSuccess) {
log("Tron TRC20 error: ${request.error} \n ${request.respose}"); 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( final feeLimit = await getFeeLimit(
@ -454,8 +458,9 @@ class TronClient {
final tronBalanceInt = tronBalance.toInt(); final tronBalanceInt = tronBalance.toInt();
if (feeLimit > tronBalanceInt) { if (feeLimit > tronBalanceInt) {
final feeInTrx = TronHelper.fromSun(BigInt.parse(feeLimit.toString()));
throw Exception( 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> <string>nano-wallet</string>
</array> </array>
</dict> </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> <dict>
<key>CFBundleTypeRole</key> <key>CFBundleTypeRole</key>
<string>Editor</string> <string>Editor</string>

View file

@ -42,12 +42,7 @@ class AuthService with Store {
Future<void> setPassword(String password) async { Future<void> setPassword(String password) async {
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
final encodedPassword = encodedPinCode(pin: password); final encodedPassword = encodedPinCode(pin: password);
// secure storage has a weird bug on macOS, where overwriting a key doesn't work, unless await writeSecureStorage(secureStorage, key: key, value: encodedPassword);
// we delete what's there first:
if (Platform.isMacOS) {
await secureStorage.delete(key: key);
}
await secureStorage.write(key: key, value: encodedPassword);
} }
Future<bool> canAuthenticate() async { Future<bool> canAuthenticate() async {
@ -74,7 +69,11 @@ class AuthService with Store {
void saveLastAuthTime() { void saveLastAuthTime() {
int timestamp = DateTime.now().millisecondsSinceEpoch; int timestamp = DateTime.now().millisecondsSinceEpoch;
secureStorage.write(key: SecureKey.lastAuthTimeMilliseconds, value: timestamp.toString()); writeSecureStorage(
secureStorage,
key: SecureKey.lastAuthTimeMilliseconds,
value: timestamp.toString(),
);
} }
Future<bool> requireAuth() async { Future<bool> requireAuth() async {

View file

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/themes/theme_list.dart'; import 'package:cake_wallet/themes/theme_list.dart';
import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/device_info.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
@ -275,7 +276,7 @@ class BackupService {
if (currentTransactionPriorityKeyLegacy != null) if (currentTransactionPriorityKeyLegacy != null)
await _sharedPreferences.setInt( await _sharedPreferences.setInt(
PreferencesKey.currentTransactionPriorityKeyLegacy, currentTransactionPriorityKeyLegacy); PreferencesKey.currentTransactionPriorityKeyLegacy, currentTransactionPriorityKeyLegacy);
if (currentBitcoinElectrumSererId != null) if (currentBitcoinElectrumSererId != null)
await _sharedPreferences.setInt( await _sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey, currentBitcoinElectrumSererId); PreferencesKey.currentBitcoinElectrumSererIdKey, currentBitcoinElectrumSererId);
@ -373,16 +374,15 @@ class BackupService {
final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword); final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
final backupPassword = keychainJSON[backupPasswordKey] as String; final backupPassword = keychainJSON[backupPasswordKey] as String;
await _flutterSecureStorage.delete(key: backupPasswordKey); await writeSecureStorage(_flutterSecureStorage, key: backupPasswordKey, value: backupPassword);
await _flutterSecureStorage.write(key: backupPasswordKey, value: backupPassword);
keychainWalletsInfo.forEach((dynamic rawInfo) async { keychainWalletsInfo.forEach((dynamic rawInfo) async {
final info = rawInfo as Map<String, dynamic>; final info = rawInfo as Map<String, dynamic>;
await importWalletKeychainInfo(info); await importWalletKeychainInfo(info);
}); });
await _flutterSecureStorage.delete(key: pinCodeKey); await writeSecureStorage(_flutterSecureStorage,
await _flutterSecureStorage.write(key: pinCodeKey, value: encodedPinCode(pin: decodedPin)); key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
keychainDumpFile.deleteSync(); keychainDumpFile.deleteSync();
} }
@ -401,16 +401,15 @@ class BackupService {
final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword); final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
final backupPassword = keychainJSON[backupPasswordKey] as String; final backupPassword = keychainJSON[backupPasswordKey] as String;
await _flutterSecureStorage.delete(key: backupPasswordKey); await writeSecureStorage(_flutterSecureStorage, key: backupPasswordKey, value: backupPassword);
await _flutterSecureStorage.write(key: backupPasswordKey, value: backupPassword);
keychainWalletsInfo.forEach((dynamic rawInfo) async { keychainWalletsInfo.forEach((dynamic rawInfo) async {
final info = rawInfo as Map<String, dynamic>; final info = rawInfo as Map<String, dynamic>;
await importWalletKeychainInfo(info); await importWalletKeychainInfo(info);
}); });
await _flutterSecureStorage.delete(key: pinCodeKey); await writeSecureStorage(_flutterSecureStorage,
await _flutterSecureStorage.write(key: pinCodeKey, value: encodedPinCode(pin: decodedPin)); key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
keychainDumpFile.deleteSync(); keychainDumpFile.deleteSync();
} }

View file

@ -20,8 +20,7 @@ class KeyService {
key: SecretStoreKey.moneroWalletPassword, walletName: walletName); key: SecretStoreKey.moneroWalletPassword, walletName: walletName);
final encodedPassword = encodeWalletPassword(password: password); final encodedPassword = encodeWalletPassword(password: password);
await _secureStorage.delete(key: key); await writeSecureStorage(_secureStorage, key: key, value: encodedPassword);
await _secureStorage.write(key: key, value: encodedPassword);
} }
Future<void> deleteWalletPassword({required String walletName}) async { Future<void> deleteWalletPassword({required String walletName}) async {

View file

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
// For now, we can create a utility function to handle this. // For now, we can create a utility function to handle this.
// //
@ -25,3 +26,13 @@ Future<String?> readSecureStorage(FlutterSecureStorage secureStorage, String key
return result; return result;
} }
Future<void> writeSecureStorage(FlutterSecureStorage secureStorage,
{required String key, required String value}) async {
// delete the value before writing on macOS because of a weird bug
// https://github.com/mogol/flutter_secure_storage/issues/581
if (Platform.isMacOS) {
await secureStorage.delete(key: key);
}
await secureStorage.write(key: key, value: value);
}

View file

@ -26,6 +26,7 @@ import 'package:cake_wallet/entities/contact.dart';
import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart'; import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
import 'package:cw_core/receive_page_option.dart'; import 'package:cw_core/receive_page_option.dart';
@ -268,6 +269,7 @@ Future<void> setup({
required Box<UnspentCoinsInfo> unspentCoinsInfoSource, required Box<UnspentCoinsInfo> unspentCoinsInfoSource,
required Box<AnonpayInvoiceInfo> anonpayInvoiceInfoSource, required Box<AnonpayInvoiceInfo> anonpayInvoiceInfoSource,
required FlutterSecureStorage secureStorage, required FlutterSecureStorage secureStorage,
required GlobalKey<NavigatorState> navigatorKey,
}) async { }) async {
_walletInfoSource = walletInfoSource; _walletInfoSource = walletInfoSource;
_nodeSource = nodeSource; _nodeSource = nodeSource;
@ -431,68 +433,89 @@ Future<void> setup({
), ),
); );
getIt.registerFactory<AuthPage>(() { getIt.registerLazySingleton<LinkViewModel>(() {
return AuthPage(getIt.get<AuthViewModel>(), 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) { onAuthenticationFinished: (isAuthenticated, AuthPageState authPageState) {
if (!isAuthenticated) { if (!isAuthenticated) {
return; return;
} else {
final authStore = getIt.get<AuthenticationStore>();
final appStore = getIt.get<AppStore>();
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); final authStore = getIt.get<AuthenticationStore>();
}, instanceName: 'login'); final appStore = getIt.get<AppStore>();
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<LinkViewModel>();
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<LinkViewModel>();
if (linkViewModel.currentLink != null) {
linkViewModel.handleLink();
}
});
}
});
});
getIt.registerSingleton<BottomSheetService>(BottomSheetServiceImpl()); getIt.registerSingleton<BottomSheetService>(BottomSheetServiceImpl());
@ -851,8 +874,10 @@ Future<void> setup({
tradesStore: getIt.get<TradesStore>(), tradesStore: getIt.get<TradesStore>(),
sendViewModel: getIt.get<SendViewModel>())); sendViewModel: getIt.get<SendViewModel>()));
getIt.registerFactory( getIt.registerFactoryParam<ExchangePage, PaymentRequest?, void>(
() => ExchangePage(getIt.get<ExchangeViewModel>(), getIt.get<AuthService>())); (PaymentRequest? paymentRequest, __) {
return ExchangePage(getIt.get<ExchangeViewModel>(), getIt.get<AuthService>(), paymentRequest);
});
getIt.registerFactory(() => ExchangeConfirmPage(tradesStore: getIt.get<TradesStore>())); getIt.registerFactory(() => ExchangeConfirmPage(tradesStore: getIt.get<TradesStore>()));

View file

@ -1,32 +1,29 @@
import 'package:local_auth/local_auth.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:flutter_local_authentication/flutter_local_authentication.dart';
class BiometricAuth { class BiometricAuth {
final _localAuth = LocalAuthentication(); final _flutterLocalAuthenticationPlugin = FlutterLocalAuthentication();
Future<bool> isAuthenticated() async { Future<bool> isAuthenticated() async {
try { try {
return await _localAuth.authenticate( final authenticated = await _flutterLocalAuthenticationPlugin.authenticate();
localizedReason: S.current.biometric_auth_reason, return authenticated;
options: AuthenticationOptions( } catch (e) {
biometricOnly: true,
useErrorDialogs: true,
stickyAuth: false));
} on PlatformException catch (e) {
print(e); print(e);
} }
return false; return false;
} }
Future<bool> canCheckBiometrics() async { Future<bool> canCheckBiometrics() async {
bool canAuthenticate;
try { try {
return await _localAuth.canCheckBiometrics; canAuthenticate = await _flutterLocalAuthenticationPlugin.canAuthenticate();
} on PlatformException catch (e) { await _flutterLocalAuthenticationPlugin.setTouchIDAuthenticationAllowableReuseDuration(0);
print(e); } catch (error) {
print("Exception checking support. $error");
canAuthenticate = false;
} }
return false; return canAuthenticate;
} }
} }

View file

@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'dart:convert'; import 'dart:convert';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -147,8 +148,8 @@ Future<void> ios_migrate_pin() async {
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
final encodedPassword = encodedPinCode(pin: pinPassword); final encodedPassword = encodedPinCode(pin: pinPassword);
await flutterSecureStorage.delete(key: key); await writeSecureStorage(flutterSecureStorage, key: key, value: encodedPassword);
await flutterSecureStorage.write(key: key, value: encodedPassword);
await prefs.setBool('ios_migration_pin_completed', true); await prefs.setBool('ios_migration_pin_completed', true);
} }

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/cake_hive.dart';
@ -10,8 +11,7 @@ Future<List<int>> getEncryptionKey(
key = CakeHive.generateSecureKey(); key = CakeHive.generateSecureKey();
final keyStringified = key.join(','); final keyStringified = key.join(',');
String storageKey = 'transactionDescriptionsBoxKey'; String storageKey = 'transactionDescriptionsBoxKey';
await secureStorage.delete(key: storageKey); await writeSecureStorage(secureStorage, key: storageKey, value: keyStringified);
await secureStorage.write(key: storageKey, value: keyStringified);
} else { } else {
key = stringifiedKey.split(',').map((i) => int.parse(i)).toList(); key = stringifiedKey.split(',').map((i) => int.parse(i)).toList();
} }

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/parsed_address.dart';
import 'package:cake_wallet/entities/unstoppable_domain_address.dart'; import 'package:cake_wallet/entities/unstoppable_domain_address.dart';
import 'package:cake_wallet/entities/emoji_string_extension.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/mastodon/mastodon_api.dart';
import 'package:cake_wallet/nostr/nostr_api.dart'; import 'package:cake_wallet/nostr/nostr_api.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
@ -71,8 +72,8 @@ class AddressResolver {
return emailRegex.hasMatch(address); 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, CryptoCurrency currency) async {
Future<ParsedAddress> resolve(BuildContext context, String text, String ticker) async { final ticker = currency.title;
try { try {
if (text.startsWith('@') && !text.substring(1).contains('@')) { if (text.startsWith('@') && !text.substring(1).contains('@')) {
if (settingsStore.lookupsTwitter) { if (settingsStore.lookupsTwitter) {
@ -116,8 +117,7 @@ class AddressResolver {
await MastodonAPI.lookupUserByUserName(userName: userName, apiHost: hostName); await MastodonAPI.lookupUserByUserName(userName: userName, apiHost: hostName);
if (mastodonUser != null) { if (mastodonUser != null) {
String? addressFromBio = extractAddressByType( String? addressFromBio = extractAddressByType(raw: mastodonUser.note, type: currency);
raw: mastodonUser.note, type: CryptoCurrency.fromString(ticker));
if (addressFromBio != null) { if (addressFromBio != null) {
return ParsedAddress.fetchMastodonAddress( return ParsedAddress.fetchMastodonAddress(
@ -131,8 +131,8 @@ class AddressResolver {
if (pinnedPosts.isNotEmpty) { if (pinnedPosts.isNotEmpty) {
final userPinnedPostsText = pinnedPosts.map((item) => item.content).join('\n'); final userPinnedPostsText = pinnedPosts.map((item) => item.content).join('\n');
String? addressFromPinnedPost = extractAddressByType( String? addressFromPinnedPost =
raw: userPinnedPostsText, type: CryptoCurrency.fromString(ticker)); extractAddressByType(raw: userPinnedPostsText, type: currency);
if (addressFromPinnedPost != null) { if (addressFromPinnedPost != null) {
return ParsedAddress.fetchMastodonAddress( 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 formattedName = OpenaliasRecord.formatDomainName(text);
final domainParts = formattedName.split('.'); final domainParts = formattedName.split('.');
final name = domainParts.last; final name = domainParts.last;
@ -204,7 +214,7 @@ class AddressResolver {
if (nostrUserData != null) { if (nostrUserData != null) {
String? addressFromBio = extractAddressByType( String? addressFromBio = extractAddressByType(
raw: nostrUserData.about, type: CryptoCurrency.fromString(ticker)); raw: nostrUserData.about, type: currency);
if (addressFromBio != null) { if (addressFromBio != null) {
return ParsedAddress.nostrAddress( return ParsedAddress.nostrAddress(
address: addressFromBio, address: addressFromBio,

View file

@ -11,7 +11,8 @@ enum ParseFrom {
ens, ens,
contact, contact,
mastodon, mastodon,
nostr nostr,
thorChain
} }
class ParsedAddress { 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 List<String> addresses;
final String name; final String name;
final String description; final String description;

View file

@ -34,11 +34,13 @@ class ThorChainExchangeProvider extends ExchangeProvider {
static final isRefundAddressSupported = [CryptoCurrency.eth]; 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 _quotePath = '/thorchain/quote/swap';
static const _txInfoPath = '/thorchain/tx/status/'; static const _txInfoPath = '/thorchain/tx/status/';
static const _affiliateName = 'cakewallet'; static const _affiliateName = 'cakewallet';
static const _affiliateBps = '175'; static const _affiliateBps = '175';
static const _nameLookUpPath= 'v2/thorname/lookup/';
final Box<Trade> tradesStore; final Box<Trade> tradesStore;
@ -154,7 +156,7 @@ class ThorChainExchangeProvider extends ExchangeProvider {
Future<Trade> findTradeById({required String id}) async { Future<Trade> findTradeById({required String id}) async {
if (id.isEmpty) throw Exception('Trade id is empty'); if (id.isEmpty) throw Exception('Trade id is empty');
final formattedId = id.startsWith('0x') ? id.substring(2) : id; 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); final response = await http.get(uri);
if (response.statusCode == 404) { 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 { 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); final response = await http.get(uri);

View file

@ -751,6 +751,50 @@ class HaMaterialLocalizations extends GlobalMaterialLocalizations {
@override @override
String get scrimOnTapHintRaw => "Scrip on Tap"; String get scrimOnTapHintRaw => "Scrip on Tap";
@override
// TODO: implement collapsedHint
String get collapsedHint => "collapsedHint";
@override
// TODO: implement expandedHint
String get expandedHint => "expandedHint";
@override
// TODO: implement expansionTileCollapsedHint
String get expansionTileCollapsedHint => "expansionTileCollapsedHint";
@override
// TODO: implement expansionTileCollapsedTapHint
String get expansionTileCollapsedTapHint => "expansionTileCollapsedTapHint";
@override
// TODO: implement expansionTileExpandedHint
String get expansionTileExpandedHint => "expansionTileExpandedHint";
@override
// TODO: implement expansionTileExpandedTapHint
String get expansionTileExpandedTapHint => "expansionTileExpandedTapHint";
@override
// TODO: implement scanTextButtonLabel
String get scanTextButtonLabel => "scanTextButtonLabel";
@override
// TODO: implement lookUpButtonLabel
String get lookUpButtonLabel => "lookUpButtonLabel";
@override
// TODO: implement menuDismissLabel
String get menuDismissLabel => "menuDismissLabel";
@override
// TODO: implement searchWebButtonLabel
String get searchWebButtonLabel => "searchWebButtonLabel";
@override
// TODO: implement shareButtonLabel
String get shareButtonLabel => "shareButtonLabel";
} }
/// Cupertino Support /// Cupertino Support
@ -955,4 +999,24 @@ class HaCupertinoLocalizations extends GlobalCupertinoLocalizations {
@override @override
String get noSpellCheckReplacementsLabel => ""; String get noSpellCheckReplacementsLabel => "";
@override
// TODO: implement clearButtonLabel
String get clearButtonLabel => "clearButtonLabel";
@override
// TODO: implement lookUpButtonLabel
String get lookUpButtonLabel => "lookUpButtonLabel";
@override
// TODO: implement menuDismissLabel
String get menuDismissLabel => "menuDismissLabel";
@override
// TODO: implement searchWebButtonLabel
String get searchWebButtonLabel => "searchWebButtonLabel";
@override
// TODO: implement shareButtonLabel
String get shareButtonLabel => "shareButtonLabel";
} }

File diff suppressed because it is too large Load diff

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/store/yat/yat_store.dart';
import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/exception_handler.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:cw_core/address_info.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cw_core/hive_type_ids.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:cw_core/unspent_coins_info.dart';
import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/monero/monero.dart';
import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/window_size.dart';
final navigatorKey = GlobalKey<NavigatorState>(); final navigatorKey = GlobalKey<NavigatorState>();
final rootKey = GlobalKey<RootState>(); final rootKey = GlobalKey<RootState>();
@ -60,6 +62,8 @@ Future<void> main() async {
return true; return true;
}; };
await setDefaultMinimumWindowSize();
await CakeHive.close(); await CakeHive.close();
await initializeAppConfigs(); await initializeAppConfigs();
@ -202,18 +206,20 @@ Future<void> initialSetup(
nodes: nodes, nodes: nodes,
powNodes: powNodes); powNodes: powNodes);
await setup( await setup(
walletInfoSource: walletInfoSource, walletInfoSource: walletInfoSource,
nodeSource: nodes, nodeSource: nodes,
powNodeSource: powNodes, powNodeSource: powNodes,
contactSource: contactSource, contactSource: contactSource,
tradesSource: tradesSource, tradesSource: tradesSource,
templates: templates, templates: templates,
exchangeTemplates: exchangeTemplates, exchangeTemplates: exchangeTemplates,
transactionDescriptionBox: transactionDescriptions, transactionDescriptionBox: transactionDescriptions,
ordersSource: ordersSource, ordersSource: ordersSource,
anonpayInvoiceInfoSource: anonpayInvoiceInfo, anonpayInvoiceInfoSource: anonpayInvoiceInfo,
unspentCoinsInfoSource: unspentCoinsInfoSource, unspentCoinsInfoSource: unspentCoinsInfoSource,
secureStorage: secureStorage); secureStorage: secureStorage,
navigatorKey: navigatorKey,
);
await bootstrap(navigatorKey); await bootstrap(navigatorKey);
monero?.onStartup(); monero?.onStartup();
} }
@ -284,6 +290,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
return Observer(builder: (BuildContext context) { return Observer(builder: (BuildContext context) {
final appStore = getIt.get<AppStore>(); final appStore = getIt.get<AppStore>();
final authService = getIt.get<AuthService>(); final authService = getIt.get<AuthService>();
final linkViewModel = getIt.get<LinkViewModel>();
final settingsStore = appStore.settingsStore; final settingsStore = appStore.settingsStore;
final statusBarColor = Colors.transparent; final statusBarColor = Colors.transparent;
final authenticationStore = getIt.get<AuthenticationStore>(); final authenticationStore = getIt.get<AuthenticationStore>();
@ -306,6 +313,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
authenticationStore: authenticationStore, authenticationStore: authenticationStore,
navigatorKey: navigatorKey, navigatorKey: navigatorKey,
authService: authService, authService: authService,
linkViewModel: linkViewModel,
child: MaterialApp( child: MaterialApp(
navigatorObservers: [routeObserver], navigatorObservers: [routeObserver],
navigatorKey: navigatorKey, navigatorKey: navigatorKey,

View file

@ -221,7 +221,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SetupPinCodePage>( builder: (_) => getIt.get<SetupPinCodePage>(
param1: (PinCodeState<PinCodeWidget> context, dynamic _) => 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, fullscreenDialog: true,
); );
@ -231,9 +232,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => ConnectDevicePage( builder: (_) => ConnectDevicePage(
ConnectDevicePageParams( ConnectDevicePageParams(
walletType: availableWalletTypes.first, walletType: availableWalletTypes.first,
onConnectDevice: (BuildContext context, _) => onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed(
Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount, Routes.chooseHardwareWalletAccount,
arguments: [availableWalletTypes.first]), arguments: [availableWalletTypes.first]),
), ),
getIt.get<LedgerViewModel>(), getIt.get<LedgerViewModel>(),
)); ));
@ -243,9 +244,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
param1: (BuildContext context, WalletType type) { param1: (BuildContext context, WalletType type) {
final arguments = ConnectDevicePageParams( final arguments = ConnectDevicePageParams(
walletType: type, walletType: type,
onConnectDevice: (BuildContext context, _) => onConnectDevice: (BuildContext context, _) => Navigator.of(context)
Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount, .pushNamed(Routes.chooseHardwareWalletAccount, arguments: [type]),
arguments: [type]),
); );
Navigator.of(context).pushNamed(Routes.connectDevices, arguments: arguments); Navigator.of(context).pushNamed(Routes.connectDevices, arguments: arguments);
@ -308,8 +308,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.bumpFeePage: case Routes.bumpFeePage:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true,
builder: (_) => builder: (_) => getIt.get<RBFDetailsPage>(param1: settings.arguments as TransactionInfo));
getIt.get<RBFDetailsPage>(param1: settings.arguments as TransactionInfo));
case Routes.newSubaddress: case Routes.newSubaddress:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
@ -461,7 +460,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.exchange: case Routes.exchange:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<ExchangePage>()); fullscreenDialog: true,
builder: (_) => getIt.get<ExchangePage>(param1: settings.arguments as PaymentRequest?),
);
case Routes.exchangeTemplate: case Routes.exchangeTemplate:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTemplatePage>()); return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTemplatePage>());

View file

@ -22,7 +22,7 @@ class DropDownItemWidget extends StatelessWidget {
child: Text( child: Text(
title, title,
style: TextStyle( style: TextStyle(
fontSize: 22, fontSize: 18,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor, color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
), ),

View file

@ -59,12 +59,15 @@ class MarketPlacePage extends StatelessWidget {
// ), // ),
SizedBox(height: 20), SizedBox(height: 20),
DashBoardRoundedCardWidget( DashBoardRoundedCardWidget(
onTap: () => launchUrl(
Uri.https("buy.cakepay.com"),
mode: LaunchMode.externalApplication,
),
title: S.of(context).cake_pay_web_cards_title, title: S.of(context).cake_pay_web_cards_title,
subTitle: S.of(context).cake_pay_web_cards_subtitle, 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"),
), ),
], ],
), ),
@ -76,6 +79,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 // TODO: Remove ionia flow/files if we will discard it
void _navigatorToGiftCardsPage(BuildContext context) { void _navigatorToGiftCardsPage(BuildContext context) {
final walletType = dashboardViewModel.type; 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/extensions/send_page_theme.dart';
import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/debounce.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:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cw_core/sync_status.dart'; import 'package:cw_core/sync_status.dart';
import 'package:cw_core/wallet_type.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'; import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
class ExchangePage extends BasePage { class ExchangePage extends BasePage {
ExchangePage(this.exchangeViewModel, this.authService) { ExchangePage(this.exchangeViewModel, this.authService, this.initialPaymentRequest) {
depositWalletName = exchangeViewModel.depositCurrency == CryptoCurrency.xmr depositWalletName = exchangeViewModel.depositCurrency == CryptoCurrency.xmr
? exchangeViewModel.wallet.name ? exchangeViewModel.wallet.name
: null; : null;
@ -54,6 +55,7 @@ class ExchangePage extends BasePage {
final ExchangeViewModel exchangeViewModel; final ExchangeViewModel exchangeViewModel;
final AuthService authService; final AuthService authService;
final PaymentRequest? initialPaymentRequest;
final depositKey = GlobalKey<ExchangeCardState>(); final depositKey = GlobalKey<ExchangeCardState>();
final receiveKey = GlobalKey<ExchangeCardState>(); final receiveKey = GlobalKey<ExchangeCardState>();
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
@ -330,10 +332,12 @@ class ExchangePage extends BasePage {
void applyTemplate( void applyTemplate(
BuildContext context, ExchangeViewModel exchangeViewModel, ExchangeTemplate template) async { BuildContext context, ExchangeViewModel exchangeViewModel, ExchangeTemplate template) async {
exchangeViewModel.changeDepositCurrency(
currency: CryptoCurrency.fromString(template.depositCurrency)); final depositCryptoCurrency = CryptoCurrency.fromString(template.depositCurrency);
exchangeViewModel.changeReceiveCurrency( final receiveCryptoCurrency = CryptoCurrency.fromString(template.receiveCurrency);
currency: CryptoCurrency.fromString(template.receiveCurrency));
exchangeViewModel.changeDepositCurrency(currency: depositCryptoCurrency);
exchangeViewModel.changeReceiveCurrency(currency: receiveCryptoCurrency);
exchangeViewModel.changeDepositAmount(amount: template.amount); exchangeViewModel.changeDepositAmount(amount: template.amount);
exchangeViewModel.depositAddress = template.depositAddress; exchangeViewModel.depositAddress = template.depositAddress;
@ -342,12 +346,10 @@ class ExchangePage extends BasePage {
exchangeViewModel.isFixedRateMode = false; exchangeViewModel.isFixedRateMode = false;
var domain = template.depositAddress; var domain = template.depositAddress;
var ticker = template.depositCurrency.toLowerCase(); exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, depositCryptoCurrency);
exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, ticker);
domain = template.receiveAddress; domain = template.receiveAddress;
ticker = template.receiveCurrency.toLowerCase(); exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, receiveCryptoCurrency);
exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, ticker);
} }
void _setReactions(BuildContext context, ExchangeViewModel exchangeViewModel) { void _setReactions(BuildContext context, ExchangeViewModel exchangeViewModel) {
@ -519,16 +521,14 @@ class ExchangePage extends BasePage {
_depositAddressFocus.addListener(() async { _depositAddressFocus.addListener(() async {
if (!_depositAddressFocus.hasFocus && depositAddressController.text.isNotEmpty) { if (!_depositAddressFocus.hasFocus && depositAddressController.text.isNotEmpty) {
final domain = depositAddressController.text; final domain = depositAddressController.text;
final ticker = exchangeViewModel.depositCurrency.title.toLowerCase(); exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency);
exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, ticker);
} }
}); });
_receiveAddressFocus.addListener(() async { _receiveAddressFocus.addListener(() async {
if (!_receiveAddressFocus.hasFocus && receiveAddressController.text.isNotEmpty) { if (!_receiveAddressFocus.hasFocus && receiveAddressController.text.isNotEmpty) {
final domain = receiveAddressController.text; final domain = receiveAddressController.text;
final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase(); exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency);
exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, ticker);
} }
}); });
@ -545,6 +545,12 @@ class ExchangePage extends BasePage {
// amount: depositAmountController.text); // amount: depositAmountController.text);
}); });
if (initialPaymentRequest != null) {
exchangeViewModel.receiveCurrency = CryptoCurrency.fromString(initialPaymentRequest!.scheme);
exchangeViewModel.depositAmount = initialPaymentRequest!.amount;
exchangeViewModel.receiveAddress = initialPaymentRequest!.address;
}
_isReactionsSet = true; _isReactionsSet = true;
} }
@ -575,8 +581,8 @@ class ExchangePage extends BasePage {
} }
} }
Future<String> fetchParsedAddress(BuildContext context, String domain, String ticker) async { Future<String> fetchParsedAddress(BuildContext context, String domain, CryptoCurrency currency) async {
final parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, ticker); final parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, currency);
final address = await extractAddressFromParsed(context, parsedAddress); final address = await extractAddressFromParsed(context, parsedAddress);
return address; return address;
} }
@ -663,15 +669,13 @@ class ExchangePage extends BasePage {
addressTextFieldValidator: AddressValidator(type: exchangeViewModel.depositCurrency), addressTextFieldValidator: AddressValidator(type: exchangeViewModel.depositCurrency),
onPushPasteButton: (context) async { onPushPasteButton: (context) async {
final domain = exchangeViewModel.depositAddress; final domain = exchangeViewModel.depositAddress;
final ticker = exchangeViewModel.depositCurrency.title.toLowerCase();
exchangeViewModel.depositAddress = exchangeViewModel.depositAddress =
await fetchParsedAddress(context, domain, ticker); await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency);
}, },
onPushAddressBookButton: (context) async { onPushAddressBookButton: (context) async {
final domain = exchangeViewModel.depositAddress; final domain = exchangeViewModel.depositAddress;
final ticker = exchangeViewModel.depositCurrency.title.toLowerCase();
exchangeViewModel.depositAddress = 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), addressTextFieldValidator: AddressValidator(type: exchangeViewModel.receiveCurrency),
onPushPasteButton: (context) async { onPushPasteButton: (context) async {
final domain = exchangeViewModel.receiveAddress; final domain = exchangeViewModel.receiveAddress;
final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase();
exchangeViewModel.receiveAddress = exchangeViewModel.receiveAddress =
await fetchParsedAddress(context, domain, ticker); await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency);
}, },
onPushAddressBookButton: (context) async { onPushAddressBookButton: (context) async {
final domain = exchangeViewModel.receiveAddress; final domain = exchangeViewModel.receiveAddress;
final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase();
exchangeViewModel.receiveAddress = 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/reactions/wallet_connect.dart';
import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/payment_request.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:cw_core/wallet_base.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
@ -25,6 +26,7 @@ class Root extends StatefulWidget {
required this.child, required this.child,
required this.navigatorKey, required this.navigatorKey,
required this.authService, required this.authService,
required this.linkViewModel,
}) : super(key: key); }) : super(key: key);
final AuthenticationStore authenticationStore; final AuthenticationStore authenticationStore;
@ -32,6 +34,7 @@ class Root extends StatefulWidget {
final GlobalKey<NavigatorState> navigatorKey; final GlobalKey<NavigatorState> navigatorKey;
final AuthService authService; final AuthService authService;
final Widget child; final Widget child;
final LinkViewModel linkViewModel;
@override @override
RootState createState() => RootState(); RootState createState() => RootState();
@ -53,7 +56,6 @@ class RootState extends State<Root> with WidgetsBindingObserver {
StreamSubscription<Uri?>? stream; StreamSubscription<Uri?>? stream;
ReactionDisposer? _walletReactionDisposer; ReactionDisposer? _walletReactionDisposer;
ReactionDisposer? _deepLinksReactionDisposer; ReactionDisposer? _deepLinksReactionDisposer;
Uri? launchUri;
@override @override
void initState() { void initState() {
@ -98,7 +100,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
void handleDeepLinking(Uri? uri) async { void handleDeepLinking(Uri? uri) async {
if (uri == null || !mounted) return; if (uri == null || !mounted) return;
launchUri = uri; widget.linkViewModel.currentLink = uri;
bool requireAuth = await widget.authService.requireAuth(); bool requireAuth = await widget.authService.requireAuth();
@ -112,7 +114,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
(AuthenticationState state) { (AuthenticationState state) {
if (state == AuthenticationState.allowed) { if (state == AuthenticationState.allowed) {
if (widget.appStore.wallet == null) { if (widget.appStore.wallet == null) {
waitForWalletInstance(context, launchUri!); waitForWalletInstance(context);
} else { } else {
_navigateToDeepLinkScreen(); _navigateToDeepLinkScreen();
} }
@ -150,6 +152,8 @@ class RootState extends State<Root> with WidgetsBindingObserver {
@override @override
Widget build(BuildContext context) { 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) { if (_isInactive && !_postFrameCallback && _requestAuth) {
_postFrameCallback = true; _postFrameCallback = true;
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
@ -158,40 +162,38 @@ class RootState extends State<Root> with WidgetsBindingObserver {
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
if (!isAuthenticatedSuccessfully) { if (!isAuthenticatedSuccessfully) {
return; 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 { } else {
final useTotp = widget.appStore.settingsStore.useTOTP2FA; _reset();
final shouldUseTotp2FAToAccessWallets = auth.close(
widget.appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet; route: widget.linkViewModel.getRouteToGo(),
if (useTotp && shouldUseTotp2FAToAccessWallets) { arguments: widget.linkViewModel.getRouteArgs(),
_reset(); );
auth.close( widget.linkViewModel.currentLink = null;
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;
}
} }
}, },
); );
@ -216,36 +218,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
_isInactiveController.add(value); _isInactiveController.add(value);
} }
bool _isValidPaymentUri() => launchUri?.path.isNotEmpty ?? false; void waitForWalletInstance(BuildContext context) {
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) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (context.mounted) { if (context.mounted) {
_walletReactionDisposer = reaction( _walletReactionDisposer = reaction(
@ -263,14 +236,6 @@ class RootState extends State<Root> with WidgetsBindingObserver {
} }
void _navigateToDeepLinkScreen() { void _navigateToDeepLinkScreen() {
if (_getRouteToGo() != null) { widget.linkViewModel.handleLink();
WidgetsBinding.instance.addPostFrameCallback((_) {
widget.navigatorKey.currentState?.pushNamed(
_getRouteToGo()!,
arguments: isWalletConnectLink ? launchUri : PaymentRequest.fromUri(launchUri),
);
launchUri = null;
});
}
} }
} }

View file

@ -35,6 +35,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.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 { class SendPage extends BasePage {
SendPage({ SendPage({
@ -420,12 +422,10 @@ class SendPage extends BasePage {
} }
reaction((_) => sendViewModel.state, (ExecutionState state) { reaction((_) => sendViewModel.state, (ExecutionState state) {
if (dialogContext != null && dialogContext?.mounted == true) { if (dialogContext != null && dialogContext?.mounted == true) {
Navigator.of(dialogContext!).pop(); Navigator.of(dialogContext!).pop();
} }
if (state is FailureState) { if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>( showPopUp<void>(
@ -460,10 +460,10 @@ class SendPage extends BasePage {
outputs: sendViewModel.outputs, outputs: sendViewModel.outputs,
rightButtonText: S.of(_dialogContext).send, rightButtonText: S.of(_dialogContext).send,
leftButtonText: S.of(_dialogContext).cancel, leftButtonText: S.of(_dialogContext).cancel,
actionRightButton: () { actionRightButton: () async {
Navigator.of(_dialogContext).pop(); Navigator.of(_dialogContext).pop();
sendViewModel.commitTransaction(); sendViewModel.commitTransaction();
showPopUp<void>( await showPopUp<void>(
context: context, context: context,
builder: (BuildContext _dialogContext) { builder: (BuildContext _dialogContext) {
return Observer(builder: (_) { return Observer(builder: (_) {
@ -481,12 +481,14 @@ class SendPage extends BasePage {
sendViewModel.selectedCryptoCurrency.toString()); sendViewModel.selectedCryptoCurrency.toString());
final waitMessage = sendViewModel.walletType == WalletType.solana final waitMessage = sendViewModel.walletType == WalletType.solana
? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}' : ''; ? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}'
: '';
final newContactMessage = newContactAddress != null 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"; "$successMessage$waitMessage$newContactMessage";
if (newContactAddress != null) { if (newContactAddress != null) {
@ -509,6 +511,10 @@ class SendPage extends BasePage {
newContactAddress = null; newContactAddress = null;
}); });
} else { } else {
if (initialPaymentRequest?.callbackMessage?.isNotEmpty ??
false) {
alertContent = initialPaymentRequest!.callbackMessage!;
}
return AlertWithOneAction( return AlertWithOneAction(
alertTitle: '', alertTitle: '',
alertContent: alertContent, alertContent: alertContent,
@ -523,6 +529,20 @@ class SendPage extends BasePage {
return Offstage(); 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()); actionLeftButton: () => Navigator.of(_dialogContext).pop());
}); });

View file

@ -56,6 +56,11 @@ Future<String> extractAddressFromParsed(
profileImageUrl = parsedAddress.profileImageUrl; profileImageUrl = parsedAddress.profileImageUrl;
profileName = parsedAddress.profileName; profileName = parsedAddress.profileName;
break; 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: case ParseFrom.yatRecord:
if (parsedAddress.name.isEmpty) { if (parsedAddress.name.isEmpty) {
title = S.of(context).yat_error; title = S.of(context).yat_error;

View file

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

View file

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/entities/pin_code_required_duration.dart'; import 'package:cake_wallet/entities/pin_code_required_duration.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
@ -58,7 +60,7 @@ class SecurityBackupPage extends BasePage {
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings, .shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
), ),
), ),
if (DeviceInfo.instance.isMobile) if (DeviceInfo.instance.isMobile || Platform.isMacOS || Platform.isLinux)
Observer(builder: (_) { Observer(builder: (_) {
return SettingsSwitcherCell( return SettingsSwitcherCell(
title: S.current.settings_allow_biometrical_authentication, title: S.current.settings_allow_biometrical_authentication,

View file

@ -146,7 +146,7 @@ class ConnectScreen extends StatelessWidget {
ElevatedButton( ElevatedButton(
onPressed: connect, onPressed: connect,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
primary: Colors.blue, // primary: Colors.blue,
padding: EdgeInsets.symmetric(horizontal: 40, vertical: 15), padding: EdgeInsets.symmetric(horizontal: 40, vertical: 15),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30), borderRadius: BorderRadius.circular(30),
@ -211,7 +211,7 @@ class DisconnectScreen extends StatelessWidget {
ElevatedButton( ElevatedButton(
onPressed: disconnect, onPressed: disconnect,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
primary: Colors.red, // primary: Colors.red,
padding: EdgeInsets.symmetric(horizontal: 40, vertical: 15), padding: EdgeInsets.symmetric(horizontal: 40, vertical: 15),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30), borderRadius: BorderRadius.circular(30),

View file

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
@ -58,7 +59,6 @@ class ChatwootWidgetState extends State<ChatwootWidget> {
} }
Future<void> storeCookie(String value) async { Future<void> storeCookie(String value) async {
await widget.secureStorage.delete(key: COOKIE_KEY); await writeSecureStorage(widget.secureStorage, key: COOKIE_KEY, value: value);
await widget.secureStorage.write(key: COOKIE_KEY, value: value);
} }
} }

View file

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

View file

@ -1,8 +1,14 @@
import 'package:cake_wallet/core/seed_validator.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class Annotation extends Comparable<Annotation> {
extension Compare<T> on Comparable<T> {
bool operator <=(T other) => compareTo(other) <= 0;
bool operator >=(T other) => compareTo(other) >= 0;
bool operator <(T other) => compareTo(other) < 0;
bool operator >(T other) => compareTo(other) > 0;
}
class Annotation implements Comparable<Annotation> {
Annotation({required this.range, required this.style}); Annotation({required this.range, required this.style});
final TextRange range; final TextRange range;
@ -12,7 +18,7 @@ class Annotation extends Comparable<Annotation> {
int compareTo(Annotation other) => range.start.compareTo(other.range.start); int compareTo(Annotation other) => range.start.compareTo(other.range.start);
} }
class TextAnnotation extends Comparable<TextAnnotation> { class TextAnnotation implements Comparable<TextAnnotation> {
TextAnnotation({required this.text, required this.style}); TextAnnotation({required this.text, required this.style});
final TextStyle style; final TextStyle style;

View file

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/provider_types.dart'; import 'package:cake_wallet/entities/provider_types.dart';
import 'package:cake_wallet/entities/cake_2fa_preset_options.dart'; import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
@ -442,79 +443,83 @@ abstract class SettingsStoreBase with Store {
// secure storage keys: // secure storage keys:
reaction( reaction(
(_) => allowBiometricalAuthentication, (_) => allowBiometricalAuthentication,
(bool biometricalAuthentication) => secureStorage.write( (bool biometricalAuthentication) => writeSecureStorage(secureStorage,
key: SecureKey.allowBiometricalAuthenticationKey, key: SecureKey.allowBiometricalAuthenticationKey,
value: biometricalAuthentication.toString())); value: biometricalAuthentication.toString()));
reaction( reaction(
(_) => selectedCake2FAPreset, (_) => selectedCake2FAPreset,
(Cake2FAPresetsOptions selectedCake2FAPreset) => secureStorage.write( (Cake2FAPresetsOptions selectedCake2FAPreset) => writeSecureStorage(secureStorage,
key: SecureKey.selectedCake2FAPreset, key: SecureKey.selectedCake2FAPreset,
value: selectedCake2FAPreset.serialize().toString())); value: selectedCake2FAPreset.serialize().toString()));
reaction( reaction(
(_) => shouldRequireTOTP2FAForAccessingWallet, (_) => shouldRequireTOTP2FAForAccessingWallet,
(bool requireTOTP2FAForAccessingWallet) => secureStorage.write( (bool requireTOTP2FAForAccessingWallet) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForAccessingWallet, key: SecureKey.shouldRequireTOTP2FAForAccessingWallet,
value: requireTOTP2FAForAccessingWallet.toString())); value: requireTOTP2FAForAccessingWallet.toString()));
reaction( reaction(
(_) => shouldRequireTOTP2FAForSendsToContact, (_) => shouldRequireTOTP2FAForSendsToContact,
(bool requireTOTP2FAForSendsToContact) => secureStorage.write( (bool requireTOTP2FAForSendsToContact) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForSendsToContact, key: SecureKey.shouldRequireTOTP2FAForSendsToContact,
value: requireTOTP2FAForSendsToContact.toString())); value: requireTOTP2FAForSendsToContact.toString()));
reaction( reaction(
(_) => shouldRequireTOTP2FAForSendsToNonContact, (_) => shouldRequireTOTP2FAForSendsToNonContact,
(bool requireTOTP2FAForSendsToNonContact) => secureStorage.write( (bool requireTOTP2FAForSendsToNonContact) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForSendsToNonContact, key: SecureKey.shouldRequireTOTP2FAForSendsToNonContact,
value: requireTOTP2FAForSendsToNonContact.toString())); value: requireTOTP2FAForSendsToNonContact.toString()));
reaction( reaction(
(_) => shouldRequireTOTP2FAForSendsToInternalWallets, (_) => shouldRequireTOTP2FAForSendsToInternalWallets,
(bool requireTOTP2FAForSendsToInternalWallets) => secureStorage.write( (bool requireTOTP2FAForSendsToInternalWallets) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForSendsToInternalWallets, key: SecureKey.shouldRequireTOTP2FAForSendsToInternalWallets,
value: requireTOTP2FAForSendsToInternalWallets.toString())); value: requireTOTP2FAForSendsToInternalWallets.toString()));
reaction( reaction(
(_) => shouldRequireTOTP2FAForExchangesToInternalWallets, (_) => shouldRequireTOTP2FAForExchangesToInternalWallets,
(bool requireTOTP2FAForExchangesToInternalWallets) => secureStorage.write( (bool requireTOTP2FAForExchangesToInternalWallets) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForExchangesToInternalWallets, key: SecureKey.shouldRequireTOTP2FAForExchangesToInternalWallets,
value: requireTOTP2FAForExchangesToInternalWallets.toString())); value: requireTOTP2FAForExchangesToInternalWallets.toString()));
reaction( reaction(
(_) => shouldRequireTOTP2FAForExchangesToExternalWallets, (_) => shouldRequireTOTP2FAForExchangesToExternalWallets,
(bool requireTOTP2FAForExchangesToExternalWallets) => secureStorage.write( (bool requireTOTP2FAForExchangesToExternalWallets) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForExchangesToExternalWallets, key: SecureKey.shouldRequireTOTP2FAForExchangesToExternalWallets,
value: requireTOTP2FAForExchangesToExternalWallets.toString())); value: requireTOTP2FAForExchangesToExternalWallets.toString()));
reaction( reaction(
(_) => shouldRequireTOTP2FAForAddingContacts, (_) => shouldRequireTOTP2FAForAddingContacts,
(bool requireTOTP2FAForAddingContacts) => secureStorage.write( (bool requireTOTP2FAForAddingContacts) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForAddingContacts, key: SecureKey.shouldRequireTOTP2FAForAddingContacts,
value: requireTOTP2FAForAddingContacts.toString())); value: requireTOTP2FAForAddingContacts.toString()));
reaction( reaction(
(_) => shouldRequireTOTP2FAForCreatingNewWallets, (_) => shouldRequireTOTP2FAForCreatingNewWallets,
(bool requireTOTP2FAForCreatingNewWallets) => secureStorage.write( (bool requireTOTP2FAForCreatingNewWallets) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForCreatingNewWallets, key: SecureKey.shouldRequireTOTP2FAForCreatingNewWallets,
value: requireTOTP2FAForCreatingNewWallets.toString())); value: requireTOTP2FAForCreatingNewWallets.toString()));
reaction( reaction(
(_) => shouldRequireTOTP2FAForAllSecurityAndBackupSettings, (_) => shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
(bool requireTOTP2FAForAllSecurityAndBackupSettings) => secureStorage.write( (bool requireTOTP2FAForAllSecurityAndBackupSettings) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings, key: SecureKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
value: requireTOTP2FAForAllSecurityAndBackupSettings.toString())); value: requireTOTP2FAForAllSecurityAndBackupSettings.toString()));
reaction((_) => useTOTP2FA, reaction(
(bool use) => secureStorage.write(key: SecureKey.useTOTP2FA, value: use.toString())); (_) => useTOTP2FA,
(bool use) =>
writeSecureStorage(secureStorage, key: SecureKey.useTOTP2FA, value: use.toString()));
reaction((_) => totpSecretKey, reaction(
(String totpKey) => secureStorage.write(key: SecureKey.totpSecretKey, value: totpKey)); (_) => totpSecretKey,
(String totpKey) =>
writeSecureStorage(secureStorage, key: SecureKey.totpSecretKey, value: totpKey));
reaction( reaction(
(_) => pinTimeOutDuration, (_) => pinTimeOutDuration,
(PinCodeRequiredDuration pinCodeInterval) => secureStorage.write( (PinCodeRequiredDuration pinCodeInterval) => writeSecureStorage(secureStorage,
key: SecureKey.pinTimeOutDuration, value: pinCodeInterval.value.toString())); key: SecureKey.pinTimeOutDuration, value: pinCodeInterval.value.toString()));
reaction( reaction(

View file

@ -1,19 +1,29 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/nano/nano.dart';
class PaymentRequest { 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) { factory PaymentRequest.fromUri(Uri? uri) {
var address = ""; var address = "";
var amount = ""; var amount = "";
var note = ""; var note = "";
var scheme = ""; var scheme = "";
String? callbackUrl;
String? callbackMessage;
if (uri != null) { if (uri != null) {
address = uri.path; address = uri.queryParameters['address'] ?? uri.path;
amount = uri.queryParameters['tx_amount'] ?? uri.queryParameters['amount'] ?? ""; amount = uri.queryParameters['tx_amount'] ?? uri.queryParameters['amount'] ?? "";
note = uri.queryParameters['tx_description'] ?? uri.queryParameters['message'] ?? ""; note = uri.queryParameters['tx_description'] ?? uri.queryParameters['message'] ?? "";
scheme = uri.scheme; 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) { 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 address;
final String amount; final String amount;
final String note; final String note;
final String scheme; final String scheme;
final String? callbackUrl;
final String? callbackMessage;
} }

View file

@ -106,14 +106,10 @@ abstract class AuthViewModelBase with Store {
@action @action
Future<void> biometricAuth() async { Future<void> biometricAuth() async {
try { try {
final canBiometricAuth = await _biometricAuth.canCheckBiometrics(); if (await _biometricAuth.canCheckBiometrics() && await _biometricAuth.isAuthenticated()) {
state = ExecutedSuccessfullyState();
if (canBiometricAuth) { } else {
final isAuthenticated = await _biometricAuth.isAuthenticated(); throw Exception('Biometric authentication failed');
if (isAuthenticated) {
state = ExecutedSuccessfullyState();
}
} }
} catch (e) { } catch (e) {
state = FailureState(e.toString()); state = FailureState(e.toString());

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cake_wallet/entities/secret_store_key.dart';
@ -37,8 +38,7 @@ abstract class EditBackupPasswordViewModelBase with Store {
@action @action
Future<void> save() async { Future<void> save() async {
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword); final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
await secureStorage.delete(key: key); await writeSecureStorage(secureStorage, key: key, value: backupPassword);
await secureStorage.write(key: key, value: backupPassword);
secretStore.write(key: key, value: backupPassword); secretStore.write(key: key, value: backupPassword);
} }
} }

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 { Future<void> fetchParsedAddress(BuildContext context) async {
final domain = address; final domain = address;
final ticker = cryptoCurrencyHandler().title.toLowerCase(); final currency = cryptoCurrencyHandler();
parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, ticker); parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, currency);
extractedAddress = await extractAddressFromParsed(context, parsedAddress); extractedAddress = await extractAddressFromParsed(context, parsedAddress);
note = parsedAddress.description; note = parsedAddress.description;
} }

View file

@ -247,7 +247,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
wallet.type != WalletType.banano && wallet.type != WalletType.banano &&
wallet.type != WalletType.solana && wallet.type != WalletType.solana &&
wallet.type != WalletType.tron; wallet.type != WalletType.tron;
@observable @observable
CryptoCurrency selectedCryptoCurrency; CryptoCurrency selectedCryptoCurrency;
@ -363,7 +363,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
} catch (e) { } catch (e) {
if (e is LedgerException) { if (e is LedgerException) {
final errorCode = e.errorCode.toRadixString(16); 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; final errorMsg = ledgerViewModel.interpretErrorCode(errorCode) ?? fallbackMsg;
state = FailureState(errorMsg); state = FailureState(errorMsg);
@ -444,7 +445,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
Object _credentials() { Object _credentials() {
final priority = _settingsStore.priority[wallet.type]; 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) { wallet.type != WalletType.tron) {
throw Exception('Priority is null for wallet type: ${wallet.type}'); throw Exception('Priority is null for wallet type: ${wallet.type}');
} }
@ -570,6 +574,16 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return errorMessage; 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 || if (walletType == WalletType.bitcoin ||
walletType == WalletType.litecoin || walletType == WalletType.litecoin ||
walletType == WalletType.bitcoinCash) { walletType == WalletType.bitcoinCash) {

View file

@ -10,6 +10,7 @@ import cw_monero
import device_info_plus import device_info_plus
import devicelocale import devicelocale
import flutter_inappwebview_macos import flutter_inappwebview_macos
import flutter_local_authentication
import flutter_secure_storage_macos import flutter_secure_storage_macos
import in_app_review import in_app_review
import package_info import package_info
@ -26,6 +27,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin")) DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin"))
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
FlutterLocalAuthenticationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalAuthenticationPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin")) FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin"))

View file

@ -26,6 +26,8 @@ PODS:
- flutter_inappwebview_macos (0.0.1): - flutter_inappwebview_macos (0.0.1):
- FlutterMacOS - FlutterMacOS
- OrderedSet (~> 5.0) - OrderedSet (~> 5.0)
- flutter_local_authentication (1.2.0):
- FlutterMacOS
- flutter_secure_storage_macos (6.1.1): - flutter_secure_storage_macos (6.1.1):
- FlutterMacOS - FlutterMacOS
- FlutterMacOS (1.0.0) - FlutterMacOS (1.0.0)
@ -56,6 +58,7 @@ DEPENDENCIES:
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- devicelocale (from `Flutter/ephemeral/.symlinks/plugins/devicelocale/macos`) - devicelocale (from `Flutter/ephemeral/.symlinks/plugins/devicelocale/macos`)
- flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/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`) - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
- FlutterMacOS (from `Flutter/ephemeral`) - FlutterMacOS (from `Flutter/ephemeral`)
- in_app_review (from `Flutter/ephemeral/.symlinks/plugins/in_app_review/macos`) - 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 :path: Flutter/ephemeral/.symlinks/plugins/devicelocale/macos
flutter_inappwebview_macos: flutter_inappwebview_macos:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/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: flutter_secure_storage_macos:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos
FlutterMacOS: FlutterMacOS:
@ -110,6 +115,7 @@ SPEC CHECKSUMS:
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225 devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225
flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d
flutter_local_authentication: 85674893931e1c9cfa7c9e4f5973cb8c56b018b0
flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
in_app_review: a850789fad746e89bce03d4aeee8078b45a53fd0 in_app_review: a850789fad746e89bce03d4aeee8078b45a53fd0

View file

@ -24,7 +24,21 @@ class AppDelegate: FlutterAppDelegate {
} }
result(secRandom(count: count)) 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: default:
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
} }

View file

@ -26,15 +26,17 @@ dependencies:
path_provider: ^2.0.11 path_provider: ^2.0.11
mobx: ^2.1.4 mobx: ^2.1.4
flutter_mobx: ^2.0.6+5 flutter_mobx: ^2.0.6+5
flutter_slidable: ^2.0.0 flutter_slidable: ^3.0.1
share_plus: ^4.0.10 share_plus: ^4.0.10
# date_range_picker: ^1.0.6 # date_range_picker: ^1.0.6
#https://api.flutter.dev/flutter/material/showDateRangePicker.html #https://api.flutter.dev/flutter/material/showDateRangePicker.html
dio: ^4.0.6 dio: ^4.0.6
hive: ^2.2.3 hive: ^2.2.3
hive_flutter: ^1.1.0 hive_flutter: ^1.1.0
local_auth: ^2.1.0
local_auth_android: 1.0.21 local_auth_android: 1.0.21
flutter_local_authentication:
git:
url: https://github.com/cake-tech/flutter_local_authentication
package_info: ^2.0.0 package_info: ^2.0.0
#package_info_plus: ^1.4.2 #package_info_plus: ^1.4.2
devicelocale: devicelocale:

View file

@ -4,4 +4,4 @@ version: 0.0.0
publish_to: none publish_to: none
environment: environment:
sdk: ">=2.17.5 <3.0.0" sdk: ">=3.1.0 <4.0.0"

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "يجب أن تكون قيمة المبلغ أكبر من أو تساوي ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "يجب أن تكون قيمة المبلغ أكبر من أو تساوي ${minAmount} ${fiatCurrency}",
"more_options": "المزيد من الخيارات", "more_options": "المزيد من الخيارات",
"name": "ﻢﺳﺍ", "name": "ﻢﺳﺍ",
"nano_gpt_thanks_message": "شكرا لاستخدام nanogpt! تذكر أن تعود إلى المتصفح بعد اكتمال معاملتك!",
"nanogpt_subtitle": "جميع النماذج الأحدث (GPT-4 ، Claude). \\ nno اشتراك ، ادفع مع Crypto.",
"nano_current_rep": "الممثل الحالي", "nano_current_rep": "الممثل الحالي",
"nano_pick_new_rep": "اختر ممثلًا جديدًا", "nano_pick_new_rep": "اختر ممثلًا جديدًا",
"narrow": "ضيق", "narrow": "ضيق",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "Сумата трябва да бъде най-малко ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "Сумата трябва да бъде най-малко ${minAmount} ${fiatCurrency}",
"more_options": "Още настройки", "more_options": "Още настройки",
"name": "Име", "name": "Име",
"nano_gpt_thanks_message": "Благодаря, че използвахте Nanogpt! Не забравяйте да се върнете обратно към браузъра, след като транзакцията ви приключи!",
"nanogpt_subtitle": "Всички най-нови модели (GPT-4, Claude). \\ Nno абонамент, платете с Crypto.",
"nano_current_rep": "Настоящ представител", "nano_current_rep": "Настоящ представител",
"nano_pick_new_rep": "Изберете нов представител", "nano_pick_new_rep": "Изберете нов представител",
"narrow": "Тесен", "narrow": "Тесен",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "Částka musí být větší nebo rovna ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "Částka musí být větší nebo rovna ${minAmount} ${fiatCurrency}",
"more_options": "Více možností", "more_options": "Více možností",
"name": "název", "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_current_rep": "Současný zástupce",
"nano_pick_new_rep": "Vyberte nového zástupce", "nano_pick_new_rep": "Vyberte nového zástupce",
"narrow": "Úzký", "narrow": "Úzký",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "Der Wert des Betrags muss größer oder gleich ${minAmount} ${fiatCurrency} sein", "moonpay_alert_text": "Der Wert des Betrags muss größer oder gleich ${minAmount} ${fiatCurrency} sein",
"more_options": "Weitere Optionen", "more_options": "Weitere Optionen",
"name": "Name", "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_current_rep": "Aktueller Vertreter",
"nano_pick_new_rep": "Wählen Sie einen neuen Vertreter aus", "nano_pick_new_rep": "Wählen Sie einen neuen Vertreter aus",
"narrow": "Eng", "narrow": "Eng",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "Value of the amount must be more or equal to ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "Value of the amount must be more or equal to ${minAmount} ${fiatCurrency}",
"more_options": "More Options", "more_options": "More Options",
"name": "Name", "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_current_rep": "Current Representative",
"nano_pick_new_rep": "Pick a new representative", "nano_pick_new_rep": "Pick a new representative",
"narrow": "Narrow", "narrow": "Narrow",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "El valor de la cantidad debe ser mayor o igual a ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "El valor de la cantidad debe ser mayor o igual a ${minAmount} ${fiatCurrency}",
"more_options": "Más Opciones", "more_options": "Más Opciones",
"name": "Nombre", "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_current_rep": "Representante actual",
"nano_pick_new_rep": "Elija un nuevo representante", "nano_pick_new_rep": "Elija un nuevo representante",
"narrow": "Angosto", "narrow": "Angosto",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "Le montant doit être au moins égal à ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "Le montant doit être au moins égal à ${minAmount} ${fiatCurrency}",
"more_options": "Plus d'options", "more_options": "Plus d'options",
"name": "Nom", "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_current_rep": "Représentant actuel",
"nano_pick_new_rep": "Choisissez un nouveau représentant", "nano_pick_new_rep": "Choisissez un nouveau représentant",
"narrow": "Étroit", "narrow": "Étroit",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "Darajar adadin dole ne ya zama fiye ko daidai da ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "Darajar adadin dole ne ya zama fiye ko daidai da ${minAmount} ${fiatCurrency}",
"more_options": "Ƙarin Zaɓuɓɓuka", "more_options": "Ƙarin Zaɓuɓɓuka",
"name": "Suna", "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_current_rep": "Wakilin Yanzu",
"nano_pick_new_rep": "Dauki sabon wakili", "nano_pick_new_rep": "Dauki sabon wakili",
"narrow": "kunkuntar", "narrow": "kunkuntar",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "राशि का मूल्य अधिक है या करने के लिए बराबर होना चाहिए ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "राशि का मूल्य अधिक है या करने के लिए बराबर होना चाहिए ${minAmount} ${fiatCurrency}",
"more_options": "और विकल्प", "more_options": "और विकल्प",
"name": "नाम", "name": "नाम",
"nano_gpt_thanks_message": "Nanogpt का उपयोग करने के लिए धन्यवाद! अपने लेन -देन के पूरा होने के बाद ब्राउज़र पर वापस जाना याद रखें!",
"nanogpt_subtitle": "सभी नवीनतम मॉडल (GPT-4, क्लाउड)। \\ nno सदस्यता, क्रिप्टो के साथ भुगतान करें।",
"nano_current_rep": "वर्तमान प्रतिनिधि", "nano_current_rep": "वर्तमान प्रतिनिधि",
"nano_pick_new_rep": "एक नया प्रतिनिधि चुनें", "nano_pick_new_rep": "एक नया प्रतिनिधि चुनें",
"narrow": "सँकरा", "narrow": "सँकरा",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "Vrijednost iznosa mora biti veća ili jednaka ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "Vrijednost iznosa mora biti veća ili jednaka ${minAmount} ${fiatCurrency}",
"more_options": "Više opcija", "more_options": "Više opcija",
"name": "Ime", "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_current_rep": "Trenutni predstavnik",
"nano_pick_new_rep": "Odaberite novog predstavnika", "nano_pick_new_rep": "Odaberite novog predstavnika",
"narrow": "Usko", "narrow": "Usko",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "Nilai jumlah harus lebih atau sama dengan ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "Nilai jumlah harus lebih atau sama dengan ${minAmount} ${fiatCurrency}",
"more_options": "Opsi Lainnya", "more_options": "Opsi Lainnya",
"name": "Nama", "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_current_rep": "Perwakilan saat ini",
"nano_pick_new_rep": "Pilih perwakilan baru", "nano_pick_new_rep": "Pilih perwakilan baru",
"narrow": "Sempit", "narrow": "Sempit",

View file

@ -369,6 +369,8 @@
"moonpay_alert_text": "Il valore dell'importo deve essere maggiore o uguale a ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "Il valore dell'importo deve essere maggiore o uguale a ${minAmount} ${fiatCurrency}",
"more_options": "Altre opzioni", "more_options": "Altre opzioni",
"name": "Nome", "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_current_rep": "Rappresentante attuale",
"nano_pick_new_rep": "Scegli un nuovo rappresentante", "nano_pick_new_rep": "Scegli un nuovo rappresentante",
"narrow": "Stretto", "narrow": "Stretto",

View file

@ -369,6 +369,8 @@
"moonpay_alert_text": "金額の値は以上でなければなりません ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "金額の値は以上でなければなりません ${minAmount} ${fiatCurrency}",
"more_options": "その他のオプション", "more_options": "その他のオプション",
"name": "名前", "name": "名前",
"nano_gpt_thanks_message": "NanoGptを使用してくれてありがとうトランザクションが完了したら、ブラウザに戻ることを忘れないでください",
"nanogpt_subtitle": "すべての最新モデルGPT-4、Claude。\\ nnoサブスクリプション、暗号で支払います。",
"nano_current_rep": "現在の代表", "nano_current_rep": "現在の代表",
"nano_pick_new_rep": "新しい代表者を選びます", "nano_pick_new_rep": "新しい代表者を選びます",
"narrow": "狭い", "narrow": "狭い",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "금액은 다음보다 크거나 같아야합니다 ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "금액은 다음보다 크거나 같아야합니다 ${minAmount} ${fiatCurrency}",
"more_options": "추가 옵션", "more_options": "추가 옵션",
"name": "이름", "name": "이름",
"nano_gpt_thanks_message": "Nanogpt를 사용해 주셔서 감사합니다! 거래가 완료된 후 브라우저로 돌아가는 것을 잊지 마십시오!",
"nanogpt_subtitle": "모든 최신 모델 (GPT-4, Claude). \\ nno 구독, Crypto로 지불하십시오.",
"nano_current_rep": "현재 대표", "nano_current_rep": "현재 대표",
"nano_pick_new_rep": "새로운 담당자를 선택하십시오", "nano_pick_new_rep": "새로운 담당자를 선택하십시오",
"narrow": "좁은", "narrow": "좁은",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "ပမာဏ၏တန်ဖိုးသည် ${minAmount} ${fiatCurrency} နှင့် ပိုနေရမည်", "moonpay_alert_text": "ပမာဏ၏တန်ဖိုးသည် ${minAmount} ${fiatCurrency} နှင့် ပိုနေရမည်",
"more_options": "နောက်ထပ် ရွေးချယ်စရာများ", "more_options": "နောက်ထပ် ရွေးချယ်စရာများ",
"name": "နာမည်", "name": "နာမည်",
"nano_gpt_thanks_message": "nanogpt ကိုသုံးပြီးကျေးဇူးတင်ပါတယ် သင်၏ငွေပေးငွေယူပြီးနောက် browser သို့ပြန်သွားရန်သတိရပါ။",
"nanogpt_subtitle": "အားလုံးနောက်ဆုံးပေါ်မော်ဒယ်များ (GPT-4, Claude) ။ \\ nno subscription, crypto နှင့်အတူပေးဆောင်။",
"nano_current_rep": "လက်ရှိကိုယ်စားလှယ်", "nano_current_rep": "လက်ရှိကိုယ်စားလှယ်",
"nano_pick_new_rep": "အသစ်တစ်ခုကိုရွေးပါ", "nano_pick_new_rep": "အသစ်တစ်ခုကိုရွေးပါ",
"narrow": "ကျဉ်းသော", "narrow": "ကျဉ်းသော",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "Waarde van het bedrag moet meer of gelijk zijn aan ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "Waarde van het bedrag moet meer of gelijk zijn aan ${minAmount} ${fiatCurrency}",
"more_options": "Meer opties", "more_options": "Meer opties",
"name": "Naam", "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_current_rep": "Huidige vertegenwoordiger",
"nano_pick_new_rep": "Kies een nieuwe vertegenwoordiger", "nano_pick_new_rep": "Kies een nieuwe vertegenwoordiger",
"narrow": "Smal", "narrow": "Smal",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "Wartość kwoty musi być większa lub równa ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "Wartość kwoty musi być większa lub równa ${minAmount} ${fiatCurrency}",
"more_options": "Więcej opcji", "more_options": "Więcej opcji",
"name": "Nazwa", "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_current_rep": "Obecny przedstawiciel",
"nano_pick_new_rep": "Wybierz nowego przedstawiciela", "nano_pick_new_rep": "Wybierz nowego przedstawiciela",
"narrow": "Wąski", "narrow": "Wąski",

View file

@ -369,6 +369,8 @@
"moonpay_alert_text": "O valor do montante deve ser maior ou igual a ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "O valor do montante deve ser maior ou igual a ${minAmount} ${fiatCurrency}",
"more_options": "Mais opções", "more_options": "Mais opções",
"name": "Nome", "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_current_rep": "Representante atual",
"nano_pick_new_rep": "Escolha um novo representante", "nano_pick_new_rep": "Escolha um novo representante",
"narrow": "Estreito", "narrow": "Estreito",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "Сумма должна быть больше или равна ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "Сумма должна быть больше или равна ${minAmount} ${fiatCurrency}",
"more_options": "Дополнительные параметры", "more_options": "Дополнительные параметры",
"name": "Имя", "name": "Имя",
"nano_gpt_thanks_message": "Спасибо за использование Nanogpt! Не забудьте вернуться в браузер после завершения транзакции!",
"nanogpt_subtitle": "Все новейшие модели (GPT-4, Claude). \\ Nno Подписка, платите с крипто.",
"nano_current_rep": "Нынешний представитель", "nano_current_rep": "Нынешний представитель",
"nano_pick_new_rep": "Выберите нового представителя", "nano_pick_new_rep": "Выберите нового представителя",
"narrow": "Узкий", "narrow": "Узкий",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "มูลค่าของจำนวนต้องมากกว่าหรือเท่ากับ ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "มูลค่าของจำนวนต้องมากกว่าหรือเท่ากับ ${minAmount} ${fiatCurrency}",
"more_options": "ตัวเลือกเพิ่มเติม", "more_options": "ตัวเลือกเพิ่มเติม",
"name": "ชื่อ", "name": "ชื่อ",
"nano_gpt_thanks_message": "ขอบคุณที่ใช้ Nanogpt! อย่าลืมกลับไปที่เบราว์เซอร์หลังจากการทำธุรกรรมของคุณเสร็จสิ้น!",
"nanogpt_subtitle": "รุ่นใหม่ล่าสุดทั้งหมด (GPT-4, Claude). การสมัครสมาชิก \\ nno, จ่ายด้วย crypto",
"nano_current_rep": "ตัวแทนปัจจุบัน", "nano_current_rep": "ตัวแทนปัจจุบัน",
"nano_pick_new_rep": "เลือกตัวแทนใหม่", "nano_pick_new_rep": "เลือกตัวแทนใหม่",
"narrow": "แคบ", "narrow": "แคบ",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "Ang halaga ng halaga ay dapat na higit pa o katumbas ng ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "Ang halaga ng halaga ay dapat na higit pa o katumbas ng ${minAmount} ${fiatCurrency}",
"more_options": "Higit pang mga pagpipilian", "more_options": "Higit pang mga pagpipilian",
"name": "Pangalan", "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_current_rep": "Kasalukuyang kinatawan",
"nano_pick_new_rep": "Pumili ng isang bagong kinatawan", "nano_pick_new_rep": "Pumili ng isang bagong kinatawan",
"narrow": "Makitid", "narrow": "Makitid",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "Tutar ${minAmount} ${fiatCurrency} miktarına eşit veya daha fazla olmalıdır", "moonpay_alert_text": "Tutar ${minAmount} ${fiatCurrency} miktarına eşit veya daha fazla olmalıdır",
"more_options": "Daha Fazla Seçenek", "more_options": "Daha Fazla Seçenek",
"name": "İsim", "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_current_rep": "Mevcut temsilci",
"nano_pick_new_rep": "Yeni bir temsilci seçin", "nano_pick_new_rep": "Yeni bir temsilci seçin",
"narrow": "Dar", "narrow": "Dar",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "Значення суми має бути більшим або дорівнювати ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "Значення суми має бути більшим або дорівнювати ${minAmount} ${fiatCurrency}",
"more_options": "Більше параметрів", "more_options": "Більше параметрів",
"name": "Ім'я", "name": "Ім'я",
"nano_gpt_thanks_message": "Дякуємо за використання наногпта! Не забудьте повернутися до браузера після завершення транзакції!",
"nanogpt_subtitle": "Усі найновіші моделі (GPT-4, Claude). \\ Nno підписка, оплата криптовалютою.",
"nano_current_rep": "Поточний представник", "nano_current_rep": "Поточний представник",
"nano_pick_new_rep": "Виберіть нового представника", "nano_pick_new_rep": "Виберіть нового представника",
"narrow": "вузькі", "narrow": "вузькі",

View file

@ -368,6 +368,8 @@
"moonpay_alert_text": "رقم کی قدر ${minAmount} ${fiatCurrency} کے برابر یا زیادہ ہونی چاہیے۔", "moonpay_alert_text": "رقم کی قدر ${minAmount} ${fiatCurrency} کے برابر یا زیادہ ہونی چاہیے۔",
"more_options": "مزید زرائے", "more_options": "مزید زرائے",
"name": "ﻡﺎﻧ", "name": "ﻡﺎﻧ",
"nano_gpt_thanks_message": "نانوگپٹ استعمال کرنے کا شکریہ! اپنے لین دین کی تکمیل کے بعد براؤزر کی طرف واپس جانا یاد رکھیں!",
"nanogpt_subtitle": "تمام تازہ ترین ماڈل (GPT-4 ، کلاڈ)۔ n n no سبسکرپشن ، کریپٹو کے ساتھ ادائیگی کریں۔",
"nano_current_rep": "موجودہ نمائندہ", "nano_current_rep": "موجودہ نمائندہ",
"nano_pick_new_rep": "ایک نیا نمائندہ منتخب کریں", "nano_pick_new_rep": "ایک نیا نمائندہ منتخب کریں",
"narrow": "تنگ", "narrow": "تنگ",

View file

@ -369,6 +369,8 @@
"moonpay_alert_text": "Iye owó kò gbọ́dọ̀ kéré ju ${minAmount} ${fiatCurrency}", "moonpay_alert_text": "Iye owó kò gbọ́dọ̀ kéré ju ${minAmount} ${fiatCurrency}",
"more_options": "Ìyàn àfikún", "more_options": "Ìyàn àfikún",
"name": "Oruko", "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_current_rep": "Aṣoju lọwọlọwọ",
"nano_pick_new_rep": "Mu aṣoju tuntun kan", "nano_pick_new_rep": "Mu aṣoju tuntun kan",
"narrow": "Taara", "narrow": "Taara",

View file

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

View file

@ -6,6 +6,7 @@ import 'utils/utils.dart';
const configPath = 'tool/.secrets-config.json'; const configPath = 'tool/.secrets-config.json';
const evmChainsConfigPath = 'tool/.evm-secrets-config.json'; const evmChainsConfigPath = 'tool/.evm-secrets-config.json';
const solanaConfigPath = 'tool/.solana-secrets-config.json'; const solanaConfigPath = 'tool/.solana-secrets-config.json';
const nanoConfigPath = 'tool/.nano-secrets-config.json';
const tronConfigPath = 'tool/.tron-secrets-config.json'; const tronConfigPath = 'tool/.tron-secrets-config.json';
Future<void> main(List<String> args) async => generateSecretsConfig(args); 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 configFile = File(configPath);
final evmChainsConfigFile = File(evmChainsConfigPath); final evmChainsConfigFile = File(evmChainsConfigPath);
final solanaConfigFile = File(solanaConfigPath); final solanaConfigFile = File(solanaConfigPath);
final nanoConfigFile = File(nanoConfigPath);
final tronConfigFile = File(tronConfigPath); final tronConfigFile = File(tronConfigPath);
final secrets = <String, dynamic>{}; final secrets = <String, dynamic>{};
@ -42,45 +44,48 @@ Future<void> generateSecretsConfig(List<String> args) async {
} }
} }
// base:
SecretKey.base.forEach((sec) { SecretKey.base.forEach((sec) {
if (secrets[sec.name] != null) { if (secrets[sec.name] != null) {
return; return;
} }
secrets[sec.name] = sec.generate(); secrets[sec.name] = sec.generate();
}); });
var secretsJson = JsonEncoder.withIndent(' ').convert(secrets); var secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
await configFile.writeAsString(secretsJson); await configFile.writeAsString(secretsJson);
secrets.clear(); secrets.clear();
// evm chains:
SecretKey.evmChainsSecrets.forEach((sec) { SecretKey.evmChainsSecrets.forEach((sec) {
if (secrets[sec.name] != null) { if (secrets[sec.name] != null) {
return; return;
} }
secrets[sec.name] = sec.generate(); secrets[sec.name] = sec.generate();
}); });
secretsJson = JsonEncoder.withIndent(' ').convert(secrets); secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
await evmChainsConfigFile.writeAsString(secretsJson); await evmChainsConfigFile.writeAsString(secretsJson);
secrets.clear(); secrets.clear();
// solana:
SecretKey.solanaSecrets.forEach((sec) { SecretKey.solanaSecrets.forEach((sec) {
if (secrets[sec.name] != null) { if (secrets[sec.name] != null) {
return; return;
} }
secrets[sec.name] = sec.generate(); secrets[sec.name] = sec.generate();
}); });
secretsJson = JsonEncoder.withIndent(' ').convert(secrets); secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
await solanaConfigFile.writeAsString(secretsJson); 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(); secrets.clear();
SecretKey.tronSecrets.forEach((sec) { SecretKey.tronSecrets.forEach((sec) {
@ -90,8 +95,7 @@ Future<void> generateSecretsConfig(List<String> args) async {
secrets[sec.name] = sec.generate(); secrets[sec.name] = sec.generate();
}); });
secretsJson = JsonEncoder.withIndent(' ').convert(secrets); secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
await tronConfigFile.writeAsString(secretsJson); await tronConfigFile.writeAsString(secretsJson);
secrets.clear();
} }

View file

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