mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-04-11 16:51:57 +00:00
commit
3118968230
50 changed files with 2314 additions and 320 deletions
.gitignore
asset_sources/default_themes
ios
lib
main.dart
models/isar/models/blockchain_data
pages
add_wallet_views
new_wallet_recovery_phrase_warning_view
restore_wallet_view
verify_recovery_phrase_view
exchange_view
pinpad_views
settings_views/global_settings_view/stack_backup_views/helpers
wallet_view/transaction_views/tx_v2
wallets_view/sub_widgets
pages_desktop_specific/my_stack_view
providers
services
themes
utilities
wallets
crypto_currency
isar/models
models
wallet
widgets
linux/flutter
macos
pubspec.lockscripts
test
windows/flutter
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -68,6 +68,10 @@ secp256k1.dll
|
|||
/lib/app_config.g.dart
|
||||
/android/app/src/main/app_icon-playstore.png
|
||||
|
||||
# Dart generated files (Freezed, Riverpod, GoRouter etc..)
|
||||
lib/**/*.g.dart
|
||||
lib/**/*.freezed.dart
|
||||
|
||||
## other generated project files
|
||||
|
||||
pubspec.yaml
|
||||
|
@ -104,3 +108,4 @@ scripts/linux/build/libsecret/subprojects/gi-docgen/.meson-subproject-wrap-hash.
|
|||
|
||||
crypto_plugins/cs_monero/built_outputs
|
||||
crypto_plugins/cs_monero/build
|
||||
crypto_plugins/*.diff
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -109,6 +109,8 @@ PODS:
|
|||
- Flutter
|
||||
- wakelock_plus (0.0.1):
|
||||
- Flutter
|
||||
- xelis_flutter (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`)
|
||||
|
@ -138,6 +140,7 @@ DEPENDENCIES:
|
|||
- tor_ffi_plugin (from `.symlinks/plugins/tor_ffi_plugin/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||
- xelis_flutter (from `.symlinks/plugins/xelis_flutter/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
|
@ -205,6 +208,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
wakelock_plus:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
xelis_flutter:
|
||||
:path: ".symlinks/plugins/xelis_flutter/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0
|
||||
|
|
351
lib/main.dart
351
lib/main.dart
|
@ -22,8 +22,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:keyboard_dismisser/keyboard_dismisser.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:window_size/window_size.dart';
|
||||
import 'package:xelis_flutter/src/api/api.dart' as xelis_api;
|
||||
import 'package:xelis_flutter/src/api/logger.dart' as xelis_logging;
|
||||
import 'package:xelis_flutter/src/frb_generated.dart' as xelis_rust;
|
||||
|
||||
import 'app_config.dart';
|
||||
import 'db/db_version_migration.dart';
|
||||
|
@ -74,13 +78,44 @@ import 'wallets/isar/providers/all_wallets_info_provider.dart';
|
|||
import 'wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||
import 'widgets/crypto_notifications.dart';
|
||||
|
||||
final openedFromSWBFileStringStateProvider =
|
||||
StateProvider<String?>((ref) => null);
|
||||
final openedFromSWBFileStringStateProvider = StateProvider<String?>(
|
||||
(ref) => null,
|
||||
);
|
||||
|
||||
void startListeningToRustLogs() {
|
||||
xelis_api.createLogStream().listen(
|
||||
(logEntry) {
|
||||
final Level level;
|
||||
switch (logEntry.level) {
|
||||
case xelis_logging.Level.error:
|
||||
level = Level.error;
|
||||
case xelis_logging.Level.warn:
|
||||
level = Level.warning;
|
||||
case xelis_logging.Level.info:
|
||||
level = Level.info;
|
||||
case xelis_logging.Level.debug:
|
||||
level = Level.debug;
|
||||
case xelis_logging.Level.trace:
|
||||
level = Level.trace;
|
||||
}
|
||||
|
||||
Logging.instance.log(
|
||||
level,
|
||||
"[Xelis Rust Log] ${logEntry.tag}: ${logEntry.msg}",
|
||||
);
|
||||
},
|
||||
onError: (dynamic e) {
|
||||
Logging.instance.e("Error receiving Xelis Rust logs: $e");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// main() is the entry point to the app. It initializes Hive (local database),
|
||||
// runs the MyApp widget and checks for new users, caching the value in the
|
||||
// miscellaneous box for later use
|
||||
void main(List<String> args) async {
|
||||
// talker.info('initializing Rust lib ...');
|
||||
await xelis_rust.RustLib.init();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
if (Util.isDesktop && args.length == 2 && args.first == "-d") {
|
||||
|
@ -108,9 +143,7 @@ void main(List<String> args) async {
|
|||
if (screenHeight != null) {
|
||||
// starting to height be 3/4 screen height or 900, whichever is smaller
|
||||
final height = min<double>(screenHeight * 0.75, 900);
|
||||
setWindowFrame(
|
||||
Rect.fromLTWH(0, 0, 1220, height),
|
||||
);
|
||||
setWindowFrame(Rect.fromLTWH(0, 0, 1220, height));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,8 +179,9 @@ void main(List<String> args) async {
|
|||
// node model adapter
|
||||
DB.instance.hive.registerAdapter(NodeModelAdapter());
|
||||
|
||||
if (!DB.instance.hive
|
||||
.isAdapterRegistered(lib_monero_compat.WalletInfoAdapter().typeId)) {
|
||||
if (!DB.instance.hive.isAdapterRegistered(
|
||||
lib_monero_compat.WalletInfoAdapter().typeId,
|
||||
)) {
|
||||
DB.instance.hive.registerAdapter(lib_monero_compat.WalletInfoAdapter());
|
||||
}
|
||||
|
||||
|
@ -168,6 +202,9 @@ void main(List<String> args) async {
|
|||
level: Prefs.instance.logLevel,
|
||||
);
|
||||
|
||||
await xelis_api.setUpRustLogger();
|
||||
startListeningToRustLogs();
|
||||
|
||||
// setup lib spark logging
|
||||
initSparkLogging(Prefs.instance.logLevel);
|
||||
|
||||
|
@ -194,10 +231,12 @@ void main(List<String> args) async {
|
|||
|
||||
// Desktop migrate handled elsewhere (currently desktop_login_view.dart)
|
||||
if (!Util.isDesktop) {
|
||||
final int dbVersion = DB.instance.get<dynamic>(
|
||||
boxName: DB.boxNameDBInfo,
|
||||
key: "hive_data_version",
|
||||
) as int? ??
|
||||
final int dbVersion =
|
||||
DB.instance.get<dynamic>(
|
||||
boxName: DB.boxNameDBInfo,
|
||||
key: "hive_data_version",
|
||||
)
|
||||
as int? ??
|
||||
0;
|
||||
if (dbVersion < Constants.currentDataVersion) {
|
||||
try {
|
||||
|
@ -232,22 +271,25 @@ void main(List<String> args) async {
|
|||
|
||||
// verify current user preference theme and revert to default
|
||||
// if problems are found to prevent app being unusable
|
||||
if (!(await ThemeService.instance
|
||||
.verifyInstalled(themeId: Prefs.instance.themeId))) {
|
||||
if (!(await ThemeService.instance.verifyInstalled(
|
||||
themeId: Prefs.instance.themeId,
|
||||
))) {
|
||||
Prefs.instance.themeId = "light";
|
||||
}
|
||||
|
||||
// verify current user preference light brightness theme and revert to default
|
||||
// if problems are found to prevent app being unusable
|
||||
if (!(await ThemeService.instance
|
||||
.verifyInstalled(themeId: Prefs.instance.systemBrightnessLightThemeId))) {
|
||||
if (!(await ThemeService.instance.verifyInstalled(
|
||||
themeId: Prefs.instance.systemBrightnessLightThemeId,
|
||||
))) {
|
||||
Prefs.instance.systemBrightnessLightThemeId = "light";
|
||||
}
|
||||
|
||||
// verify current user preference dark brightness theme and revert to default
|
||||
// if problems are found to prevent app being unusable
|
||||
if (!(await ThemeService.instance
|
||||
.verifyInstalled(themeId: Prefs.instance.systemBrightnessDarkThemeId))) {
|
||||
if (!(await ThemeService.instance.verifyInstalled(
|
||||
themeId: Prefs.instance.systemBrightnessDarkThemeId,
|
||||
))) {
|
||||
Prefs.instance.systemBrightnessDarkThemeId = "dark";
|
||||
}
|
||||
|
||||
|
@ -263,18 +305,14 @@ class MyApp extends StatelessWidget {
|
|||
final localeService = LocaleService();
|
||||
localeService.loadLocale();
|
||||
|
||||
return const KeyboardDismisser(
|
||||
child: MaterialAppWithTheme(),
|
||||
);
|
||||
return const KeyboardDismisser(child: MaterialAppWithTheme());
|
||||
}
|
||||
}
|
||||
|
||||
// Sidenote: MaterialAppWithTheme and InitView are only separated for clarity. No other reason.
|
||||
|
||||
class MaterialAppWithTheme extends ConsumerStatefulWidget {
|
||||
const MaterialAppWithTheme({
|
||||
super.key,
|
||||
});
|
||||
const MaterialAppWithTheme({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<MaterialAppWithTheme> createState() =>
|
||||
|
@ -348,7 +386,9 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
prefs: ref.read(prefsChangeNotifierProvider),
|
||||
);
|
||||
ref.read(priceAnd24hChangeNotifierProvider).start(true);
|
||||
await ref.read(pWallets).load(
|
||||
await ref
|
||||
.read(pWallets)
|
||||
.load(
|
||||
ref.read(prefsChangeNotifierProvider),
|
||||
ref.read(mainDBProvider),
|
||||
);
|
||||
|
@ -378,7 +418,9 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled) {
|
||||
switch (ref.read(prefsChangeNotifierProvider).backupFrequencyType) {
|
||||
case BackupFrequencyType.everyTenMinutes:
|
||||
ref.read(autoSWBServiceProvider).startPeriodicBackupTimer(
|
||||
ref
|
||||
.read(autoSWBServiceProvider)
|
||||
.startPeriodicBackupTimer(
|
||||
duration: const Duration(minutes: 10),
|
||||
);
|
||||
break;
|
||||
|
@ -411,9 +453,10 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
ref.read(prefsChangeNotifierProvider).systemBrightnessDarkThemeId;
|
||||
break;
|
||||
case Brightness.light:
|
||||
themeId = ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.systemBrightnessLightThemeId;
|
||||
themeId =
|
||||
ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.systemBrightnessLightThemeId;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
@ -432,9 +475,8 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
ref.read(applicationThemesDirectoryPathProvider.notifier).state =
|
||||
StackFileSystem.themesDir!.path;
|
||||
|
||||
ref.read(themeProvider.state).state = ref.read(pThemeService).getTheme(
|
||||
themeId: themeId,
|
||||
)!;
|
||||
ref.read(themeProvider.state).state =
|
||||
ref.read(pThemeService).getTheme(themeId: themeId)!;
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
// fetch open file if it exists
|
||||
|
@ -462,18 +504,17 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
ref.read(prefsChangeNotifierProvider).systemBrightnessDarkThemeId;
|
||||
break;
|
||||
case Brightness.light:
|
||||
themeId = ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.systemBrightnessLightThemeId;
|
||||
themeId =
|
||||
ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.systemBrightnessLightThemeId;
|
||||
break;
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (ref.read(prefsChangeNotifierProvider).enableSystemBrightness) {
|
||||
ref.read(themeProvider.state).state =
|
||||
ref.read(pThemeService).getTheme(
|
||||
themeId: themeId,
|
||||
)!;
|
||||
ref.read(pThemeService).getTheme(themeId: themeId)!;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -552,8 +593,8 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
/// should only be called on android currently
|
||||
Future<void> getOpenFile() async {
|
||||
// update provider with new file content state
|
||||
ref.read(openedFromSWBFileStringStateProvider.state).state =
|
||||
await platform.invokeMethod("getOpenFile");
|
||||
ref.read(openedFromSWBFileStringStateProvider.state).state = await platform
|
||||
.invokeMethod("getOpenFile");
|
||||
|
||||
// call reset to clear cached value
|
||||
await resetOpenPath();
|
||||
|
@ -570,9 +611,9 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
|
||||
Future<void> goToRestoreSWB(String encrypted) async {
|
||||
if (!ref.read(prefsChangeNotifierProvider).hasPin) {
|
||||
await Navigator.of(navigatorKey.currentContext!)
|
||||
.pushNamed(CreatePinView.routeName, arguments: true)
|
||||
.then((value) {
|
||||
await Navigator.of(
|
||||
navigatorKey.currentContext!,
|
||||
).pushNamed(CreatePinView.routeName, arguments: true).then((value) {
|
||||
if (value is! bool || value == false) {
|
||||
Navigator.of(navigatorKey.currentContext!).pushNamed(
|
||||
RestoreFromEncryptedStringView.routeName,
|
||||
|
@ -586,16 +627,17 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
navigatorKey.currentContext!,
|
||||
RouteGenerator.getRoute(
|
||||
shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute,
|
||||
builder: (_) => LockscreenView(
|
||||
showBackButton: true,
|
||||
routeOnSuccess: RestoreFromEncryptedStringView.routeName,
|
||||
routeOnSuccessArguments: encrypted,
|
||||
biometricsCancelButtonString: "CANCEL",
|
||||
biometricsLocalizedReason:
|
||||
"Authenticate to restore ${AppConfig.appName} backup",
|
||||
biometricsAuthenticationTitle:
|
||||
"Restore ${AppConfig.prefix} backup",
|
||||
),
|
||||
builder:
|
||||
(_) => LockscreenView(
|
||||
showBackButton: true,
|
||||
routeOnSuccess: RestoreFromEncryptedStringView.routeName,
|
||||
routeOnSuccessArguments: encrypted,
|
||||
biometricsCancelButtonString: "CANCEL",
|
||||
biometricsLocalizedReason:
|
||||
"Authenticate to restore ${AppConfig.appName} backup",
|
||||
biometricsAuthenticationTitle:
|
||||
"Restore ${AppConfig.prefix} backup",
|
||||
),
|
||||
settings: const RouteSettings(name: "/swbrestorelockscreen"),
|
||||
),
|
||||
),
|
||||
|
@ -605,10 +647,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
|
||||
InputBorder _buildOutlineInputBorder(Color color) {
|
||||
return OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
width: 1,
|
||||
color: color,
|
||||
),
|
||||
borderSide: BorderSide(width: 1, color: color),
|
||||
borderRadius: BorderRadius.circular(Constants.size.circularBorderRadius),
|
||||
);
|
||||
}
|
||||
|
@ -646,9 +685,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
),
|
||||
// splashFactory: NoSplash.splashFactory,
|
||||
splashColor: Colors.transparent,
|
||||
buttonTheme: ButtonThemeData(
|
||||
splashColor: colorScheme.splash,
|
||||
),
|
||||
buttonTheme: ButtonThemeData(splashColor: colorScheme.splash),
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: ButtonStyle(
|
||||
// splashFactory: NoSplash.splashFactory,
|
||||
|
@ -656,8 +693,9 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
minimumSize: MaterialStateProperty.all<Size>(const Size(46, 46)),
|
||||
// textStyle: MaterialStateProperty.all<TextStyle>(
|
||||
// STextStyles.button(context)),
|
||||
foregroundColor:
|
||||
MaterialStateProperty.all(colorScheme.buttonTextSecondary),
|
||||
foregroundColor: MaterialStateProperty.all(
|
||||
colorScheme.buttonTextSecondary,
|
||||
),
|
||||
backgroundColor: MaterialStateProperty.all<Color>(
|
||||
colorScheme.buttonBackSecondary,
|
||||
),
|
||||
|
@ -674,25 +712,22 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
checkboxTheme: CheckboxThemeData(
|
||||
splashRadius: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(Constants.size.checkboxBorderRadius),
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.checkboxBorderRadius,
|
||||
),
|
||||
),
|
||||
checkColor: MaterialStateColor.resolveWith(
|
||||
(state) {
|
||||
if (state.contains(MaterialState.selected)) {
|
||||
return colorScheme.checkboxIconChecked;
|
||||
}
|
||||
checkColor: MaterialStateColor.resolveWith((state) {
|
||||
if (state.contains(MaterialState.selected)) {
|
||||
return colorScheme.checkboxIconChecked;
|
||||
}
|
||||
return colorScheme.checkboxBGChecked;
|
||||
}),
|
||||
fillColor: MaterialStateColor.resolveWith((states) {
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return colorScheme.checkboxBGChecked;
|
||||
},
|
||||
),
|
||||
fillColor: MaterialStateColor.resolveWith(
|
||||
(states) {
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return colorScheme.checkboxBGChecked;
|
||||
}
|
||||
return colorScheme.checkboxBorderEmpty;
|
||||
},
|
||||
),
|
||||
}
|
||||
return colorScheme.checkboxBorderEmpty;
|
||||
}),
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
centerTitle: false,
|
||||
|
@ -710,91 +745,101 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
),
|
||||
// labelStyle: STextStyles.fieldLabel(context),
|
||||
// hintStyle: STextStyles.fieldLabel(context),
|
||||
enabledBorder:
|
||||
_buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
|
||||
focusedBorder:
|
||||
_buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
|
||||
enabledBorder: _buildOutlineInputBorder(
|
||||
colorScheme.textFieldDefaultBG,
|
||||
),
|
||||
focusedBorder: _buildOutlineInputBorder(
|
||||
colorScheme.textFieldDefaultBG,
|
||||
),
|
||||
errorBorder: _buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
|
||||
disabledBorder:
|
||||
_buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
|
||||
focusedErrorBorder:
|
||||
_buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
|
||||
disabledBorder: _buildOutlineInputBorder(
|
||||
colorScheme.textFieldDefaultBG,
|
||||
),
|
||||
focusedErrorBorder: _buildOutlineInputBorder(
|
||||
colorScheme.textFieldDefaultBG,
|
||||
),
|
||||
),
|
||||
),
|
||||
home: CryptoNotifications(
|
||||
child: Util.isDesktop
|
||||
? FutureBuilder(
|
||||
future: loadShared(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (_desktopHasPassword) {
|
||||
String? startupWalletId;
|
||||
if (ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.gotoWalletOnStartup) {
|
||||
startupWalletId = ref
|
||||
child:
|
||||
Util.isDesktop
|
||||
? FutureBuilder(
|
||||
future: loadShared(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (_desktopHasPassword) {
|
||||
String? startupWalletId;
|
||||
if (ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.startupWalletId;
|
||||
}
|
||||
.gotoWalletOnStartup) {
|
||||
startupWalletId =
|
||||
ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.startupWalletId;
|
||||
}
|
||||
|
||||
return DesktopLoginView(
|
||||
startupWalletId: startupWalletId,
|
||||
load: load,
|
||||
);
|
||||
} else {
|
||||
return const IntroView();
|
||||
}
|
||||
} else {
|
||||
return const LoadingView();
|
||||
}
|
||||
},
|
||||
)
|
||||
: FutureBuilder(
|
||||
future: load(),
|
||||
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
// FlutterNativeSplash.remove();
|
||||
if (ref.read(pAllWalletsInfo).isNotEmpty ||
|
||||
ref.read(prefsChangeNotifierProvider).hasPin) {
|
||||
// return HomeView();
|
||||
|
||||
String? startupWalletId;
|
||||
if (ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.gotoWalletOnStartup) {
|
||||
startupWalletId = ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.startupWalletId;
|
||||
}
|
||||
|
||||
return LockscreenView(
|
||||
isInitialAppLogin: true,
|
||||
routeOnSuccess: HomeView.routeName,
|
||||
routeOnSuccessArguments: startupWalletId,
|
||||
biometricsAuthenticationTitle:
|
||||
"Unlock ${AppConfig.prefix}",
|
||||
biometricsLocalizedReason:
|
||||
"Unlock your ${AppConfig.appName} using biometrics",
|
||||
biometricsCancelButtonString: "Cancel",
|
||||
);
|
||||
} else {
|
||||
if (AppConfig.appName == "Campfire" &&
|
||||
!CampfireMigration.didRun &&
|
||||
CampfireMigration.hasOldWallets) {
|
||||
return const CampfireMigrateView();
|
||||
return DesktopLoginView(
|
||||
startupWalletId: startupWalletId,
|
||||
load: load,
|
||||
);
|
||||
} else {
|
||||
return const IntroView();
|
||||
}
|
||||
} else {
|
||||
return const LoadingView();
|
||||
}
|
||||
} else {
|
||||
// CURRENTLY DISABLED as cannot be animated
|
||||
// technically not needed as FlutterNativeSplash will overlay
|
||||
// anything returned here until the future completes but
|
||||
// FutureBuilder requires you to return something
|
||||
return const LoadingView();
|
||||
}
|
||||
},
|
||||
),
|
||||
},
|
||||
)
|
||||
: FutureBuilder(
|
||||
future: load(),
|
||||
builder: (
|
||||
BuildContext context,
|
||||
AsyncSnapshot<void> snapshot,
|
||||
) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
// FlutterNativeSplash.remove();
|
||||
if (ref.read(pAllWalletsInfo).isNotEmpty ||
|
||||
ref.read(prefsChangeNotifierProvider).hasPin) {
|
||||
// return HomeView();
|
||||
|
||||
String? startupWalletId;
|
||||
if (ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.gotoWalletOnStartup) {
|
||||
startupWalletId =
|
||||
ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.startupWalletId;
|
||||
}
|
||||
|
||||
return LockscreenView(
|
||||
isInitialAppLogin: true,
|
||||
routeOnSuccess: HomeView.routeName,
|
||||
routeOnSuccessArguments: startupWalletId,
|
||||
biometricsAuthenticationTitle:
|
||||
"Unlock ${AppConfig.prefix}",
|
||||
biometricsLocalizedReason:
|
||||
"Unlock your ${AppConfig.appName} using biometrics",
|
||||
biometricsCancelButtonString: "Cancel",
|
||||
);
|
||||
} else {
|
||||
if (AppConfig.appName == "Campfire" &&
|
||||
!CampfireMigration.didRun &&
|
||||
CampfireMigration.hasOldWallets) {
|
||||
return const CampfireMigrateView();
|
||||
} else {
|
||||
return const IntroView();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// CURRENTLY DISABLED as cannot be animated
|
||||
// technically not needed as FlutterNativeSplash will overlay
|
||||
// anything returned here until the future completes but
|
||||
// FutureBuilder requires you to return something
|
||||
return const LoadingView();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -175,7 +175,8 @@ enum AddressType {
|
|||
frostMS,
|
||||
p2tr,
|
||||
solana,
|
||||
cardanoShelley;
|
||||
cardanoShelley,
|
||||
xelis;
|
||||
|
||||
String get readableName {
|
||||
switch (this) {
|
||||
|
@ -213,6 +214,8 @@ enum AddressType {
|
|||
return "P2TR (taproot)";
|
||||
case AddressType.cardanoShelley:
|
||||
return "Cardano Shelley";
|
||||
case AddressType.xelis:
|
||||
return "Xelis";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -279,6 +279,7 @@ const _AddresstypeEnumValueMap = {
|
|||
'p2tr': 14,
|
||||
'solana': 15,
|
||||
'cardanoShelley': 16,
|
||||
'xelis': 17,
|
||||
};
|
||||
const _AddresstypeValueEnumMap = {
|
||||
0: AddressType.p2pkh,
|
||||
|
@ -298,6 +299,7 @@ const _AddresstypeValueEnumMap = {
|
|||
14: AddressType.p2tr,
|
||||
15: AddressType.solana,
|
||||
16: AddressType.cardanoShelley,
|
||||
17: AddressType.xelis,
|
||||
};
|
||||
|
||||
Id _addressGetId(Address object) {
|
||||
|
|
|
@ -191,7 +191,7 @@ class _NewWalletRecoveryPhraseWarningViewState
|
|||
|
||||
// TODO: Refactor these to generate each coin in their respective classes
|
||||
// This code should not be in a random view page file
|
||||
if (coin is Monero || coin is Wownero) {
|
||||
if (coin is Monero || coin is Wownero || coin is Xelis) {
|
||||
// currently a special case due to the
|
||||
// xmr/wow libraries handling their
|
||||
// own mnemonic generation
|
||||
|
|
|
@ -31,6 +31,7 @@ import '../../../wallets/isar/models/wallet_info.dart';
|
|||
import '../../../wallets/wallet/impl/epiccash_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/monero_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/wownero_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/xelis_wallet.dart';
|
||||
import '../../../wallets/wallet/wallet.dart';
|
||||
import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
|
||||
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
|
@ -264,6 +265,10 @@ class _RestoreViewOnlyWalletViewState
|
|||
await (wallet as WowneroWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
case const (XelisWallet):
|
||||
await (wallet as XelisWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
default:
|
||||
await wallet.init();
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
import 'package:xelis_flutter/src/api/seed_search_engine.dart' as x_seed;
|
||||
|
||||
import '../../../notifications/show_flush_bar.dart';
|
||||
import '../../../pages_desktop_specific/desktop_home_view.dart';
|
||||
import '../../../pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
|
||||
|
@ -48,7 +50,8 @@ import '../../../wallets/isar/models/wallet_info.dart';
|
|||
import '../../../wallets/wallet/impl/epiccash_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/monero_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/wownero_wallet.dart';
|
||||
import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../../wallets/wallet/intermediate/external_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/xelis_wallet.dart';
|
||||
import '../../../wallets/wallet/supporting/epiccash_wallet_info_extension.dart';
|
||||
import '../../../wallets/wallet/wallet.dart';
|
||||
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
|
@ -103,6 +106,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
late final int _seedWordCount;
|
||||
late final bool isDesktop;
|
||||
|
||||
x_seed.SearchEngine? _xelisSeedSearch;
|
||||
final HashSet<String> _wordListHashSet = HashSet.from(bip39wordlist.WORDLIST);
|
||||
final ScrollController controller = ScrollController();
|
||||
|
||||
|
@ -167,6 +171,10 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
// _focusNodes.add(FocusNode());
|
||||
}
|
||||
|
||||
if (widget.coin is Xelis) {
|
||||
_xelisSeedSearch = x_seed.SearchEngine.init(languageIndex: BigInt.from(0));
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -199,6 +207,9 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
);
|
||||
return wowneroWordList.contains(word);
|
||||
}
|
||||
if (widget.coin is Xelis) {
|
||||
return _xelisSeedSearch!.search(query: word).length > 0;
|
||||
}
|
||||
return _wordListHashSet.contains(word);
|
||||
}
|
||||
|
||||
|
@ -283,10 +294,9 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
);
|
||||
}
|
||||
|
||||
// TODO: do actual check to make sure it is a valid mnemonic for monero
|
||||
// TODO: do actual check to make sure it is a valid mnemonic for monero + xelis
|
||||
if (bip39.validateMnemonic(mnemonic) == false &&
|
||||
!(widget.coin is Monero || widget.coin is Wownero)) {
|
||||
if (mounted) setState(() => _hideSeedWords = false);
|
||||
!(widget.coin is Monero || widget.coin is Wownero || widget.coin is Xelis)) {
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
|
@ -371,13 +381,17 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
await (wallet as WowneroWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
case const (XelisWallet):
|
||||
await (wallet as XelisWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
default:
|
||||
await wallet.init();
|
||||
}
|
||||
|
||||
await wallet.recover(isRescan: false);
|
||||
|
||||
if (wallet is LibMoneroWallet) {
|
||||
if (wallet is ExternalWallet) {
|
||||
await wallet.exit();
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ import '../../../wallets/isar/providers/wallet_info_provider.dart';
|
|||
import '../../../wallets/wallet/impl/epiccash_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/monero_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/wownero_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/xelis_wallet.dart';
|
||||
import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../../wallets/wallet/wallet.dart';
|
||||
import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
|
||||
|
@ -225,6 +226,10 @@ class _VerifyRecoveryPhraseViewState
|
|||
await (voWallet as WowneroWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
case const (XelisWallet):
|
||||
await (voWallet as XelisWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
default:
|
||||
await voWallet.init();
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ import '../../wallets/crypto_currency/crypto_currency.dart';
|
|||
import '../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../wallets/models/tx_data.dart';
|
||||
import '../../wallets/wallet/impl/firo_wallet.dart';
|
||||
import '../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../wallets/wallet/intermediate/external_wallet.dart';
|
||||
import '../../widgets/background.dart';
|
||||
import '../../widgets/conditional_parent.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
|
@ -277,7 +277,7 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
|
|||
// access to this screen but this is needed to get past an error that
|
||||
// would occur only to lead to another error which is why xmr/wow wallets
|
||||
// don't have access to this screen currently
|
||||
if (wallet is LibMoneroWallet) {
|
||||
if (wallet is ExternalWallet) {
|
||||
await wallet.init();
|
||||
await wallet.open();
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import '../../utilities/show_loading.dart';
|
|||
import '../../utilities/show_node_tor_settings_mismatch.dart';
|
||||
import '../../utilities/text_styles.dart';
|
||||
import '../../utilities/util.dart';
|
||||
import '../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../wallets/wallet/intermediate/external_wallet.dart';
|
||||
import '../../widgets/background.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../widgets/custom_buttons/blue_text_button.dart';
|
||||
|
@ -119,7 +119,7 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
|
|||
}
|
||||
|
||||
final Future<void> loadFuture;
|
||||
if (wallet is LibMoneroWallet) {
|
||||
if (wallet is ExternalWallet) {
|
||||
loadFuture =
|
||||
wallet.init().then((value) async => await (wallet).open());
|
||||
} else {
|
||||
|
|
|
@ -55,6 +55,7 @@ import '../../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
|||
import '../../../../../wallets/wallet/impl/epiccash_wallet.dart';
|
||||
import '../../../../../wallets/wallet/impl/monero_wallet.dart';
|
||||
import '../../../../../wallets/wallet/impl/wownero_wallet.dart';
|
||||
import '../../../../../wallets/wallet/impl/xelis_wallet.dart';
|
||||
import '../../../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../../../../wallets/wallet/wallet.dart';
|
||||
import '../../../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
|
||||
|
@ -506,6 +507,10 @@ abstract class SWB {
|
|||
case const (WowneroWallet):
|
||||
await (wallet as WowneroWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
case const (XelisWallet):
|
||||
await (wallet as XelisWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
default:
|
||||
await wallet.init();
|
||||
|
|
|
@ -1366,13 +1366,17 @@ class _TransactionV2DetailsViewState
|
|||
],
|
||||
),
|
||||
),
|
||||
if (coin is! NanoCurrency)
|
||||
if (coin is! NanoCurrency &&
|
||||
!(coin is Xelis && _transaction.type == TransactionType.incoming)
|
||||
)
|
||||
isDesktop
|
||||
? const _Divider()
|
||||
: const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
if (coin is! NanoCurrency)
|
||||
if (coin is! NanoCurrency &&
|
||||
!(coin is Xelis && _transaction.type == TransactionType.incoming)
|
||||
)
|
||||
RoundedWhiteContainer(
|
||||
padding: isDesktop
|
||||
? const EdgeInsets.all(16)
|
||||
|
|
|
@ -27,7 +27,7 @@ import '../../../utilities/text_styles.dart';
|
|||
import '../../../utilities/util.dart';
|
||||
import '../../../wallets/crypto_currency/coins/firo.dart';
|
||||
import '../../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../../wallets/wallet/intermediate/external_wallet.dart';
|
||||
import '../../../widgets/coin_card.dart';
|
||||
import '../../../widgets/conditional_parent.dart';
|
||||
import '../../wallet_view/wallet_view.dart';
|
||||
|
@ -132,7 +132,7 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
|||
}
|
||||
|
||||
final Future<void> loadFuture;
|
||||
if (wallet is LibMoneroWallet) {
|
||||
if (wallet is ExternalWallet) {
|
||||
loadFuture =
|
||||
wallet.init().then((value) async => await (wallet).open());
|
||||
} else {
|
||||
|
|
|
@ -25,7 +25,7 @@ import '../../../utilities/show_node_tor_settings_mismatch.dart';
|
|||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../../wallets/wallet/intermediate/external_wallet.dart';
|
||||
import '../../../widgets/dialogs/tor_warning_dialog.dart';
|
||||
import '../../../widgets/rounded_white_container.dart';
|
||||
import '../../wallet_view/wallet_view.dart';
|
||||
|
@ -99,7 +99,7 @@ class WalletListItem extends ConsumerWidget {
|
|||
}
|
||||
|
||||
final Future<void> loadFuture;
|
||||
if (wallet is LibMoneroWallet) {
|
||||
if (wallet is ExternalWallet) {
|
||||
loadFuture =
|
||||
wallet.init().then((value) async => await (wallet).open());
|
||||
} else {
|
||||
|
|
|
@ -21,7 +21,7 @@ import '../../utilities/show_loading.dart';
|
|||
import '../../utilities/show_node_tor_settings_mismatch.dart';
|
||||
import '../../utilities/util.dart';
|
||||
import '../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../wallets/wallet/intermediate/external_wallet.dart';
|
||||
import '../../widgets/rounded_container.dart';
|
||||
import '../../widgets/wallet_info_row/wallet_info_row.dart';
|
||||
import 'wallet_view/desktop_wallet_view.dart';
|
||||
|
@ -101,7 +101,7 @@ class CoinWalletsTable extends ConsumerWidget {
|
|||
}
|
||||
|
||||
final Future<void> loadFuture;
|
||||
if (wallet is LibMoneroWallet) {
|
||||
if (wallet is ExternalWallet) {
|
||||
loadFuture = wallet
|
||||
.init()
|
||||
.then((value) async => await (wallet).open());
|
||||
|
|
|
@ -25,7 +25,7 @@ import '../../utilities/text_styles.dart';
|
|||
import '../../utilities/util.dart';
|
||||
import '../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../wallets/isar/providers/all_wallets_info_provider.dart';
|
||||
import '../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../wallets/wallet/intermediate/external_wallet.dart';
|
||||
import '../../widgets/breathing.dart';
|
||||
import '../../widgets/conditional_parent.dart';
|
||||
import '../../widgets/desktop/desktop_dialog.dart';
|
||||
|
@ -138,7 +138,7 @@ class _DesktopWalletSummaryRowState
|
|||
}
|
||||
|
||||
final Future<void> loadFuture;
|
||||
if (wallet is LibMoneroWallet) {
|
||||
if (wallet is ExternalWallet) {
|
||||
loadFuture =
|
||||
wallet.init().then((value) async => await (wallet).open());
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
import 'package:xelis_flutter/src/api/api.dart' as xelis_api;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
enum XelisTableGenerationStep {
|
||||
t1PointsGeneration,
|
||||
t1CuckooSetup,
|
||||
t2Table,
|
||||
unknown;
|
||||
|
||||
factory XelisTableGenerationStep.fromString(String step) {
|
||||
return switch (step) {
|
||||
"T1PointsGeneration" => XelisTableGenerationStep.t1PointsGeneration,
|
||||
"T1CuckooSetup" => XelisTableGenerationStep.t1CuckooSetup,
|
||||
"T2Table" => XelisTableGenerationStep.t2Table,
|
||||
_ => XelisTableGenerationStep.unknown,
|
||||
};
|
||||
}
|
||||
|
||||
String get displayName => switch (this) {
|
||||
t1PointsGeneration => "Generating T1 Points",
|
||||
t1CuckooSetup => "Setting up T1 Cuckoo",
|
||||
t2Table => "Generating T2 Table",
|
||||
unknown => "Processing",
|
||||
};
|
||||
}
|
||||
|
||||
class XelisTableProgressState {
|
||||
final double? tableProgress;
|
||||
final XelisTableGenerationStep currentStep;
|
||||
|
||||
const XelisTableProgressState({
|
||||
this.tableProgress,
|
||||
this.currentStep = XelisTableGenerationStep.unknown,
|
||||
});
|
||||
|
||||
XelisTableProgressState copyWith({
|
||||
double? tableProgress,
|
||||
XelisTableGenerationStep? currentStep,
|
||||
}) {
|
||||
return XelisTableProgressState(
|
||||
tableProgress: tableProgress ?? this.tableProgress,
|
||||
currentStep: currentStep ?? this.currentStep,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final xelisTableProgressProvider = StreamProvider<XelisTableProgressState>((ref) {
|
||||
double lastPrintedProgress = 0.0;
|
||||
return xelis_api.createProgressReportStream().map((report) {
|
||||
return report.when(
|
||||
tableGeneration: (progress, step, _) {
|
||||
final currentStep = XelisTableGenerationStep.fromString(step);
|
||||
final stepIndex = switch(currentStep) {
|
||||
XelisTableGenerationStep.t1PointsGeneration => 0,
|
||||
XelisTableGenerationStep.t1CuckooSetup => 1,
|
||||
XelisTableGenerationStep.t2Table => 2,
|
||||
XelisTableGenerationStep.unknown => 0,
|
||||
};
|
||||
|
||||
if ((progress - lastPrintedProgress).abs() >= 0.05 ||
|
||||
currentStep != XelisTableGenerationStep.fromString(step) ||
|
||||
progress >= 0.99) {
|
||||
debugPrint("Xelis Table Generation: $step - ${progress*100.0}%");
|
||||
lastPrintedProgress = progress;
|
||||
}
|
||||
|
||||
return XelisTableProgressState(
|
||||
tableProgress: progress,
|
||||
currentStep: currentStep,
|
||||
);
|
||||
},
|
||||
misc: (_) => const XelisTableProgressState(),
|
||||
);
|
||||
});
|
||||
});
|
|
@ -32,3 +32,4 @@ export './ui/verify_recovery_phrase/correct_word_provider.dart';
|
|||
export './ui/verify_recovery_phrase/random_index_provider.dart';
|
||||
export './ui/verify_recovery_phrase/selected_word_provider.dart';
|
||||
export './wallet/transaction_note_provider.dart';
|
||||
export './progress_report/xelis_table_progress_provider.dart';
|
||||
|
|
|
@ -48,6 +48,7 @@ class PriceAPI {
|
|||
Namecoin: "namecoin",
|
||||
Nano: "nano",
|
||||
Banano: "banano",
|
||||
Xelis: "xelis",
|
||||
};
|
||||
|
||||
static const refreshInterval = 60;
|
||||
|
|
|
@ -31,9 +31,7 @@ final pThemeService = Provider<ThemeService>((ref) {
|
|||
});
|
||||
|
||||
class ThemeService {
|
||||
// dumb quick conditional based on name. Should really be done better
|
||||
static const _currentDefaultThemeVersion =
|
||||
AppConfig.appName == "Campfire" ? 17 : 16;
|
||||
static const _currentDefaultThemeVersion = 17;
|
||||
ThemeService._();
|
||||
static ThemeService? _instance;
|
||||
static ThemeService get instance => _instance ??= ThemeService._();
|
||||
|
@ -61,9 +59,7 @@ class ThemeService {
|
|||
final jsonString = utf8.decode(themeJsonFiles.first.content as List<int>);
|
||||
final json = jsonDecode(jsonString) as Map;
|
||||
|
||||
final theme = StackTheme.fromJson(
|
||||
json: Map<String, dynamic>.from(json),
|
||||
);
|
||||
final theme = StackTheme.fromJson(json: Map<String, dynamic>.from(json));
|
||||
|
||||
try {
|
||||
theme.assets;
|
||||
|
@ -96,11 +92,12 @@ class ThemeService {
|
|||
|
||||
Future<void> remove({required String themeId}) async {
|
||||
final themesDir = StackFileSystem.themesDir!;
|
||||
final isarId = await db.isar.stackThemes
|
||||
.where()
|
||||
.themeIdEqualTo(themeId)
|
||||
.idProperty()
|
||||
.findFirst();
|
||||
final isarId =
|
||||
await db.isar.stackThemes
|
||||
.where()
|
||||
.themeIdEqualTo(themeId)
|
||||
.idProperty()
|
||||
.findFirst();
|
||||
if (isarId != null) {
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.stackThemes.delete(isarId);
|
||||
|
@ -184,22 +181,27 @@ class ThemeService {
|
|||
try {
|
||||
final response = await client.get(
|
||||
url: Uri.parse("$baseServerUrl/themes"),
|
||||
proxyInfo: Prefs.instance.useTor
|
||||
? TorService.sharedInstance.getProxyInfo()
|
||||
: null,
|
||||
proxyInfo:
|
||||
Prefs.instance.useTor
|
||||
? TorService.sharedInstance.getProxyInfo()
|
||||
: null,
|
||||
);
|
||||
|
||||
final jsonList = jsonDecode(response.body) as List;
|
||||
|
||||
final result = List<Map<String, dynamic>>.from(jsonList)
|
||||
.map((e) => StackThemeMetaData.fromMap(e))
|
||||
.where((e) => e.id != "light" && e.id != "dark")
|
||||
.toList();
|
||||
final result =
|
||||
List<Map<String, dynamic>>.from(jsonList)
|
||||
.map((e) => StackThemeMetaData.fromMap(e))
|
||||
.where((e) => e.id != "light" && e.id != "dark")
|
||||
.toList();
|
||||
|
||||
return result;
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.w("Failed to fetch themes list: ", error: e, stackTrace: s);
|
||||
Logging.instance.w(
|
||||
"Failed to fetch themes list: ",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
@ -210,9 +212,10 @@ class ThemeService {
|
|||
try {
|
||||
final response = await client.get(
|
||||
url: Uri.parse("$baseServerUrl/theme/${themeMetaData.id}"),
|
||||
proxyInfo: Prefs.instance.useTor
|
||||
? TorService.sharedInstance.getProxyInfo()
|
||||
: null,
|
||||
proxyInfo:
|
||||
Prefs.instance.useTor
|
||||
? TorService.sharedInstance.getProxyInfo()
|
||||
: null,
|
||||
);
|
||||
|
||||
final bytes = Uint8List.fromList(response.bodyBytes);
|
||||
|
@ -228,8 +231,11 @@ class ThemeService {
|
|||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.w("Failed to fetch themes list: ", error: e, stackTrace: s);
|
||||
Logging.instance.w(
|
||||
"Failed to fetch themes list: ",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
@ -270,9 +276,10 @@ class StackThemeMetaData {
|
|||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.f(
|
||||
"Failed to create instance of StackThemeMetaData using $map",
|
||||
error: e,
|
||||
stackTrace: s);
|
||||
"Failed to create instance of StackThemeMetaData using $map",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,8 @@ enum DerivePathType {
|
|||
eCash44,
|
||||
solana,
|
||||
bip86,
|
||||
cardanoShelley;
|
||||
cardanoShelley,
|
||||
xelis;
|
||||
|
||||
AddressType getAddressType() {
|
||||
switch (this) {
|
||||
|
@ -45,6 +46,9 @@ enum DerivePathType {
|
|||
|
||||
case DerivePathType.cardanoShelley:
|
||||
return AddressType.cardanoShelley;
|
||||
|
||||
case DerivePathType.xelis:
|
||||
return AddressType.xelis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,8 +39,9 @@ abstract class StackFileSystem {
|
|||
// todo: can merge and do same as regular linux home dir?
|
||||
if (Util.isArmLinux) {
|
||||
appDirectory = await getApplicationDocumentsDirectory();
|
||||
appDirectory =
|
||||
Directory("${appDirectory.path}/.${AppConfig.appDefaultDataDirName}");
|
||||
appDirectory = Directory(
|
||||
"${appDirectory.path}/.${AppConfig.appDefaultDataDirName}",
|
||||
);
|
||||
} else if (Platform.isLinux) {
|
||||
if (_overrideDesktopDirPath != null) {
|
||||
appDirectory = Directory(_overrideDesktopDirPath!);
|
||||
|
@ -148,6 +149,24 @@ abstract class StackFileSystem {
|
|||
}
|
||||
}
|
||||
|
||||
static Future<Directory> applicationXelisDirectory() async {
|
||||
final root = await applicationRootDirectory();
|
||||
final dir = Directory("${root.path}${Platform.pathSeparator}xelis");
|
||||
if (!dir.existsSync()) {
|
||||
await dir.create();
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
static Future<Directory> applicationXelisTableDirectory() async {
|
||||
final xelis = await applicationXelisDirectory();
|
||||
final dir = Directory("${xelis.path}${Platform.pathSeparator}table");
|
||||
if (!dir.existsSync()) {
|
||||
await dir.create();
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
static Future<void> initThemesDir() async {
|
||||
final root = await applicationRootDirectory();
|
||||
|
||||
|
|
|
@ -26,6 +26,8 @@ import 'test_monero_node_connection.dart';
|
|||
import 'test_stellar_node_connection.dart';
|
||||
import 'tor_plain_net_option_enum.dart';
|
||||
|
||||
import 'package:xelis_dart_sdk/xelis_dart_sdk.dart' as xelis_sdk;
|
||||
|
||||
Future<bool> _xmrHelper(
|
||||
NodeFormData nodeFormData,
|
||||
BuildContext context,
|
||||
|
@ -297,6 +299,28 @@ Future<bool> testNodeConnection({
|
|||
testPassed = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Xelis():
|
||||
try {
|
||||
final daemon = xelis_sdk.DaemonClient(
|
||||
endPoint: "${formData.host!}:${formData.port!}",
|
||||
secureWebSocket: formData.useSSL ?? false,
|
||||
timeout: 5000
|
||||
);
|
||||
daemon.connect();
|
||||
|
||||
final xelis_sdk.GetInfoResult networkInfo = await daemon.getInfo();
|
||||
testPassed = networkInfo.height != null;
|
||||
|
||||
daemon.disconnect();
|
||||
|
||||
Logging.instance.i(
|
||||
"Xelis testNodeConnection result: \"${networkInfo.toString()}\"",
|
||||
);
|
||||
} catch (e, s) {
|
||||
testPassed = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return testPassed;
|
||||
|
|
142
lib/wallets/crypto_currency/coins/xelis.dart
Normal file
142
lib/wallets/crypto_currency/coins/xelis.dart
Normal file
|
@ -0,0 +1,142 @@
|
|||
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||
import '../../../models/node_model.dart';
|
||||
import '../../../utilities/default_nodes.dart';
|
||||
import '../../../utilities/enums/derive_path_type_enum.dart';
|
||||
import '../crypto_currency.dart';
|
||||
import '../intermediate/electrum_currency.dart';
|
||||
|
||||
import 'package:xelis_flutter/src/api/utils.dart' as x_utils;
|
||||
|
||||
class Xelis extends ElectrumCurrency {
|
||||
Xelis(super.network) {
|
||||
_idMain = "xelis";
|
||||
_uriScheme = "xelis";
|
||||
switch (network) {
|
||||
case CryptoCurrencyNetwork.main:
|
||||
_id = _idMain;
|
||||
_name = "Xelis";
|
||||
_ticker = "XEL";
|
||||
case CryptoCurrencyNetwork.test:
|
||||
_id = "xelisTestNet";
|
||||
_name = "tXelis";
|
||||
_ticker = "XET";
|
||||
default:
|
||||
throw Exception("Unsupported network: $network");
|
||||
}
|
||||
}
|
||||
|
||||
late final String _id;
|
||||
@override
|
||||
String get identifier => _id;
|
||||
|
||||
late final String _idMain;
|
||||
@override
|
||||
String get mainNetId => _idMain;
|
||||
|
||||
late final String _name;
|
||||
@override
|
||||
String get prettyName => _name;
|
||||
|
||||
late final String _uriScheme;
|
||||
@override
|
||||
String get uriScheme => _uriScheme;
|
||||
|
||||
late final String _ticker;
|
||||
@override
|
||||
String get ticker => _ticker;
|
||||
|
||||
@override
|
||||
NodeModel get defaultNode {
|
||||
switch (network) {
|
||||
case CryptoCurrencyNetwork.main:
|
||||
return NodeModel(
|
||||
host: "us-node.xelis.io",
|
||||
port: 443,
|
||||
name: DefaultNodes.defaultName,
|
||||
id: DefaultNodes.buildId(this),
|
||||
useSSL: true,
|
||||
enabled: true,
|
||||
coinName: identifier,
|
||||
isFailover: true,
|
||||
isDown: false,
|
||||
torEnabled: false,
|
||||
clearnetEnabled: true,
|
||||
);
|
||||
|
||||
case CryptoCurrencyNetwork.test:
|
||||
return NodeModel(
|
||||
host: "testnet-node.xelis.io",
|
||||
port: 443,
|
||||
name: DefaultNodes.defaultName,
|
||||
id: DefaultNodes.buildId(this),
|
||||
useSSL: true,
|
||||
enabled: true,
|
||||
coinName: identifier,
|
||||
isFailover: true,
|
||||
isDown: false,
|
||||
torEnabled: false,
|
||||
clearnetEnabled: true,
|
||||
);
|
||||
|
||||
default:
|
||||
throw Exception("Unsupported network: $network");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get minConfirms => 1;
|
||||
|
||||
@override
|
||||
bool get torSupport => false;
|
||||
|
||||
@override
|
||||
bool validateAddress(String address) {
|
||||
try {
|
||||
return x_utils.isAddressValid(strAddress: address);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String get genesisHash => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
int get defaultSeedPhraseLength => 25;
|
||||
|
||||
@override
|
||||
int get fractionDigits => 8;
|
||||
|
||||
@override
|
||||
bool get hasBuySupport => false;
|
||||
|
||||
@override
|
||||
bool get hasMnemonicPassphraseSupport => false;
|
||||
|
||||
@override
|
||||
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength];
|
||||
|
||||
@override
|
||||
AddressType get defaultAddressType => defaultDerivePathType.getAddressType();
|
||||
|
||||
@override
|
||||
BigInt get satsPerCoin => BigInt.from(1000000000);
|
||||
|
||||
@override
|
||||
int get targetBlockTimeSeconds => 15;
|
||||
|
||||
@override
|
||||
DerivePathType get defaultDerivePathType => DerivePathType.xelis;
|
||||
|
||||
@override
|
||||
Uri defaultBlockExplorer(String txid) {
|
||||
switch (network) {
|
||||
case CryptoCurrencyNetwork.main:
|
||||
return Uri.parse("https://explorer.xelis.io/txs/$txid");
|
||||
default:
|
||||
throw Exception(
|
||||
"Unsupported network for defaultBlockExplorer(): $network",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ export 'coins/solana.dart';
|
|||
export 'coins/stellar.dart';
|
||||
export 'coins/tezos.dart';
|
||||
export 'coins/wownero.dart';
|
||||
export 'coins/xelis.dart';
|
||||
|
||||
enum CryptoCurrencyNetwork {
|
||||
main,
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import '../crypto_currency.dart';
|
||||
|
||||
abstract class ElectrumCurrency extends CryptoCurrency {
|
||||
ElectrumCurrency(super.network);
|
||||
}
|
|
@ -517,6 +517,7 @@ abstract class WalletInfoKeys {
|
|||
static const String epiccashData = "epiccashDataKey";
|
||||
static const String bananoMonkeyImageBytes = "monkeyImageBytesKey";
|
||||
static const String tezosDerivationPath = "tezosDerivationPathKey";
|
||||
static const String xelisDerivationPath = "xelisDerivationPathKey";
|
||||
static const String lelantusCoinIsarRescanRequired =
|
||||
"lelantusCoinIsarRescanRequired";
|
||||
static const String enableLelantusScanning = "enableLelantusScanningKey";
|
||||
|
|
|
@ -269,6 +269,7 @@ const _WalletInfomainAddressTypeEnumValueMap = {
|
|||
'p2tr': 14,
|
||||
'solana': 15,
|
||||
'cardanoShelley': 16,
|
||||
'xelis': 17,
|
||||
};
|
||||
const _WalletInfomainAddressTypeValueEnumMap = {
|
||||
0: AddressType.p2pkh,
|
||||
|
@ -288,6 +289,7 @@ const _WalletInfomainAddressTypeValueEnumMap = {
|
|||
14: AddressType.p2tr,
|
||||
15: AddressType.solana,
|
||||
16: AddressType.cardanoShelley,
|
||||
17: AddressType.xelis,
|
||||
};
|
||||
|
||||
Id _walletInfoGetId(WalletInfo object) {
|
||||
|
|
|
@ -74,6 +74,9 @@ class TxData {
|
|||
final List<TxData>? sparkMints;
|
||||
final List<SparkCoin>? usedSparkCoins;
|
||||
|
||||
// xelis specific
|
||||
final String? otherData;
|
||||
|
||||
final TransactionV2? tempTx;
|
||||
|
||||
final bool ignoreCachedBalanceChecks;
|
||||
|
@ -113,6 +116,7 @@ class TxData {
|
|||
this.mintsMapLelantus,
|
||||
this.tezosOperationsList,
|
||||
this.sparkRecipients,
|
||||
this.otherData,
|
||||
this.sparkMints,
|
||||
this.usedSparkCoins,
|
||||
this.tempTx,
|
||||
|
@ -213,6 +217,7 @@ class TxData {
|
|||
String? note,
|
||||
String? noteOnChain,
|
||||
String? memo,
|
||||
String? otherData,
|
||||
Set<UTXO>? utxos,
|
||||
List<UTXO>? usedUTXOs,
|
||||
List<TxRecipient>? recipients,
|
||||
|
@ -258,6 +263,7 @@ class TxData {
|
|||
note: note ?? this.note,
|
||||
noteOnChain: noteOnChain ?? this.noteOnChain,
|
||||
memo: memo ?? this.memo,
|
||||
otherData: otherData ?? this.otherData,
|
||||
utxos: utxos ?? this.utxos,
|
||||
usedUTXOs: usedUTXOs ?? this.usedUTXOs,
|
||||
recipients: recipients ?? this.recipients,
|
||||
|
@ -321,6 +327,7 @@ class TxData {
|
|||
'sparkRecipients: $sparkRecipients, '
|
||||
'sparkMints: $sparkMints, '
|
||||
'usedSparkCoins: $usedSparkCoins, '
|
||||
'otherData: $otherData, '
|
||||
'tempTx: $tempTx, '
|
||||
'ignoreCachedBalanceChecks: $ignoreCachedBalanceChecks, '
|
||||
'opNameState: $opNameState, '
|
||||
|
|
990
lib/wallets/wallet/impl/xelis_wallet.dart
Normal file
990
lib/wallets/wallet/impl/xelis_wallet.dart
Normal file
|
@ -0,0 +1,990 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:stack_wallet_backup/generate_password.dart';
|
||||
import 'package:xelis_dart_sdk/xelis_dart_sdk.dart' as xelis_sdk;
|
||||
import 'package:xelis_flutter/src/api/wallet.dart' as x_wallet;
|
||||
|
||||
import '../../../models/balance.dart';
|
||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||
import '../../../models/isar/models/blockchain_data/transaction.dart';
|
||||
import '../../../models/isar/models/blockchain_data/v2/input_v2.dart';
|
||||
import '../../../models/isar/models/blockchain_data/v2/output_v2.dart';
|
||||
import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import '../../../models/paymint/fee_object_model.dart';
|
||||
import '../../../services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||
import '../../../services/event_bus/global_event_bus.dart';
|
||||
import '../../../utilities/amount/amount.dart';
|
||||
import '../../../utilities/logger.dart';
|
||||
import '../../../utilities/stack_file_system.dart';
|
||||
import '../../crypto_currency/crypto_currency.dart';
|
||||
import '../../models/tx_data.dart';
|
||||
import '../intermediate/lib_xelis_wallet.dart';
|
||||
import '../wallet.dart';
|
||||
|
||||
class XelisWallet extends LibXelisWallet {
|
||||
Completer<void>? _initCompleter;
|
||||
|
||||
XelisWallet(CryptoCurrencyNetwork network) : super(Xelis(network));
|
||||
// ==================== Overrides ============================================
|
||||
|
||||
@override
|
||||
int get isarTransactionVersion => 2;
|
||||
|
||||
Future<void> _restoreWallet() async {
|
||||
final tablePath = await getPrecomputedTablesPath();
|
||||
final tableState = await getTableState();
|
||||
final xelisDir = await StackFileSystem.applicationXelisDirectory();
|
||||
final String name = walletId;
|
||||
final String directory = xelisDir.path;
|
||||
final password = await secureStorageInterface.read(
|
||||
key: Wallet.mnemonicPassphraseKey(walletId: info.walletId),
|
||||
);
|
||||
|
||||
final mnemonic = await getMnemonic();
|
||||
final seedLength = mnemonic.trim().split(" ").length;
|
||||
|
||||
invalidSeedLengthCheck(seedLength);
|
||||
|
||||
Logging.instance.i("Xelis: recovering wallet");
|
||||
final wallet = await x_wallet.createXelisWallet(
|
||||
name: name,
|
||||
directory: directory,
|
||||
password: password!,
|
||||
seed: mnemonic.trim(),
|
||||
network: cryptoCurrency.network.xelisNetwork,
|
||||
precomputedTablesPath: tablePath,
|
||||
l1Low: tableState.currentSize.isLow,
|
||||
);
|
||||
|
||||
await secureStorageInterface.write(
|
||||
key: Wallet.mnemonicKey(walletId: walletId),
|
||||
value: mnemonic.trim(),
|
||||
);
|
||||
|
||||
libXelisWallet = wallet;
|
||||
|
||||
await _finishInit();
|
||||
}
|
||||
|
||||
Future<void> _createNewWallet() async {
|
||||
final tablePath = await getPrecomputedTablesPath();
|
||||
final tableState = await getTableState();
|
||||
final xelisDir = await StackFileSystem.applicationXelisDirectory();
|
||||
final String name = walletId;
|
||||
final String directory = xelisDir.path;
|
||||
final String password = generatePassword();
|
||||
|
||||
Logging.instance.d("Xelis: storing password");
|
||||
await secureStorageInterface.write(
|
||||
key: Wallet.mnemonicPassphraseKey(walletId: info.walletId),
|
||||
value: password,
|
||||
);
|
||||
|
||||
final wallet = await x_wallet.createXelisWallet(
|
||||
name: name,
|
||||
directory: directory,
|
||||
password: password,
|
||||
network: cryptoCurrency.network.xelisNetwork,
|
||||
precomputedTablesPath: tablePath,
|
||||
l1Low: tableState.currentSize.isLow,
|
||||
);
|
||||
|
||||
final mnemonic = await wallet.getSeed();
|
||||
await secureStorageInterface.write(
|
||||
key: Wallet.mnemonicKey(walletId: walletId),
|
||||
value: mnemonic.trim(),
|
||||
);
|
||||
|
||||
libXelisWallet = wallet;
|
||||
|
||||
await _finishInit();
|
||||
}
|
||||
|
||||
Future<void> _existingWallet() async {
|
||||
Logging.instance.i("Xelis: opening existing wallet");
|
||||
final tablePath = await getPrecomputedTablesPath();
|
||||
final tableState = await getTableState();
|
||||
final xelisDir = await StackFileSystem.applicationXelisDirectory();
|
||||
final String name = walletId;
|
||||
final String directory = xelisDir.path;
|
||||
final password = await secureStorageInterface.read(
|
||||
key: Wallet.mnemonicPassphraseKey(walletId: info.walletId),
|
||||
);
|
||||
|
||||
libXelisWallet = await x_wallet.openXelisWallet(
|
||||
name: name,
|
||||
directory: directory,
|
||||
password: password!,
|
||||
network: cryptoCurrency.network.xelisNetwork,
|
||||
precomputedTablesPath: tablePath,
|
||||
l1Low: tableState.currentSize.isLow,
|
||||
);
|
||||
|
||||
await _finishInit();
|
||||
}
|
||||
|
||||
Future<void> _finishInit() async {
|
||||
if (await isTableUpgradeAvailable()) {
|
||||
unawaited(updateTablesToDesiredSize());
|
||||
}
|
||||
|
||||
final newReceivingAddress =
|
||||
await getCurrentReceivingAddress() ??
|
||||
Address(
|
||||
walletId: walletId,
|
||||
derivationIndex: 0,
|
||||
derivationPath: null,
|
||||
value: libXelisWallet!.getAddressStr(),
|
||||
publicKey: [],
|
||||
type: AddressType.xelis,
|
||||
subType: AddressSubType.receiving,
|
||||
);
|
||||
|
||||
await mainDB.updateOrPutAddresses([newReceivingAddress]);
|
||||
|
||||
if (info.cachedReceivingAddress != newReceivingAddress.value) {
|
||||
await info.updateReceivingAddress(
|
||||
newAddress: newReceivingAddress.value,
|
||||
isar: mainDB.isar,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> init({bool? isRestore}) async {
|
||||
Logging.instance.d("Xelis: init");
|
||||
|
||||
if (_initCompleter != null) {
|
||||
await _initCompleter!.future;
|
||||
return super.init();
|
||||
}
|
||||
|
||||
_initCompleter = Completer<void>();
|
||||
|
||||
try {
|
||||
final bool walletExists = await LibXelisWallet.checkWalletExists(
|
||||
walletId,
|
||||
);
|
||||
|
||||
if (libXelisWallet == null) {
|
||||
if (isRestore == true) {
|
||||
await _restoreWallet();
|
||||
} else {
|
||||
if (!walletExists) {
|
||||
await _createNewWallet();
|
||||
} else {
|
||||
await _existingWallet();
|
||||
}
|
||||
}
|
||||
}
|
||||
_initCompleter!.complete();
|
||||
} catch (e, s) {
|
||||
_initCompleter!.completeError(e);
|
||||
Logging.instance.e(
|
||||
"Xelis init() rethrowing error",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
|
||||
return super.init();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> recover({required bool isRescan}) async {
|
||||
if (isRescan) {
|
||||
await refreshMutex.protect(() async {
|
||||
await mainDB.deleteWalletBlockchainData(walletId);
|
||||
await updateTransactions(isRescan: true, topoheight: 0);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Borrowed from libmonero for now, need to refactor for Xelis view keys
|
||||
// if (isViewOnly) {
|
||||
// await recoverViewOnly();
|
||||
// return;
|
||||
// }
|
||||
|
||||
try {
|
||||
await open();
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
"Error rethrown from $runtimeType recover(isRescan: $isRescan)",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> pingCheck() async {
|
||||
try {
|
||||
await libXelisWallet!.getDaemonInfo();
|
||||
await handleOnline();
|
||||
return true;
|
||||
} catch (_) {
|
||||
await handleOffline();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final _balanceUpdateMutex = Mutex();
|
||||
|
||||
@override
|
||||
Future<void> updateBalance({int? newBalance}) async {
|
||||
await _balanceUpdateMutex.protect(() async {
|
||||
try {
|
||||
if (await libXelisWallet!.hasXelisBalance()) {
|
||||
final BigInt xelBalance =
|
||||
newBalance != null
|
||||
? BigInt.from(newBalance)
|
||||
: await libXelisWallet!
|
||||
.getXelisBalanceRaw(); // in the future, use getAssetBalances and handle each
|
||||
final balance = Balance(
|
||||
total: Amount(
|
||||
rawValue: xelBalance,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
),
|
||||
spendable: Amount(
|
||||
rawValue: xelBalance,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
),
|
||||
blockedTotal: Amount.zeroWith(
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
),
|
||||
pendingSpendable: Amount.zeroWith(
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
),
|
||||
);
|
||||
await info.updateBalance(newBalance: balance, isar: mainDB.isar);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
"Error in $runtimeType updateBalance()",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<int> _fetchChainHeight() async {
|
||||
final infoString = await libXelisWallet!.getDaemonInfo();
|
||||
final Map<String, dynamic> nodeInfo =
|
||||
(json.decode(infoString) as Map).cast();
|
||||
|
||||
pruningHeight =
|
||||
int.tryParse(nodeInfo['pruned_topoheight']?.toString() ?? '0') ?? 0;
|
||||
return int.parse(nodeInfo['topoheight'].toString());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateChainHeight({int? topoheight}) async {
|
||||
try {
|
||||
final height = topoheight ?? await _fetchChainHeight();
|
||||
|
||||
await info.updateCachedChainHeight(
|
||||
newHeight: height.toInt(),
|
||||
isar: mainDB.isar,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
"Error in $runtimeType updateChainHeight()",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateNode() async {
|
||||
try {
|
||||
final bool online = await libXelisWallet!.isOnline();
|
||||
if (online == true) {
|
||||
await libXelisWallet!.offlineMode();
|
||||
}
|
||||
await super.connect();
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
"Error rethrown from $runtimeType updateNode()",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<String>> updateTransactions({
|
||||
bool isRescan = false,
|
||||
List<String>? rawTransactions,
|
||||
int? topoheight,
|
||||
}) async {
|
||||
checkInitialized();
|
||||
|
||||
final newReceivingAddress =
|
||||
await getCurrentReceivingAddress() ??
|
||||
Address(
|
||||
walletId: walletId,
|
||||
derivationIndex: 0,
|
||||
derivationPath: null,
|
||||
value: libXelisWallet!.getAddressStr(),
|
||||
publicKey: [],
|
||||
type: AddressType.xelis,
|
||||
subType: AddressSubType.receiving,
|
||||
);
|
||||
|
||||
final thisAddress = newReceivingAddress.value;
|
||||
|
||||
int firstBlock = 0;
|
||||
if (!isRescan) {
|
||||
firstBlock =
|
||||
await mainDB.isar.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.heightProperty()
|
||||
.max() ??
|
||||
0;
|
||||
|
||||
if (firstBlock > 10) {
|
||||
// add some buffer
|
||||
firstBlock -= 10;
|
||||
}
|
||||
} else {
|
||||
await libXelisWallet!.rescan(topoheight: BigInt.from(pruningHeight));
|
||||
}
|
||||
|
||||
final txListJson = rawTransactions ?? await libXelisWallet!.allHistory();
|
||||
|
||||
final List<TransactionV2> txns = [];
|
||||
|
||||
for (final jsonString in txListJson) {
|
||||
try {
|
||||
final transactionEntry = xelis_sdk.TransactionEntry.fromJson(
|
||||
(json.decode(jsonString) as Map).cast(),
|
||||
);
|
||||
|
||||
// Check for duplicates
|
||||
final storedTx =
|
||||
await mainDB.isar.transactionV2s
|
||||
.where()
|
||||
.txidWalletIdEqualTo(transactionEntry.hash, walletId)
|
||||
.findFirst();
|
||||
|
||||
if (storedTx != null &&
|
||||
storedTx.height != null &&
|
||||
storedTx.height! > 0) {
|
||||
continue; // Skip already processed transactions
|
||||
}
|
||||
|
||||
final List<OutputV2> outputs = [];
|
||||
final List<InputV2> inputs = [];
|
||||
TransactionType? txType;
|
||||
const TransactionSubType txSubType = TransactionSubType.none;
|
||||
int? nonce;
|
||||
Amount fee = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
final Map<String, dynamic> otherData = {};
|
||||
|
||||
final entryType = transactionEntry.txEntryType;
|
||||
|
||||
if (entryType is xelis_sdk.CoinbaseEntry) {
|
||||
final coinbase = entryType;
|
||||
txType = TransactionType.incoming;
|
||||
|
||||
final int decimals = await libXelisWallet!.getAssetDecimals(
|
||||
asset: xelis_sdk.xelisAsset,
|
||||
);
|
||||
|
||||
fee = Amount(rawValue: BigInt.zero, fractionDigits: decimals);
|
||||
|
||||
outputs.add(
|
||||
OutputV2.isarCantDoRequiredInDefaultConstructor(
|
||||
scriptPubKeyHex: "",
|
||||
valueStringSats: coinbase.reward.toString(),
|
||||
addresses: [thisAddress],
|
||||
walletOwns: true,
|
||||
),
|
||||
);
|
||||
} else if (entryType is xelis_sdk.BurnEntry) {
|
||||
final burn = entryType;
|
||||
txType = TransactionType.outgoing;
|
||||
|
||||
final int decimals = await libXelisWallet!.getAssetDecimals(
|
||||
asset: burn.asset,
|
||||
);
|
||||
|
||||
fee = Amount(
|
||||
rawValue: BigInt.from(burn.fee),
|
||||
fractionDigits: decimals,
|
||||
);
|
||||
|
||||
inputs.add(
|
||||
InputV2.isarCantDoRequiredInDefaultConstructor(
|
||||
scriptSigAsm: null,
|
||||
scriptSigHex: null,
|
||||
sequence: null,
|
||||
outpoint: null,
|
||||
valueStringSats: burn.amount.toString(),
|
||||
addresses: [thisAddress],
|
||||
witness: null,
|
||||
innerRedeemScriptAsm: null,
|
||||
coinbase: null,
|
||||
walletOwns: true,
|
||||
),
|
||||
);
|
||||
|
||||
outputs.add(
|
||||
OutputV2.isarCantDoRequiredInDefaultConstructor(
|
||||
scriptPubKeyHex: "",
|
||||
valueStringSats: burn.amount.toString(),
|
||||
addresses: ['burn'],
|
||||
walletOwns: false,
|
||||
),
|
||||
);
|
||||
|
||||
otherData['burnAsset'] = burn.asset;
|
||||
} else if (entryType is xelis_sdk.IncomingEntry) {
|
||||
final incoming = entryType;
|
||||
txType =
|
||||
incoming.from == thisAddress
|
||||
? TransactionType.sentToSelf
|
||||
: TransactionType.incoming;
|
||||
|
||||
for (final transfer in incoming.transfers) {
|
||||
final int decimals = await libXelisWallet!.getAssetDecimals(
|
||||
asset: transfer.asset,
|
||||
);
|
||||
|
||||
fee = Amount(rawValue: BigInt.zero, fractionDigits: decimals);
|
||||
|
||||
outputs.add(
|
||||
OutputV2.isarCantDoRequiredInDefaultConstructor(
|
||||
scriptPubKeyHex: "",
|
||||
valueStringSats: transfer.amount.toString(),
|
||||
addresses: [thisAddress],
|
||||
walletOwns: true,
|
||||
),
|
||||
);
|
||||
|
||||
otherData['asset_${transfer.asset}'] = transfer.amount.toString();
|
||||
if (transfer.extraData != null) {
|
||||
otherData['extraData_${transfer.asset}'] =
|
||||
transfer.extraData!.toJson();
|
||||
}
|
||||
}
|
||||
} else if (entryType is xelis_sdk.OutgoingEntry) {
|
||||
final outgoing = entryType;
|
||||
txType = TransactionType.outgoing;
|
||||
nonce = outgoing.nonce;
|
||||
|
||||
for (final transfer in outgoing.transfers) {
|
||||
final int decimals = await libXelisWallet!.getAssetDecimals(
|
||||
asset: transfer.asset,
|
||||
);
|
||||
|
||||
fee = Amount(
|
||||
rawValue: BigInt.from(outgoing.fee),
|
||||
fractionDigits: decimals,
|
||||
);
|
||||
|
||||
inputs.add(
|
||||
InputV2.isarCantDoRequiredInDefaultConstructor(
|
||||
scriptSigHex: null,
|
||||
scriptSigAsm: null,
|
||||
sequence: null,
|
||||
outpoint: null,
|
||||
addresses: [thisAddress],
|
||||
valueStringSats: (transfer.amount + outgoing.fee).toString(),
|
||||
witness: null,
|
||||
innerRedeemScriptAsm: null,
|
||||
coinbase: null,
|
||||
walletOwns: true,
|
||||
),
|
||||
);
|
||||
|
||||
outputs.add(
|
||||
OutputV2.isarCantDoRequiredInDefaultConstructor(
|
||||
scriptPubKeyHex: "",
|
||||
valueStringSats: transfer.amount.toString(),
|
||||
addresses: [transfer.destination],
|
||||
walletOwns: false,
|
||||
),
|
||||
);
|
||||
|
||||
otherData['asset_${transfer.asset}_amount'] =
|
||||
transfer.amount.toString();
|
||||
otherData['asset_${transfer.asset}_fee'] = fee.raw.toString();
|
||||
if (transfer.extraData != null) {
|
||||
otherData['extraData_${transfer.asset}'] =
|
||||
transfer.extraData!.toJson();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Skip unknown entry types
|
||||
continue;
|
||||
}
|
||||
|
||||
final txn = TransactionV2(
|
||||
walletId: walletId,
|
||||
blockHash: "", // Not provided in Xelis data
|
||||
hash: transactionEntry.hash,
|
||||
txid: transactionEntry.hash,
|
||||
timestamp:
|
||||
(transactionEntry.timestamp?.millisecondsSinceEpoch ?? 0) ~/ 1000,
|
||||
height: transactionEntry.topoheight,
|
||||
inputs: List.unmodifiable(inputs),
|
||||
outputs: List.unmodifiable(outputs),
|
||||
version: -1, // Version not provided
|
||||
type: txType,
|
||||
subType: txSubType,
|
||||
otherData: jsonEncode({
|
||||
...otherData,
|
||||
if (nonce != null) 'nonce': nonce,
|
||||
'overrideFee': fee.toJsonString(),
|
||||
}),
|
||||
);
|
||||
|
||||
// Logging.instance.log(
|
||||
// "Entry done ${entryType.toString()}",
|
||||
// level: LogLevel.Debug,
|
||||
// );
|
||||
|
||||
txns.add(txn);
|
||||
} catch (e, s) {
|
||||
Logging.instance.w(
|
||||
"Error in $runtimeType handling transaction: $jsonString",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
await updateBalance();
|
||||
|
||||
await mainDB.updateOrPutTransactionV2s(txns);
|
||||
return txns.map((e) => e.txid).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> updateUTXOs() async {
|
||||
// not used in xel
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> checkSaveInitialReceivingAddress() async {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@override
|
||||
FilterOperation? get changeAddressFilterOperation =>
|
||||
throw UnimplementedError("Not used for $runtimeType");
|
||||
|
||||
@override
|
||||
FilterOperation? get receivingAddressFilterOperation =>
|
||||
FilterGroup.and(standardReceivingAddressFilters);
|
||||
|
||||
@override
|
||||
Future<FeeObject> get fees async {
|
||||
// TODO: implement _getFees... maybe
|
||||
return FeeObject(
|
||||
numberOfBlocksFast: 10,
|
||||
numberOfBlocksAverage: 10,
|
||||
numberOfBlocksSlow: 10,
|
||||
fast: 1,
|
||||
medium: 1,
|
||||
slow: 1,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TxData> prepareSend({required TxData txData, String? assetId}) async {
|
||||
try {
|
||||
checkInitialized();
|
||||
|
||||
final recipients =
|
||||
txData.recipients?.isNotEmpty == true
|
||||
? txData.recipients!
|
||||
: throw ArgumentError(
|
||||
'Address cannot be empty.',
|
||||
); // in the future, support for multiple recipients will work.
|
||||
|
||||
final asset = assetId ?? xelis_sdk.xelisAsset;
|
||||
|
||||
// Calculate total send amount
|
||||
final totalSendAmount = recipients.fold<Amount>(
|
||||
Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
),
|
||||
(sum, recipient) => sum + recipient.amount,
|
||||
);
|
||||
|
||||
// Check balance using raw method
|
||||
final xelBalance = await libXelisWallet!.getXelisBalanceRaw();
|
||||
final balance = Amount(
|
||||
rawValue: xelBalance,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
|
||||
// Estimate fee using the shared method
|
||||
final boostedFee = await estimateFeeFor(
|
||||
totalSendAmount,
|
||||
1,
|
||||
feeMultiplier: 1.0,
|
||||
recipients: recipients,
|
||||
assetId: asset,
|
||||
);
|
||||
|
||||
// Check if we have enough for both transfers and fee
|
||||
if (totalSendAmount + boostedFee > balance) {
|
||||
final requiredAmt = await libXelisWallet!.formatCoin(
|
||||
atomicAmount: (totalSendAmount + boostedFee).raw,
|
||||
assetHash: asset,
|
||||
);
|
||||
|
||||
final availableAmt = await libXelisWallet!.formatCoin(
|
||||
atomicAmount: xelBalance,
|
||||
assetHash: asset,
|
||||
);
|
||||
|
||||
throw Exception(
|
||||
"Insufficient balance to cover transfers and fees. "
|
||||
"Required: $requiredAmt, Available: $availableAmt",
|
||||
);
|
||||
}
|
||||
|
||||
return txData.copyWith(
|
||||
fee: boostedFee,
|
||||
otherData: jsonEncode({'asset': asset}),
|
||||
);
|
||||
} catch (_) {
|
||||
// Logging.instance.log(
|
||||
// "Exception rethrown from prepareSend(): $e\n$s",
|
||||
// level: LogLevel.Error,
|
||||
// );
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Amount> estimateFeeFor(
|
||||
Amount amount,
|
||||
int feeRate, {
|
||||
double? feeMultiplier,
|
||||
List<TxRecipient> recipients = const [],
|
||||
String? assetId,
|
||||
}) async {
|
||||
try {
|
||||
checkInitialized();
|
||||
final asset = assetId ?? xelis_sdk.xelisAsset;
|
||||
|
||||
// Default values for a new wallet or when estimation fails
|
||||
final defaultDecimals = cryptoCurrency.fractionDigits;
|
||||
final defaultFee = BigInt.from(0);
|
||||
|
||||
// Use default address if recipients list is empty to ensure basic fee estimates are readily available
|
||||
final effectiveRecipients =
|
||||
recipients.isNotEmpty
|
||||
? recipients
|
||||
: [
|
||||
(
|
||||
address:
|
||||
'xel:xz9574c80c4xegnvurazpmxhw5dlg2n0g9qm60uwgt75uqyx3pcsqzzra9m',
|
||||
amount: amount,
|
||||
isChange: false,
|
||||
),
|
||||
];
|
||||
|
||||
try {
|
||||
final transfers = await Future.wait(
|
||||
effectiveRecipients.map((recipient) async {
|
||||
try {
|
||||
final amt = double.parse(
|
||||
await libXelisWallet!.formatCoin(
|
||||
atomicAmount: recipient.amount.raw,
|
||||
assetHash: asset,
|
||||
),
|
||||
);
|
||||
return x_wallet.Transfer(
|
||||
floatAmount: amt,
|
||||
strAddress: recipient.address,
|
||||
assetHash: asset,
|
||||
extraData: null,
|
||||
);
|
||||
} catch (e, s) {
|
||||
// Handle formatCoin error - use default conversion
|
||||
Logging.instance.d(
|
||||
"formatCoin failed, using fallback conversion",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
final rawAmount = recipient.amount.raw;
|
||||
final floatAmount =
|
||||
rawAmount / BigInt.from(10).pow(defaultDecimals);
|
||||
return x_wallet.Transfer(
|
||||
floatAmount: floatAmount.toDouble(),
|
||||
strAddress: recipient.address,
|
||||
assetHash: asset,
|
||||
extraData: null,
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
final decimals = await libXelisWallet!.getAssetDecimals(asset: asset);
|
||||
final estimatedFee = double.parse(
|
||||
await libXelisWallet!.estimateFees(transfers: transfers),
|
||||
);
|
||||
final rawFee = (estimatedFee * pow(10, decimals)).round();
|
||||
return Amount(
|
||||
rawValue: BigInt.from(rawFee),
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.d(
|
||||
"Fee estimation failed. Using fallback fee: $defaultFee",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
return Amount(
|
||||
rawValue: defaultFee,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
}
|
||||
} catch (_) {
|
||||
// Logging.instance.log(
|
||||
// "Exception rethrown from estimateFeeFor(): $e\n$s",
|
||||
// level: LogLevel.Error,
|
||||
// );
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TxData> confirmSend({required TxData txData}) async {
|
||||
try {
|
||||
checkInitialized();
|
||||
|
||||
// Validate recipients
|
||||
if (txData.recipients == null || txData.recipients!.length != 1) {
|
||||
throw Exception("$runtimeType confirmSend requires 1 recipient");
|
||||
}
|
||||
|
||||
final recipient = txData.recipients!.first;
|
||||
final Amount sendAmount = recipient.amount;
|
||||
|
||||
final asset =
|
||||
(txData.otherData != null
|
||||
? jsonDecode(txData.otherData!)
|
||||
: null)?['asset']
|
||||
as String? ??
|
||||
xelis_sdk.xelisAsset;
|
||||
|
||||
final amt = double.parse(
|
||||
await libXelisWallet!.formatCoin(
|
||||
atomicAmount: sendAmount.raw,
|
||||
assetHash: asset,
|
||||
),
|
||||
);
|
||||
|
||||
// Create a transfer transaction
|
||||
final txJson = await libXelisWallet!.createTransfersTransaction(
|
||||
transfers: [
|
||||
x_wallet.Transfer(
|
||||
floatAmount: amt,
|
||||
strAddress: recipient.address,
|
||||
assetHash: asset,
|
||||
extraData: null, // Add extra data if needed
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
final txMap = jsonDecode(txJson);
|
||||
final txHash = txMap['hash'] as String;
|
||||
|
||||
// Broadcast the transaction
|
||||
await libXelisWallet!.broadcastTransaction(txHash: txHash);
|
||||
|
||||
return await updateSentCachedTxData(
|
||||
txData: txData.copyWith(txid: txHash),
|
||||
);
|
||||
} catch (_) {
|
||||
// Logging.instance.log(
|
||||
// "Exception rethrown from confirmSend(): $e\n$s",
|
||||
// level: LogLevel.Error,
|
||||
// );
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleEvent(Event event) async {
|
||||
try {
|
||||
switch (event) {
|
||||
case NewTopoheight(:final height):
|
||||
await handleNewTopoHeight(height);
|
||||
case NewAsset(:final asset):
|
||||
await handleNewAsset(asset);
|
||||
case NewTransaction(:final transaction):
|
||||
await handleNewTransaction(transaction);
|
||||
case BalanceChanged(:final event):
|
||||
await handleBalanceChanged(event);
|
||||
case Rescan(:final startTopoheight):
|
||||
await handleRescan(startTopoheight);
|
||||
case Online():
|
||||
await handleOnline();
|
||||
case Offline():
|
||||
await handleOffline();
|
||||
case HistorySynced(:final topoheight):
|
||||
await handleHistorySynced(topoheight);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
"Error in $runtimeType handleEvent($event)",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleNewTopoHeight(int height) async {
|
||||
await info.updateCachedChainHeight(newHeight: height, isar: mainDB.isar);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleNewTransaction(xelis_sdk.TransactionEntry tx) async {
|
||||
try {
|
||||
final txListJson = [jsonEncode(tx.toString())];
|
||||
final newTxIds = await updateTransactions(
|
||||
isRescan: false,
|
||||
rawTransactions: txListJson,
|
||||
);
|
||||
|
||||
await updateBalance();
|
||||
|
||||
// Logging.instance.log(
|
||||
// "New transaction processed: ${newTxIds.first}",
|
||||
// level: LogLevel.Info,
|
||||
// );
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
"Error in $runtimeType handleNewTransaction($tx)",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleBalanceChanged(xelis_sdk.BalanceChangedEvent event) async {
|
||||
try {
|
||||
final asset = event.assetHash;
|
||||
if (asset == xelis_sdk.xelisAsset) {
|
||||
await updateBalance(newBalance: event.balance);
|
||||
}
|
||||
|
||||
// TODO: Update asset balances if needed
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
"Error in $runtimeType handleBalanceChanged($event)",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleRescan(int startTopoheight) async {
|
||||
await refreshMutex.protect(() async {
|
||||
await mainDB.deleteWalletBlockchainData(walletId);
|
||||
await updateTransactions(isRescan: true, topoheight: startTopoheight);
|
||||
await updateBalance();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleOnline() async {
|
||||
await updateChainHeight();
|
||||
await updateBalance();
|
||||
await updateTransactions();
|
||||
GlobalEventBus.instance.fire(
|
||||
WalletSyncStatusChangedEvent(
|
||||
WalletSyncStatus.synced,
|
||||
walletId,
|
||||
info.coin,
|
||||
),
|
||||
);
|
||||
unawaited(refresh());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleOffline() async {
|
||||
GlobalEventBus.instance.fire(
|
||||
WalletSyncStatusChangedEvent(
|
||||
WalletSyncStatus.unableToSync,
|
||||
walletId,
|
||||
info.coin,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleHistorySynced(int topoheight) async {
|
||||
await updateChainHeight();
|
||||
await updateBalance();
|
||||
await updateTransactions();
|
||||
GlobalEventBus.instance.fire(
|
||||
WalletSyncStatusChangedEvent(
|
||||
WalletSyncStatus.synced,
|
||||
walletId,
|
||||
info.coin,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleNewAsset(xelis_sdk.AssetData asset) async {
|
||||
// TODO: Store asset information if needed
|
||||
// TODO: Update UI/state for new asset
|
||||
Logging.instance.d("New xelis asset detected: $asset");
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> refresh({int? topoheight}) async {
|
||||
await refreshMutex.protect(() async {
|
||||
try {
|
||||
final bool online = await libXelisWallet!.isOnline();
|
||||
if (online == true) {
|
||||
await updateChainHeight(topoheight: topoheight);
|
||||
await updateBalance();
|
||||
await updateTransactions();
|
||||
} else {
|
||||
GlobalEventBus.instance.fire(
|
||||
WalletSyncStatusChangedEvent(
|
||||
WalletSyncStatus.unableToSync,
|
||||
walletId,
|
||||
info.coin,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
"Error in $runtimeType refresh()",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -2,8 +2,9 @@ import '../../crypto_currency/intermediate/cryptonote_currency.dart';
|
|||
import '../wallet.dart';
|
||||
import '../wallet_mixin_interfaces/coin_control_interface.dart';
|
||||
import '../wallet_mixin_interfaces/mnemonic_interface.dart';
|
||||
import 'external_wallet.dart';
|
||||
|
||||
abstract class CryptonoteWallet<T extends CryptonoteCurrency> extends Wallet<T>
|
||||
abstract class CryptonoteWallet<T extends CryptonoteCurrency> extends ExternalWallet<T>
|
||||
with MnemonicInterface<T>, CoinControlInterface<T> {
|
||||
CryptonoteWallet(super.currency);
|
||||
}
|
||||
|
|
12
lib/wallets/wallet/intermediate/external_wallet.dart
Normal file
12
lib/wallets/wallet/intermediate/external_wallet.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import '../../crypto_currency/crypto_currency.dart';
|
||||
import '../wallet.dart';
|
||||
|
||||
// anstract class to be fleshed out for the standardization of wallet implementations
|
||||
// that rely on bridged code libraries outside, or external native wallet functions
|
||||
abstract class ExternalWallet<T extends CryptoCurrency> extends Wallet<T> {
|
||||
ExternalWallet(super.currency);
|
||||
|
||||
// wallet opening and initialization separated to prevent db lock collision errors
|
||||
// must be overridden
|
||||
Future<void> open();
|
||||
}
|
|
@ -190,6 +190,7 @@ abstract class LibMoneroWallet<T extends CryptonoteCurrency>
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> open() async {
|
||||
bool wasNull = false;
|
||||
|
||||
|
|
438
lib/wallets/wallet/intermediate/lib_xelis_wallet.dart
Normal file
438
lib/wallets/wallet/intermediate/lib_xelis_wallet.dart
Normal file
|
@ -0,0 +1,438 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:xelis_dart_sdk/xelis_dart_sdk.dart' as xelis_sdk;
|
||||
import 'package:xelis_flutter/src/api/network.dart' as x_network;
|
||||
import 'package:xelis_flutter/src/api/wallet.dart' as x_wallet;
|
||||
|
||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||
import '../../../utilities/logger.dart';
|
||||
import '../../../utilities/stack_file_system.dart';
|
||||
import '../../crypto_currency/crypto_currency.dart';
|
||||
import '../../crypto_currency/intermediate/electrum_currency.dart';
|
||||
import '../wallet_mixin_interfaces/mnemonic_interface.dart';
|
||||
import 'external_wallet.dart';
|
||||
|
||||
enum XelisTableSize {
|
||||
low,
|
||||
full;
|
||||
|
||||
bool get isLow => this == XelisTableSize.low;
|
||||
|
||||
static XelisTableSize get platformDefault {
|
||||
if (kIsWeb) {
|
||||
return XelisTableSize.low;
|
||||
}
|
||||
return XelisTableSize.full;
|
||||
}
|
||||
}
|
||||
|
||||
class XelisTableState {
|
||||
final bool isGenerating;
|
||||
final XelisTableSize currentSize;
|
||||
final XelisTableSize _desiredSize;
|
||||
|
||||
XelisTableSize get desiredSize {
|
||||
if (kIsWeb) {
|
||||
return XelisTableSize.low;
|
||||
}
|
||||
return _desiredSize;
|
||||
}
|
||||
|
||||
const XelisTableState({
|
||||
this.isGenerating = false,
|
||||
this.currentSize = XelisTableSize.low,
|
||||
XelisTableSize desiredSize = XelisTableSize.full,
|
||||
}) : _desiredSize = desiredSize;
|
||||
|
||||
XelisTableState copyWith({
|
||||
bool? isGenerating,
|
||||
XelisTableSize? currentSize,
|
||||
XelisTableSize? desiredSize,
|
||||
}) {
|
||||
return XelisTableState(
|
||||
isGenerating: isGenerating ?? this.isGenerating,
|
||||
currentSize: currentSize ?? this.currentSize,
|
||||
desiredSize: kIsWeb ? XelisTableSize.low : (desiredSize ?? _desiredSize),
|
||||
);
|
||||
}
|
||||
|
||||
factory XelisTableState.fromJson(Map<String, dynamic> json) {
|
||||
return XelisTableState(
|
||||
isGenerating: json['isGenerating'] as bool,
|
||||
currentSize: XelisTableSize.values[json['currentSize'] as int],
|
||||
desiredSize: XelisTableSize.values[json['desiredSize'] as int],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'isGenerating': isGenerating,
|
||||
'currentSize': currentSize.index,
|
||||
'desiredSize': _desiredSize.index,
|
||||
};
|
||||
}
|
||||
|
||||
extension XelisNetworkConversion on CryptoCurrencyNetwork {
|
||||
x_network.Network get xelisNetwork {
|
||||
switch (this) {
|
||||
case CryptoCurrencyNetwork.main:
|
||||
return x_network.Network.mainnet;
|
||||
case CryptoCurrencyNetwork.test:
|
||||
return x_network.Network.testnet;
|
||||
default:
|
||||
throw ArgumentError('Unsupported network type for Xelis: $this');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CryptoCurrencyNetworkConversion on x_network.Network {
|
||||
CryptoCurrencyNetwork get cryptoCurrencyNetwork {
|
||||
switch (this) {
|
||||
case x_network.Network.mainnet:
|
||||
return CryptoCurrencyNetwork.main;
|
||||
case x_network.Network.testnet:
|
||||
return CryptoCurrencyNetwork.test;
|
||||
default:
|
||||
throw ArgumentError('Unsupported Xelis network type: $this');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Event {
|
||||
const Event();
|
||||
}
|
||||
|
||||
final class NewTopoheight extends Event {
|
||||
final int height;
|
||||
const NewTopoheight(this.height);
|
||||
}
|
||||
|
||||
final class NewAsset extends Event {
|
||||
final xelis_sdk.AssetData asset;
|
||||
const NewAsset(this.asset);
|
||||
}
|
||||
|
||||
final class NewTransaction extends Event {
|
||||
final xelis_sdk.TransactionEntry transaction;
|
||||
const NewTransaction(this.transaction);
|
||||
}
|
||||
|
||||
final class BalanceChanged extends Event {
|
||||
final xelis_sdk.BalanceChangedEvent event;
|
||||
const BalanceChanged(this.event);
|
||||
}
|
||||
|
||||
final class Rescan extends Event {
|
||||
final int startTopoheight;
|
||||
const Rescan(this.startTopoheight);
|
||||
}
|
||||
|
||||
final class Online extends Event {
|
||||
const Online();
|
||||
}
|
||||
|
||||
final class Offline extends Event {
|
||||
const Offline();
|
||||
}
|
||||
|
||||
final class HistorySynced extends Event {
|
||||
final int topoheight;
|
||||
const HistorySynced(this.topoheight);
|
||||
}
|
||||
|
||||
abstract class LibXelisWallet<T extends ElectrumCurrency>
|
||||
extends ExternalWallet<T>
|
||||
with MnemonicInterface {
|
||||
LibXelisWallet(super.currency);
|
||||
|
||||
static const String _kHasFullTablesKey = 'xelis_has_full_tables';
|
||||
static const String _kGeneratingTablesKey = 'xelis_generating_tables';
|
||||
static const String _kWantsFullTablesKey = 'xelis_wants_full_tables';
|
||||
static final _tableGenerationMutex = Mutex();
|
||||
static Completer<void>? _tableGenerationCompleter;
|
||||
|
||||
x_wallet.XelisWallet? libXelisWallet;
|
||||
int pruningHeight = 0;
|
||||
|
||||
x_wallet.XelisWallet? get wallet => libXelisWallet;
|
||||
set wallet(x_wallet.XelisWallet? newWallet) {
|
||||
if (newWallet == null && libXelisWallet != null) {
|
||||
throw StateError('Cannot set wallet to null after initialization');
|
||||
}
|
||||
libXelisWallet = newWallet;
|
||||
}
|
||||
|
||||
void checkInitialized() {
|
||||
if (libXelisWallet == null) {
|
||||
throw StateError('libXelisWallet not initialized');
|
||||
}
|
||||
}
|
||||
|
||||
final syncMutex = Mutex();
|
||||
Timer? timer;
|
||||
|
||||
StreamSubscription<void>? _eventSubscription;
|
||||
|
||||
Future<String> getPrecomputedTablesPath() async {
|
||||
if (kIsWeb) {
|
||||
return "";
|
||||
} else {
|
||||
final appDir = await StackFileSystem.applicationXelisTableDirectory();
|
||||
return "${appDir.path}${Platform.pathSeparator}";
|
||||
}
|
||||
}
|
||||
|
||||
Future<XelisTableState> getTableState() async {
|
||||
final hasFullTables =
|
||||
await secureStorageInterface.read(key: _kHasFullTablesKey) == 'true';
|
||||
final isGenerating =
|
||||
await secureStorageInterface.read(key: _kGeneratingTablesKey) == 'true';
|
||||
final wantsFull =
|
||||
await secureStorageInterface.read(key: _kWantsFullTablesKey) != 'false';
|
||||
|
||||
return XelisTableState(
|
||||
isGenerating: isGenerating,
|
||||
currentSize: hasFullTables ? XelisTableSize.full : XelisTableSize.low,
|
||||
desiredSize: wantsFull ? XelisTableSize.full : XelisTableSize.low,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setTableState(XelisTableState state) async {
|
||||
await secureStorageInterface.write(
|
||||
key: _kHasFullTablesKey,
|
||||
value: state.currentSize == XelisTableSize.full ? 'true' : 'false',
|
||||
);
|
||||
await secureStorageInterface.write(
|
||||
key: _kGeneratingTablesKey,
|
||||
value: state.isGenerating ? 'true' : 'false',
|
||||
);
|
||||
await secureStorageInterface.write(
|
||||
key: _kWantsFullTablesKey,
|
||||
value: state.desiredSize == XelisTableSize.full ? 'true' : 'false',
|
||||
);
|
||||
}
|
||||
|
||||
Stream<Event> convertRawEvents() async* {
|
||||
checkInitialized();
|
||||
final rawEventStream = libXelisWallet!.eventsStream();
|
||||
|
||||
await for (final rawData in rawEventStream) {
|
||||
final json = jsonDecode(rawData);
|
||||
try {
|
||||
final eventType = xelis_sdk.WalletEvent.fromStr(
|
||||
json['event'] as String,
|
||||
);
|
||||
switch (eventType) {
|
||||
case xelis_sdk.WalletEvent.newTopoHeight:
|
||||
yield NewTopoheight(json['data']['topoheight'] as int);
|
||||
case xelis_sdk.WalletEvent.newAsset:
|
||||
yield NewAsset(
|
||||
xelis_sdk.AssetData.fromJson(
|
||||
json['data'] as Map<String, dynamic>,
|
||||
),
|
||||
);
|
||||
case xelis_sdk.WalletEvent.newTransaction:
|
||||
yield NewTransaction(
|
||||
xelis_sdk.TransactionEntry.fromJson(
|
||||
json['data'] as Map<String, dynamic>,
|
||||
),
|
||||
);
|
||||
case xelis_sdk.WalletEvent.balanceChanged:
|
||||
yield BalanceChanged(
|
||||
xelis_sdk.BalanceChangedEvent.fromJson(
|
||||
json['data'] as Map<String, dynamic>,
|
||||
),
|
||||
);
|
||||
case xelis_sdk.WalletEvent.rescan:
|
||||
yield Rescan(json['data']['start_topoheight'] as int);
|
||||
case xelis_sdk.WalletEvent.online:
|
||||
yield const Online();
|
||||
case xelis_sdk.WalletEvent.offline:
|
||||
yield const Offline();
|
||||
case xelis_sdk.WalletEvent.historySynced:
|
||||
yield HistorySynced(json['data']['topoheight'] as int);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
"Error processing xelis wallet event: $rawData",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> handleEvent(Event event) async {}
|
||||
Future<void> handleNewTopoHeight(int height);
|
||||
Future<void> handleNewTransaction(xelis_sdk.TransactionEntry tx);
|
||||
Future<void> handleBalanceChanged(xelis_sdk.BalanceChangedEvent event);
|
||||
Future<void> handleRescan(int startTopoheight) async {}
|
||||
Future<void> handleOnline() async {}
|
||||
Future<void> handleOffline() async {}
|
||||
Future<void> handleHistorySynced(int topoheight) async {}
|
||||
Future<void> handleNewAsset(xelis_sdk.AssetData asset) async {}
|
||||
|
||||
@override
|
||||
Future<void> refresh({int? topoheight});
|
||||
|
||||
Future<void> connect() async {
|
||||
final node = getCurrentNode();
|
||||
try {
|
||||
_eventSubscription = convertRawEvents().listen(handleEvent);
|
||||
|
||||
Logging.instance.i("Connecting to node: ${node.host}:${node.port}");
|
||||
await libXelisWallet!.onlineMode(
|
||||
daemonAddress: "${node.host}:${node.port}",
|
||||
);
|
||||
await super.refresh();
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
"rethrowing error connecting to node: $node",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
List<FilterOperation> get standardReceivingAddressFilters => [
|
||||
FilterCondition.equalTo(property: r"type", value: info.mainAddressType),
|
||||
const FilterCondition.equalTo(
|
||||
property: r"subType",
|
||||
value: AddressSubType.receiving,
|
||||
),
|
||||
];
|
||||
|
||||
List<FilterOperation> get standardChangeAddressFilters => [
|
||||
FilterCondition.equalTo(property: r"type", value: info.mainAddressType),
|
||||
const FilterCondition.equalTo(
|
||||
property: r"subType",
|
||||
value: AddressSubType.change,
|
||||
),
|
||||
];
|
||||
|
||||
static Future<bool> checkWalletExists(String walletId) async {
|
||||
final xelisDir = await StackFileSystem.applicationXelisDirectory();
|
||||
final walletDir = Directory(
|
||||
"${xelisDir.path}${Platform.pathSeparator}$walletId",
|
||||
);
|
||||
// TODO: should we check for certain files within the dir?
|
||||
return await walletDir.exists();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> open() async {
|
||||
try {
|
||||
await connect();
|
||||
} catch (e) {
|
||||
// Logging.instance.log(
|
||||
// "Failed to start sync: $e",
|
||||
// level: LogLevel.Error,
|
||||
// );
|
||||
rethrow;
|
||||
}
|
||||
unawaited(refresh());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> exit() async {
|
||||
await refreshMutex.protect(() async {
|
||||
timer?.cancel();
|
||||
timer = null;
|
||||
|
||||
await _eventSubscription?.cancel();
|
||||
_eventSubscription = null;
|
||||
|
||||
await libXelisWallet?.offlineMode();
|
||||
await super.exit();
|
||||
});
|
||||
}
|
||||
|
||||
void invalidSeedLengthCheck(int length) {
|
||||
if (!(length == 25)) {
|
||||
throw Exception("Invalid Xelis mnemonic length found: $length");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension XelisTableManagement on LibXelisWallet {
|
||||
Future<bool> isTableUpgradeAvailable() async {
|
||||
if (kIsWeb) return false;
|
||||
|
||||
final state = await getTableState();
|
||||
return state.currentSize != state.desiredSize;
|
||||
}
|
||||
|
||||
Future<void> updateTablesToDesiredSize() async {
|
||||
if (kIsWeb) return;
|
||||
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
if (LibXelisWallet._tableGenerationCompleter != null) {
|
||||
try {
|
||||
await LibXelisWallet._tableGenerationCompleter!.future;
|
||||
return;
|
||||
} catch (_) {
|
||||
// Previous generation failed, we'll try again
|
||||
}
|
||||
}
|
||||
|
||||
await LibXelisWallet._tableGenerationMutex.protect(() async {
|
||||
// Check again after acquiring mutex
|
||||
if (LibXelisWallet._tableGenerationCompleter != null) {
|
||||
try {
|
||||
await LibXelisWallet._tableGenerationCompleter!.future;
|
||||
return;
|
||||
} catch (_) {
|
||||
// Previous generation failed, we'll try again
|
||||
}
|
||||
}
|
||||
|
||||
final state = await getTableState();
|
||||
if (state.currentSize == state.desiredSize) return;
|
||||
|
||||
LibXelisWallet._tableGenerationCompleter = Completer<void>();
|
||||
await setTableState(state.copyWith(isGenerating: true));
|
||||
|
||||
try {
|
||||
Logging.instance.i("Xelis: Generating large tables in background");
|
||||
|
||||
final tablePath = await getPrecomputedTablesPath();
|
||||
await x_wallet.updateTables(
|
||||
precomputedTablesPath: tablePath,
|
||||
l1Low: state.desiredSize.isLow,
|
||||
);
|
||||
|
||||
await setTableState(
|
||||
XelisTableState(
|
||||
isGenerating: false,
|
||||
currentSize: state.desiredSize,
|
||||
desiredSize: state.desiredSize,
|
||||
),
|
||||
);
|
||||
|
||||
Logging.instance.i("Xelis: Table upgrade done");
|
||||
LibXelisWallet._tableGenerationCompleter!.complete();
|
||||
} catch (e) {
|
||||
// Logging.instance.log(
|
||||
// "Failed to update tables: $e\n$s",
|
||||
// level: LogLevel.Error,
|
||||
// );
|
||||
await setTableState(state.copyWith(isGenerating: false));
|
||||
|
||||
LibXelisWallet._tableGenerationCompleter!.completeError(e);
|
||||
} finally {
|
||||
if (!LibXelisWallet._tableGenerationCompleter!.isCompleted) {
|
||||
LibXelisWallet._tableGenerationCompleter!.completeError(
|
||||
Exception('Table generation abandoned'),
|
||||
);
|
||||
}
|
||||
LibXelisWallet._tableGenerationCompleter = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -47,6 +47,7 @@ import 'impl/stellar_wallet.dart';
|
|||
import 'impl/sub_wallets/eth_token_wallet.dart';
|
||||
import 'impl/tezos_wallet.dart';
|
||||
import 'impl/wownero_wallet.dart';
|
||||
import 'impl/xelis_wallet.dart';
|
||||
import 'intermediate/cryptonote_wallet.dart';
|
||||
import 'wallet_mixin_interfaces/electrumx_interface.dart';
|
||||
import 'wallet_mixin_interfaces/lelantus_interface.dart';
|
||||
|
@ -172,8 +173,8 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
value: viewOnlyData!.toJsonEncodedString(),
|
||||
);
|
||||
} else if (wallet is MnemonicInterface) {
|
||||
if (wallet is CryptonoteWallet) {
|
||||
// currently a special case due to the xmr/wow libraries handling their
|
||||
if (wallet is CryptonoteWallet || wallet is XelisWallet) { //
|
||||
// currently a special case due to the xmr/wow/xelis libraries handling their
|
||||
// own mnemonic generation on new wallet creation
|
||||
// if its a restore we must set them
|
||||
if (mnemonic != null) {
|
||||
|
@ -406,6 +407,9 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
case const (Wownero):
|
||||
return WowneroWallet(net);
|
||||
|
||||
case const (Xelis):
|
||||
return XelisWallet(net);
|
||||
|
||||
default:
|
||||
// should never hit in reality
|
||||
throw Exception("Unknown crypto currency: ${walletInfo.coin}");
|
||||
|
|
|
@ -28,7 +28,7 @@ import '../utilities/util.dart';
|
|||
import '../wallets/isar/providers/eth/current_token_wallet_provider.dart';
|
||||
import '../wallets/wallet/impl/ethereum_wallet.dart';
|
||||
import '../wallets/wallet/impl/sub_wallets/eth_token_wallet.dart';
|
||||
import '../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../wallets/wallet/intermediate/external_wallet.dart';
|
||||
import '../wallets/wallet/wallet.dart';
|
||||
import 'conditional_parent.dart';
|
||||
import 'desktop/primary_button.dart';
|
||||
|
@ -111,7 +111,7 @@ class SimpleWalletCard extends ConsumerWidget {
|
|||
|
||||
if (context.mounted) {
|
||||
final Future<void> loadFuture;
|
||||
if (wallet is LibMoneroWallet) {
|
||||
if (wallet is ExternalWallet) {
|
||||
loadFuture = wallet.init().then((value) async => await (wallet).open());
|
||||
} else {
|
||||
loadFuture = wallet.init();
|
||||
|
|
77
lib/widgets/xelis_table_progress.dart
Normal file
77
lib/widgets/xelis_table_progress.dart
Normal file
|
@ -0,0 +1,77 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../../themes/stack_colors.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../widgets/progress_bar.dart';
|
||||
|
||||
import '../providers/providers.dart';
|
||||
|
||||
class XelisTableProgress extends ConsumerWidget {
|
||||
const XelisTableProgress({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final progressAsyncValue = ref.watch(xelisTableProgressProvider);
|
||||
|
||||
return DefaultTextStyle(
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodyLarge?.color ?? Colors.black,
|
||||
fontSize: 14,
|
||||
),
|
||||
child: Center(
|
||||
child: progressAsyncValue.when(
|
||||
data: (progress) => Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
constraints: const BoxConstraints(maxWidth: 450),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
"Generating Precomputed Tables...",
|
||||
style: STextStyles.desktopH3(context).copyWith(
|
||||
fontSize: 24,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
"These tables are required for the fast decryption of private transactions. This is a one-time process upon the creation of your first Xelis wallet in Stack Wallet.",
|
||||
style: STextStyles.subtitle600(context).copyWith(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).extension<StackColors>()!.textSubtitle1,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
progress.currentStep.displayName,
|
||||
style: STextStyles.titleBold12(context),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ProgressBar(
|
||||
width: 200,
|
||||
height: 8,
|
||||
fillColor: const Color.fromARGB(255,2,255,207),
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.textFieldDefaultBG,
|
||||
percent: progress.tableProgress ?? 0.0,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"${((progress.tableProgress ?? 0.0) * 100).toStringAsFixed(1)}%",
|
||||
style: STextStyles.label(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
loading: () => const SizedBox.shrink(),
|
||||
error: (_, __) => const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
|||
flutter_libsparkmobile
|
||||
frostdart
|
||||
tor_ffi_plugin
|
||||
xelis_flutter
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
|
|
@ -40,7 +40,7 @@ PODS:
|
|||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- ReachabilitySwift (5.0.0)
|
||||
- ReachabilitySwift (5.2.3)
|
||||
- share_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- "sqlite3 (3.46.0+1)":
|
||||
|
@ -67,6 +67,8 @@ PODS:
|
|||
- FlutterMacOS
|
||||
- window_size (0.0.2):
|
||||
- FlutterMacOS
|
||||
- xelis_flutter (0.0.1):
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- camera_macos (from `Flutter/ephemeral/.symlinks/plugins/camera_macos/macos`)
|
||||
|
@ -95,6 +97,7 @@ DEPENDENCIES:
|
|||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
|
||||
- window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`)
|
||||
- xelis_flutter (from `Flutter/ephemeral/.symlinks/plugins/xelis_flutter/macos`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
|
@ -154,6 +157,8 @@ EXTERNAL SOURCES:
|
|||
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos
|
||||
window_size:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/window_size/macos
|
||||
xelis_flutter:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/xelis_flutter/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
camera_macos: c2603f5eed16f05076cf17e12030d2ce55a77839
|
||||
|
@ -173,9 +178,9 @@ SPEC CHECKSUMS:
|
|||
isar_flutter_libs: 43385c99864c168fadba7c9adeddc5d38838ca6a
|
||||
lelantus: 308e42c5a648598936a07a234471dd8cf8e687a0
|
||||
local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3
|
||||
package_info_plus: f5790acc797bf17c3e959e9d6cf162cc68ff7523
|
||||
package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
ReachabilitySwift: 7f151ff156cea1481a8411701195ac6a984f4979
|
||||
share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7
|
||||
sqlite3: 292c3e1bfe89f64e51ea7fc7dab9182a017c8630
|
||||
sqlite3_flutter_libs: 1be4459672f8168ded2d8667599b8e3ca5e72b83
|
||||
|
@ -184,6 +189,7 @@ SPEC CHECKSUMS:
|
|||
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
|
||||
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
|
||||
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195
|
||||
xelis_flutter: 34e05f3621e46381fb1b10d7c11f63764d3f7a80
|
||||
|
||||
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
|
||||
|
||||
|
|
241
pubspec.lock
241
pubspec.lock
|
@ -50,10 +50,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
version: "2.7.0"
|
||||
async:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -183,50 +183,58 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
|
||||
sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.4.2"
|
||||
build_cli_annotations:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_cli_annotations
|
||||
sha256: b59d2769769efd6c9ff6d4c4cede0be115a566afc591705c2040b707534b1172
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
|
||||
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.1.2"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9"
|
||||
sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
version: "4.0.4"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
|
||||
sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.4.4"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
|
||||
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.13"
|
||||
version: "2.4.15"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
|
||||
sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.3.2"
|
||||
version: "8.0.0"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -239,10 +247,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
|
||||
sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.9.2"
|
||||
version: "8.9.5"
|
||||
calendar_date_picker2:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -404,10 +412,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: coverage
|
||||
sha256: "88b0fddbe4c92910fefc09cc0248f5e7f0cd23e450ded4c28f16ab8ee8f83268"
|
||||
sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
version: "1.11.1"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -564,10 +572,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab"
|
||||
sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.7"
|
||||
version: "2.3.8"
|
||||
dartx:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -742,10 +750,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
version: "2.1.4"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -823,9 +831,11 @@ packages:
|
|||
flutter_libsparkmobile:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../flutter_libsparkmobile"
|
||||
relative: true
|
||||
source: path
|
||||
path: "."
|
||||
ref: ca0c72cecc40fc0bfbafc0d26af675d973ab516b
|
||||
resolved-ref: ca0c72cecc40fc0bfbafc0d26af675d973ab516b
|
||||
url: "https://github.com/cypherstack/flutter_libsparkmobile.git"
|
||||
source: git
|
||||
version: "0.0.2"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
|
@ -871,10 +881,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398"
|
||||
sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.23"
|
||||
version: "2.0.24"
|
||||
flutter_riverpod:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -883,6 +893,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
flutter_rust_bridge:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_rust_bridge
|
||||
sha256: "5a5c7a5deeef2cc2ffe6076a33b0429f4a20ceac22a397297aed2b1eb067e611"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.9.0"
|
||||
flutter_secure_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -935,10 +953,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: "1b7723a814d84fb65869ea7115cdb3ee7c3be5a27a755c1ec60e049f6b9fcbb2"
|
||||
sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.11"
|
||||
version: "2.0.17"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
@ -990,10 +1008,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.3"
|
||||
google_fonts:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1070,18 +1088,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
|
||||
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
version: "3.2.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
version: "4.1.2"
|
||||
ieee754:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1123,10 +1141,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
|
||||
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "1.0.5"
|
||||
isar:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1179,10 +1197,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: json_serializable
|
||||
sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b
|
||||
sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.8.0"
|
||||
version: "6.9.0"
|
||||
jsontool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jsontool
|
||||
sha256: e49bf419e82d90f009426cd7fdec8d54ba8382975b3454ed16a3af3ee1d1b697
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
keyboard_dismisser:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1428,26 +1454,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
|
||||
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.2.0"
|
||||
package_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: df3eb3e0aed5c1107bb0fdb80a8e82e778114958b1c5ac5644fb1ac9cae8a998
|
||||
sha256: "739e0a5c3c4055152520fa321d0645ee98e932718b4c8efeeb51451968fe0790"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.0"
|
||||
version: "8.1.3"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66
|
||||
sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1460,10 +1486,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: path_parsing
|
||||
sha256: caa17e8f0b386eb190dd5b6a3b71211c76375aa8b6ffb4465b5863d019bdb334
|
||||
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
version: "1.1.0"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1476,18 +1502,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a
|
||||
sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.12"
|
||||
version: "2.2.16"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
|
||||
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
version: "2.4.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1636,18 +1662,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.2.0"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8
|
||||
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.5.0"
|
||||
qr:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1732,10 +1758,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
|
||||
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
version: "1.4.2"
|
||||
shelf_packages_handler:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1756,10 +1782,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611"
|
||||
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "3.0.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -1778,10 +1804,10 @@ packages:
|
|||
description:
|
||||
path: "."
|
||||
ref: master
|
||||
resolved-ref: b1fa8ca505e7e488edb4c2859f0218d48b15dead
|
||||
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
|
||||
url: "https://github.com/cypherstack/socks_socket.git"
|
||||
source: git
|
||||
version: "1.0.0"
|
||||
version: "1.1.1"
|
||||
solana:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1803,10 +1829,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: source_helper
|
||||
sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd"
|
||||
sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.4"
|
||||
version: "1.3.5"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1819,10 +1845,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: source_maps
|
||||
sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703"
|
||||
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.12"
|
||||
version: "0.10.13"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1892,10 +1918,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: stream_transform
|
||||
sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
|
||||
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.1.1"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1981,10 +2007,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: timing
|
||||
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
|
||||
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
version: "1.0.2"
|
||||
tint:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -2062,26 +2088,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e
|
||||
sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.1"
|
||||
version: "6.3.2"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af
|
||||
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
version: "3.2.1"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672"
|
||||
sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
version: "3.2.2"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -2094,18 +2120,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e"
|
||||
sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
version: "2.4.0"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4"
|
||||
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
version: "3.1.4"
|
||||
uuid:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -2118,26 +2144,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics
|
||||
sha256: "0b9149c6ddb013818075b072b9ddc1b89a5122fff1275d4648d297086b46c4f0"
|
||||
sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.12"
|
||||
version: "1.1.15"
|
||||
vector_graphics_codec:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_codec
|
||||
sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb"
|
||||
sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.12"
|
||||
version: "1.1.13"
|
||||
vector_graphics_compiler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: f3b9b6e4591c11394d4be4806c63e72d3a41778547b2c1e2a8a04fadcfd7d173
|
||||
sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.12"
|
||||
version: "1.1.16"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -2146,6 +2172,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
very_good_analysis:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: very_good_analysis
|
||||
sha256: "62d2b86d183fb81b2edc22913d9f155d26eb5cf3855173adb1f59fac85035c63"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -2207,10 +2241,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||
sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
web:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
|
@ -2235,6 +2269,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.5"
|
||||
web_socket_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_client
|
||||
sha256: "0ec5230852349191188c013112e4d2be03e3fc83dbe80139ead9bf3a136e53b5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.5"
|
||||
webdriver:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -2255,10 +2297,10 @@ packages:
|
|||
dependency: "direct overridden"
|
||||
description:
|
||||
name: win32
|
||||
sha256: "10169d3934549017f0ae278ccb07f828f9d6ea21573bab0fb77b0e1ef0fce454"
|
||||
sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.7.2"
|
||||
version: "5.10.1"
|
||||
win32_registry:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -2284,6 +2326,23 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xelis_dart_sdk:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xelis_dart_sdk
|
||||
sha256: "2a7f8ab4c30fad2fd824ba6ea4e83ac20c726b47c7aa4f1e713ef3971a3ec1f7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.24.0"
|
||||
xelis_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "v0.1.0"
|
||||
resolved-ref: c685c5d3550cca414ec30d4b61259761f129dda6
|
||||
url: "https://github.com/Tritonn204/xelis_flutter_ffi.git"
|
||||
source: git
|
||||
version: "0.1.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -2304,10 +2363,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
version: "3.1.3"
|
||||
zxcvbn:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
|
@ -73,6 +73,7 @@ final List<CryptoCurrency> _supportedCoins = List.unmodifiable([
|
|||
Stellar(CryptoCurrencyNetwork.main),
|
||||
Tezos(CryptoCurrencyNetwork.main),
|
||||
Wownero(CryptoCurrencyNetwork.main),
|
||||
Xelis(CryptoCurrencyNetwork.main),
|
||||
Bitcoin(CryptoCurrencyNetwork.test),
|
||||
Bitcoin(CryptoCurrencyNetwork.test4),
|
||||
Bitcoincash(CryptoCurrencyNetwork.test),
|
||||
|
@ -83,6 +84,7 @@ final List<CryptoCurrency> _supportedCoins = List.unmodifiable([
|
|||
Litecoin(CryptoCurrencyNetwork.test),
|
||||
Peercoin(CryptoCurrencyNetwork.test),
|
||||
Stellar(CryptoCurrencyNetwork.test),
|
||||
Xelis(CryptoCurrencyNetwork.test),
|
||||
]);
|
||||
|
||||
final ({String from, String to}) _swapDefaults = (from: "BTC", to: "XMR");
|
||||
|
|
|
@ -30,10 +30,15 @@ dependencies:
|
|||
frostdart:
|
||||
path: ./crypto_plugins/frostdart
|
||||
|
||||
xelis_flutter:
|
||||
git:
|
||||
url: https://github.com/Tritonn204/xelis_flutter_ffi.git
|
||||
ref: v0.1.0
|
||||
|
||||
flutter_libsparkmobile:
|
||||
git:
|
||||
url: https://github.com/cypherstack/flutter_libsparkmobile.git
|
||||
ref: 28d0f6c8db56a7d14d6495e810b8705a5c438929
|
||||
ref: ca0c72cecc40fc0bfbafc0d26af675d973ab516b
|
||||
|
||||
# cs_monero compat (unpublished)
|
||||
compat:
|
||||
|
|
|
@ -7,6 +7,6 @@ git reset --hard
|
|||
cmake -G "Visual Studio 17 2022" -A x64 -S . -B build
|
||||
cd build
|
||||
cmake --build .
|
||||
if not exist "..\..\..\..\build\" mkdir "..\..\..\..\build\"
|
||||
xcopy bin\Debug\libsecp256k1-2.dll "..\..\..\..\build\secp256k1.dll" /Y
|
||||
if not exist "..\..\..\..\..\build\" mkdir "..\..\..\..\..\build\"
|
||||
xcopy bin\Debug\libsecp256k1-2.dll "..\..\..\..\..\build\secp256k1.dll" /Y
|
||||
cd ..\..\..\
|
||||
|
|
|
@ -30,7 +30,7 @@ void main() {
|
|||
url: Uri.parse(
|
||||
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids"
|
||||
"=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,bitcoin-cash"
|
||||
",namecoin,wownero,ethereum,particl,nano,banano,stellar,tezos"
|
||||
",namecoin,wownero,ethereum,particl,nano,banano,stellar,tezos,xelis"
|
||||
"&order=market_cap_desc&per_page=50"
|
||||
"&page=1&sparkline=false"),
|
||||
headers: {
|
||||
|
@ -93,7 +93,10 @@ void main() {
|
|||
'max_supply":null,"ath":0.00013848,"ath_change_percentage":-79.75864'
|
||||
',"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-07,"atl_chang'
|
||||
'e_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01.177Z","roi'
|
||||
'":null,"last_updated":"2022-08-22T16:38:32.826Z"}]'),
|
||||
'":null,"last_updated":"2022-08-22T16:38:32.826Z"},{"id":"xelis","sy'
|
||||
'mbol":"xel","name":"Xelis","image":"https://assets.coingecko.com/co'
|
||||
'ins/images/37615/large/green_background_black_logo.png","current_pr'
|
||||
'ice":0.00001234,"price_change_percentage_24h":5.67}]'),
|
||||
200));
|
||||
|
||||
final priceAPI = PriceAPI(client);
|
||||
|
@ -125,7 +128,8 @@ void main() {
|
|||
'Coin.dogecoinTestNet: [0, 0.0], '
|
||||
'Coin.firoTestNet: [0, 0.0], '
|
||||
'Coin.litecoinTestNet: [0, 0.0], '
|
||||
'Coin.stellarTestnet: [0, 0.0]'
|
||||
'Coin.stellarTestnet: [0, 0.0], '
|
||||
'Coin.xelis: [0.00001234, 5.67]'
|
||||
'}',
|
||||
);
|
||||
verify(client.get(
|
||||
|
@ -134,7 +138,7 @@ void main() {
|
|||
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc"
|
||||
"&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,"
|
||||
"bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar"
|
||||
",tezos"
|
||||
",tezos,xelis"
|
||||
"&order=market_cap_desc&per_page=50&page=1&sparkline=false",
|
||||
),
|
||||
headers: {'Content-Type': 'application/json'})).called(1);
|
||||
|
@ -151,7 +155,7 @@ void main() {
|
|||
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&"
|
||||
"ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,"
|
||||
"bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar"
|
||||
",tezos"
|
||||
",tezos,xelis"
|
||||
"&order=market_cap_desc&per_page=50&page=1&sparkline=false"),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
@ -213,7 +217,10 @@ void main() {
|
|||
'21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_percentag'
|
||||
'e":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-0'
|
||||
'7,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01'
|
||||
'.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]'),
|
||||
'.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"},{"id":'
|
||||
'"xelis","symbol":"xel","name":"Xelis","image":"https://assets.coing'
|
||||
'ecko.com/coins/images/37615/large/green_background_black_logo.png",'
|
||||
'"current_price":0.00001234,"price_change_percentage_24h":5.67}]'),
|
||||
200));
|
||||
|
||||
final priceAPI = PriceAPI(client);
|
||||
|
@ -247,7 +254,8 @@ void main() {
|
|||
'Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], '
|
||||
'Coin.firoTestNet: [0, 0.0], '
|
||||
'Coin.litecoinTestNet: [0, 0.0], '
|
||||
'Coin.stellarTestnet: [0, 0.0]'
|
||||
'Coin.stellarTestnet: [0, 0.0], '
|
||||
'Coin.xelis: [0.00001234, 5.67]'
|
||||
'}',
|
||||
);
|
||||
|
||||
|
@ -258,7 +266,7 @@ void main() {
|
|||
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids"
|
||||
"=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,"
|
||||
"bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar"
|
||||
",tezos"
|
||||
",tezos,xelis"
|
||||
"&order=market_cap_desc&per_page=50&page=1&sparkline=false"),
|
||||
headers: {'Content-Type': 'application/json'})).called(1);
|
||||
|
||||
|
@ -274,7 +282,7 @@ void main() {
|
|||
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc"
|
||||
"&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,"
|
||||
"bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar"
|
||||
",tezos"
|
||||
",tezos,xelis"
|
||||
"&order=market_cap_desc&per_page=50&page=1&sparkline=false"),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
@ -337,7 +345,9 @@ void main() {
|
|||
'y":21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_perce'
|
||||
'ntage":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74'
|
||||
'028e-07,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T'
|
||||
'16:55:01.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]'),
|
||||
'16:55:01.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"'
|
||||
'},{"id":"xelis","symbol":xel,"name":com/coins/images/37615/large/g'
|
||||
'reen_background_black_logo.png,"image":"https://assets.coingecko'),
|
||||
200));
|
||||
|
||||
final priceAPI = PriceAPI(client);
|
||||
|
@ -368,7 +378,8 @@ void main() {
|
|||
'Coin.dogecoinTestNet: [0, 0.0], '
|
||||
'Coin.firoTestNet: [0, 0.0], '
|
||||
'Coin.litecoinTestNet: [0, 0.0], '
|
||||
'Coin.stellarTestnet: [0, 0.0]'
|
||||
'Coin.stellarTestnet: [0, 0.0], '
|
||||
'Coin.xelis: [0, 0.0]'
|
||||
'}',
|
||||
);
|
||||
});
|
||||
|
@ -382,7 +393,7 @@ void main() {
|
|||
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc"
|
||||
"&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,"
|
||||
"bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar"
|
||||
",tezos"
|
||||
",tezos,xelis"
|
||||
"&order=market_cap_desc&per_page=50&page=1&sparkline=false"),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
@ -418,7 +429,8 @@ void main() {
|
|||
'Coin.dogecoinTestNet: [0, 0.0], '
|
||||
'Coin.firoTestNet: [0, 0.0], '
|
||||
'Coin.litecoinTestNet: [0, 0.0], '
|
||||
'Coin.stellarTestnet: [0, 0.0]'
|
||||
'Coin.stellarTestnet: [0, 0.0], '
|
||||
'Coin.xelis: [0, 0.0]'
|
||||
'}',
|
||||
);
|
||||
});
|
||||
|
|
|
@ -24,6 +24,7 @@ list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
|||
flutter_libsparkmobile
|
||||
frostdart
|
||||
tor_ffi_plugin
|
||||
xelis_flutter
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
|
Loading…
Reference in a new issue