implement optional import of xelis ffi package
What changed, and why it matters
This commit refactors how the Stack Wallet app talks to the Xelis cryptocurrency library. Instead of importing the Xelis package directly throughout the app, it introduces a new internal interface layer (`libXelis`) that wraps the Xelis package. The main visible change is making this Xelis support optional at build time, controlled by a code-generation template. There is no direct evidence in the commit that this fixes a security vulnerability; it appears to be an architectural/build change.
Treat this as a routine refactor. If Xelis support is disabled in a build, verify that the generated stub throws clearly and that no runtime code path calls the disabled interface. Review the generated implementation for correct wallet lifecycle management (opening, closing, error handling) and ensure the wallet ID map does not leak instances or allow ID collisions.
Security signals we found
Refactoring of native/FFI wallet integration boundary
Introduction of optional build-time feature flag for Xelis support
Change from direct FFI object references to wallet-ID-indexed map
No explicit security fix, CVE reference, or vulnerability description in commit message or diff
Evidence from the diff
The commit replaces direct imports and calls to xelis_flutter and xelis_dart_sdk with a generated abstraction layer libXelis defined in lib/wl_gen/interfaces/lib_xelis_interface.dart. A template file (tool/wl_templates/XEL_lib_xelis_interface_impl.template.dart) provides the actual implementation when Xelis is enabled, using //ON///OFF preprocessor-style markers. The wallet implementation now passes a walletId to the interface, which maintains a map of x_wallet.XelisWallet instances. The commit also moves Xelis-specific state classes and event wrappers into the interface layer. No security bug or vulnerability is directly addressed in the diff.
Changed components
lib/main.dartlib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dartlib/providers/progress_report/xelis_table_progress_provider.dartlib/wallets/crypto_currency/coins/xelis.dartlib/wallets/wallet/impl/xelis_wallet.dartlib/wallets/wallet/intermediate/lib_xelis_wallet.dartlib/wl_gen/interfaces/lib_xelis_interface.darttool/wl_templates/XEL_lib_xelis_interface_impl.template.dartInspect captured patch +855 / −394
diff --git a/lib/main.dart b/lib/main.dart
index c75941e..481e398 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -25,9 +25,6 @@ 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';
@@ -79,45 +76,18 @@ import 'wallets/isar/providers/all_wallets_info_provider.dart';
import 'wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
import 'widgets/crypto_notifications.dart';
import 'wl_gen/interfaces/cs_monero_interface.dart';
+import 'wl_gen/interfaces/lib_xelis_interface.dart';
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();
+ await libXelis.initRustLib();
WidgetsFlutterBinding.ensureInitialized();
if (Util.isDesktop && args.length == 2 && args.first == "-d") {
@@ -205,8 +175,8 @@ void main(List<String> args) async {
debugConsoleLevel: kDebugMode ? Level.trace : null,
);
- await xelis_api.setUpRustLogger();
- startListeningToRustLogs();
+ await libXelis.setupRustLogger();
+ libXelis.startListeningToRustLogs();
// setup lib spark logging
initSparkLogging(Prefs.instance.logLevel);
diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart
index f1bf78b..50df152 100644
--- a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart
+++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart
@@ -21,7 +21,6 @@ import 'package:flutter/services.dart';
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';
@@ -62,6 +61,7 @@ import '../../../widgets/table_view/table_view.dart';
import '../../../widgets/table_view/table_view_cell.dart';
import '../../../widgets/table_view/table_view_row.dart';
import '../../../wl_gen/interfaces/cs_monero_interface.dart';
+import '../../../wl_gen/interfaces/lib_xelis_interface.dart';
import '../../home_view/home_view.dart';
import '../add_token_view/edit_wallet_tokens_view.dart';
import '../select_wallet_for_token_view.dart';
@@ -101,7 +101,6 @@ 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();
@@ -163,12 +162,6 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
// _focusNodes.add(FocusNode());
}
- if (widget.coin is Xelis) {
- _xelisSeedSearch = x_seed.SearchEngine.init(
- languageIndex: BigInt.from(0),
- );
- }
-
super.initState();
}
@@ -203,7 +196,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
return wowneroWordList.contains(word);
}
if (widget.coin is Xelis) {
- return _xelisSeedSearch!.search(query: word).length > 0;
+ return libXelis.validateSeedWord(word);
}
return _wordListHashSet.contains(word);
}
diff --git a/lib/providers/progress_report/xelis_table_progress_provider.dart b/lib/providers/progress_report/xelis_table_progress_provider.dart
index cbebe82..82a6b35 100644
--- a/lib/providers/progress_report/xelis_table_progress_provider.dart
+++ b/lib/providers/progress_report/xelis_table_progress_provider.dart
@@ -1,8 +1,6 @@
-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;
+import '../../wl_gen/interfaces/lib_xelis_interface.dart';
enum XelisTableGenerationStep {
t1PointsGeneration,
@@ -47,32 +45,6 @@ class XelisTableProgressState {
}
}
-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(),
- );
- });
-});
\ No newline at end of file
+final xelisTableProgressProvider = StreamProvider<XelisTableProgressState>(
+ (ref) => libXelis.createProgressReportStream(),
+);
diff --git a/lib/wallets/crypto_currency/coins/xelis.dart b/lib/wallets/crypto_currency/coins/xelis.dart
index 62b3332..e036c24 100644
--- a/lib/wallets/crypto_currency/coins/xelis.dart
+++ b/lib/wallets/crypto_currency/coins/xelis.dart
@@ -1,9 +1,8 @@
-import 'package:xelis_flutter/src/api/utils.dart' as x_utils;
-
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 '../../../wl_gen/interfaces/lib_xelis_interface.dart';
import '../crypto_currency.dart';
import '../intermediate/electrum_currency.dart';
@@ -94,7 +93,7 @@ class Xelis extends ElectrumCurrency {
@override
bool validateAddress(String address) {
try {
- return x_utils.isAddressValid(strAddress: address);
+ return libXelis.isAddressValid(address: address);
} catch (_) {
return false;
}
diff --git a/lib/wallets/wallet/impl/xelis_wallet.dart b/lib/wallets/wallet/impl/xelis_wallet.dart
index 71f5f38..d36b831 100644
--- a/lib/wallets/wallet/impl/xelis_wallet.dart
+++ b/lib/wallets/wallet/impl/xelis_wallet.dart
@@ -5,8 +5,6 @@ import 'dart:math';
import 'package:isar_community/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';
@@ -20,6 +18,7 @@ import '../../../services/event_bus/global_event_bus.dart';
import '../../../utilities/amount/amount.dart';
import '../../../utilities/logger.dart';
import '../../../utilities/stack_file_system.dart';
+import '../../../wl_gen/interfaces/lib_xelis_interface.dart';
import '../../crypto_currency/crypto_currency.dart';
import '../../models/tx_data.dart';
import '../intermediate/lib_xelis_wallet.dart';
@@ -44,29 +43,28 @@ class XelisWallet extends LibXelisWallet {
key: Wallet.mnemonicPassphraseKey(walletId: info.walletId),
);
- final mnemonic = await getMnemonic();
- final seedLength = mnemonic.trim().split(" ").length;
+ final mnemonic = (await getMnemonic()).trim();
+ final seedLength = mnemonic.split(" ").length;
invalidSeedLengthCheck(seedLength);
Logging.instance.i("Xelis: recovering wallet");
- final wallet = await x_wallet.createXelisWallet(
+ await libXelis.createXelisWallet(
+ walletId,
name: name,
directory: directory,
password: password!,
- seed: mnemonic.trim(),
- network: cryptoCurrency.network.xelisNetwork,
+ seed: mnemonic,
+ network: cryptoCurrency.network,
precomputedTablesPath: tablePath,
l1Low: tableState.currentSize.isLow,
);
await secureStorageInterface.write(
key: Wallet.mnemonicKey(walletId: walletId),
- value: mnemonic.trim(),
+ value: mnemonic,
);
- libXelisWallet = wallet;
-
await _finishInit();
}
@@ -84,23 +82,22 @@ class XelisWallet extends LibXelisWallet {
value: password,
);
- final wallet = await x_wallet.createXelisWallet(
+ await libXelis.createXelisWallet(
+ walletId,
name: name,
directory: directory,
password: password,
- network: cryptoCurrency.network.xelisNetwork,
+ network: cryptoCurrency.network,
precomputedTablesPath: tablePath,
l1Low: tableState.currentSize.isLow,
);
- final mnemonic = await wallet.getSeed();
+ final mnemonic = await libXelis.getSeed(walletId);
await secureStorageInterface.write(
key: Wallet.mnemonicKey(walletId: walletId),
value: mnemonic.trim(),
);
- libXelisWallet = wallet;
-
await _finishInit();
}
@@ -115,11 +112,12 @@ class XelisWallet extends LibXelisWallet {
key: Wallet.mnemonicPassphraseKey(walletId: info.walletId),
);
- libXelisWallet = await x_wallet.openXelisWallet(
+ await libXelis.openXelisWallet(
+ walletId,
name: name,
directory: directory,
password: password!,
- network: cryptoCurrency.network.xelisNetwork,
+ network: cryptoCurrency.network,
precomputedTablesPath: tablePath,
l1Low: tableState.currentSize.isLow,
);
@@ -138,7 +136,7 @@ class XelisWallet extends LibXelisWallet {
walletId: walletId,
derivationIndex: 0,
derivationPath: null,
- value: libXelisWallet!.getAddressStr(),
+ value: libXelis.getAddress(walletId),
publicKey: [],
type: AddressType.xelis,
subType: AddressSubType.receiving,
@@ -170,7 +168,7 @@ class XelisWallet extends LibXelisWallet {
walletId,
);
- if (libXelisWallet == null) {
+ if (!libXelis.walletInstanceExists(walletId)) {
if (isRestore == true) {
await _restoreWallet();
} else {
@@ -226,7 +224,7 @@ class XelisWallet extends LibXelisWallet {
@override
Future<bool> pingCheck() async {
try {
- await libXelisWallet!.getDaemonInfo();
+ await libXelis.getDaemonInfo(walletId);
await handleOnline();
return true;
} catch (_) {
@@ -241,12 +239,12 @@ class XelisWallet extends LibXelisWallet {
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
+ if (await libXelis.hasXelisBalance(walletId)) {
+ final BigInt xelBalance = newBalance != null
+ ? BigInt.from(newBalance)
+ : await libXelis.getXelisBalanceRaw(
+ walletId,
+ ); // in the future, use getAssetBalances and handle each
final balance = Balance(
total: Amount(
rawValue: xelBalance,
@@ -276,9 +274,9 @@ class XelisWallet extends LibXelisWallet {
}
Future<int> _fetchChainHeight() async {
- final infoString = await libXelisWallet!.getDaemonInfo();
- final Map<String, dynamic> nodeInfo =
- (json.decode(infoString) as Map).cast();
+ final infoString = await libXelis.getDaemonInfo(walletId);
+ final Map<String, dynamic> nodeInfo = (json.decode(infoString) as Map)
+ .cast();
pruningHeight =
int.tryParse(nodeInfo['pruned_topoheight']?.toString() ?? '0') ?? 0;
@@ -306,9 +304,9 @@ class XelisWallet extends LibXelisWallet {
@override
Future<void> updateNode() async {
try {
- final bool online = await libXelisWallet!.isOnline();
+ final bool online = await libXelis.isOnline(walletId);
if (online == true) {
- await libXelisWallet!.offlineMode();
+ await libXelis.offlineMode(walletId);
}
await super.connect();
} catch (e, s) {
@@ -324,7 +322,7 @@ class XelisWallet extends LibXelisWallet {
@override
Future<List<String>> updateTransactions({
bool isRescan = false,
- List<xelis_sdk.TransactionEntry>? objTransactions,
+ List<TransactionEntryWrapper>? objTransactions,
int? topoheight,
}) async {
checkInitialized();
@@ -335,7 +333,7 @@ class XelisWallet extends LibXelisWallet {
walletId: walletId,
derivationIndex: 0,
derivationPath: null,
- value: libXelisWallet!.getAddressStr(),
+ value: libXelis.getAddress(walletId),
publicKey: [],
type: AddressType.xelis,
subType: AddressSubType.receiving,
@@ -358,36 +356,20 @@ class XelisWallet extends LibXelisWallet {
firstBlock -= 10;
}
} else {
- await libXelisWallet!.rescan(topoheight: BigInt.from(pruningHeight));
- }
-
- xelis_sdk.TransactionEntry _checkDecodeJsonStringTxEntry(
- String jsonString,
- ) {
- final json = jsonDecode(jsonString);
- if (json is Map) {
- return xelis_sdk.TransactionEntry.fromJson(json.cast());
- }
-
- throw Exception("Not a Map on jsonDecode($jsonString)");
+ await libXelis.rescan(walletId, topoheight: BigInt.from(pruningHeight));
}
- final txList =
- objTransactions ??
- (await libXelisWallet!.allHistory())
- .map(_checkDecodeJsonStringTxEntry)
- .toList();
+ final txList = objTransactions ?? (await libXelis.allHistory(walletId));
final List<TransactionV2> txns = [];
for (final transactionEntry in txList) {
try {
// Check for duplicates
- final storedTx =
- await mainDB.isar.transactionV2s
- .where()
- .txidWalletIdEqualTo(transactionEntry.hash, walletId)
- .findFirst();
+ final storedTx = await mainDB.isar.transactionV2s
+ .where()
+ .txidWalletIdEqualTo(transactionEntry.hash, walletId)
+ .findFirst();
if (storedTx != null &&
storedTx.height != null &&
@@ -406,14 +388,15 @@ class XelisWallet extends LibXelisWallet {
);
final Map<String, dynamic> otherData = {};
- final entryType = transactionEntry.txEntryType;
+ final entryType = transactionEntry.entryType;
- if (entryType is xelis_sdk.CoinbaseEntry) {
+ if (entryType is CoinbaseEntryWrapper) {
final coinbase = entryType;
txType = TransactionType.incoming;
- final int decimals = await libXelisWallet!.getAssetDecimals(
- asset: xelis_sdk.xelisAsset,
+ final int decimals = await libXelis.getAssetDecimals(
+ walletId,
+ asset: libXelis.xelisAsset,
);
fee = Amount(
@@ -430,11 +413,12 @@ class XelisWallet extends LibXelisWallet {
),
);
otherData['overrideFee'] = fee.toJsonString();
- } else if (entryType is xelis_sdk.BurnEntry) {
+ } else if (entryType is BurnEntryWrapper) {
final burn = entryType;
txType = TransactionType.outgoing;
- final int decimals = await libXelisWallet!.getAssetDecimals(
+ final int decimals = await libXelis.getAssetDecimals(
+ walletId,
asset: burn.asset,
);
@@ -468,15 +452,15 @@ class XelisWallet extends LibXelisWallet {
);
otherData['burnAsset'] = burn.asset;
- } else if (entryType is xelis_sdk.IncomingEntry) {
+ } else if (entryType is IncomingEntryWrapper) {
final incoming = entryType;
- txType =
- incoming.from == thisAddress
- ? TransactionType.sentToSelf
- : TransactionType.incoming;
+ txType = incoming.from == thisAddress
+ ? TransactionType.sentToSelf
+ : TransactionType.incoming;
for (final transfer in incoming.transfers) {
- final int decimals = await libXelisWallet!.getAssetDecimals(
+ final int decimals = await libXelis.getAssetDecimals(
+ walletId,
asset: transfer.asset,
);
@@ -496,12 +480,11 @@ class XelisWallet extends LibXelisWallet {
otherData['asset_${transfer.asset}'] = transfer.amount.toString();
if (transfer.extraData != null) {
- otherData['extraData_${transfer.asset}'] =
- transfer.extraData!.toJson();
+ otherData['extraData_${transfer.asset}'] = transfer.extraData!;
}
otherData['overrideFee'] = fee.toJsonString();
}
- } else if (entryType is xelis_sdk.OutgoingEntry) {
+ } else if (entryType is OutgoingEntryWrapper) {
final outgoing = entryType;
txType = TransactionType.outgoing;
nonce = outgoing.nonce;
@@ -551,11 +534,10 @@ class XelisWallet extends LibXelisWallet {
),
);
- otherData['asset_${transfer.asset}_amount'] =
- transfer.amount.toString();
+ otherData['asset_${transfer.asset}_amount'] = transfer.amount
+ .toString();
if (transfer.extraData != null) {
- otherData['extraData_${transfer.asset}'] =
- transfer.extraData!.toJson();
+ otherData['extraData_${transfer.asset}'] = transfer.extraData!;
}
}
} else {
@@ -639,14 +621,13 @@ class XelisWallet extends LibXelisWallet {
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 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;
+ final asset = assetId ?? libXelis.xelisAsset;
// Calculate total send amount
final totalSendAmount = recipients.fold<Amount>(
@@ -658,7 +639,7 @@ class XelisWallet extends LibXelisWallet {
);
// Check balance using raw method
- final xelBalance = await libXelisWallet!.getXelisBalanceRaw();
+ final xelBalance = await libXelis.getXelisBalanceRaw(walletId);
final balance = Amount(
rawValue: xelBalance,
fractionDigits: cryptoCurrency.fractionDigits,
@@ -675,12 +656,14 @@ class XelisWallet extends LibXelisWallet {
// Check if we have enough for both transfers and fee
if (totalSendAmount + boostedFee > balance) {
- final requiredAmt = await libXelisWallet!.formatCoin(
+ final requiredAmt = await libXelis.formatCoin(
+ walletId,
atomicAmount: (totalSendAmount + boostedFee).raw,
assetHash: asset,
);
- final availableAmt = await libXelisWallet!.formatCoin(
+ final availableAmt = await libXelis.formatCoin(
+ walletId,
atomicAmount: xelBalance,
assetHash: asset,
);
@@ -714,37 +697,37 @@ class XelisWallet extends LibXelisWallet {
}) async {
try {
checkInitialized();
- final asset = assetId ?? xelis_sdk.xelisAsset;
+ final asset = assetId ?? libXelis.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
- : [
- TxRecipient(
- address:
- 'xel:xz9574c80c4xegnvurazpmxhw5dlg2n0g9qm60uwgt75uqyx3pcsqzzra9m',
- amount: amount,
- isChange: false,
- addressType: AddressType.xelis,
- ),
- ];
+ final effectiveRecipients = recipients.isNotEmpty
+ ? recipients
+ : [
+ TxRecipient(
+ address:
+ 'xel:xz9574c80c4xegnvurazpmxhw5dlg2n0g9qm60uwgt75uqyx3pcsqzzra9m',
+ amount: amount,
+ isChange: false,
+ addressType: AddressType.xelis,
+ ),
+ ];
try {
final transfers = await Future.wait(
effectiveRecipients.map((recipient) async {
try {
final amt = double.parse(
- await libXelisWallet!.formatCoin(
+ await libXelis.formatCoin(
+ walletId,
atomicAmount: recipient.amount.raw,
assetHash: asset,
),
);
- return x_wallet.Transfer(
+ return WrappedTransfer(
floatAmount: amt,
strAddress: recipient.address,
assetHash: asset,
@@ -760,7 +743,7 @@ class XelisWallet extends LibXelisWallet {
final rawAmount = recipient.amount.raw;
final floatAmount =
rawAmount / BigInt.from(10).pow(defaultDecimals);
- return x_wallet.Transfer(
+ return WrappedTransfer(
floatAmount: floatAmount.toDouble(),
strAddress: recipient.address,
assetHash: asset,
@@ -770,9 +753,12 @@ class XelisWallet extends LibXelisWallet {
}),
);
- final decimals = await libXelisWallet!.getAssetDecimals(asset: asset);
+ final decimals = await libXelis.getAssetDecimals(
+ walletId,
+ asset: asset,
+ );
final estimatedFee = double.parse(
- await libXelisWallet!.estimateFees(transfers: transfers),
+ await libXelis.estimateFees(walletId, transfers: transfers),
);
final rawFee = (estimatedFee * pow(10, decimals)).round();
return Amount(
@@ -817,19 +803,21 @@ class XelisWallet extends LibXelisWallet {
? jsonDecode(txData.otherData!)
: null)?['asset']
as String? ??
- xelis_sdk.xelisAsset;
+ libXelis.xelisAsset;
final amt = double.parse(
- await libXelisWallet!.formatCoin(
+ await libXelis.formatCoin(
+ walletId,
atomicAmount: sendAmount.raw,
assetHash: asset,
),
);
// Create a transfer transaction
- final txJson = await libXelisWallet!.createTransfersTransaction(
+ final txJson = await libXelis.createTransfersTransaction(
+ walletId,
transfers: [
- x_wallet.Transfer(
+ WrappedTransfer(
floatAmount: amt,
strAddress: recipient.address,
assetHash: asset,
@@ -842,7 +830,7 @@ class XelisWallet extends LibXelisWallet {
final txHash = txMap['hash'] as String;
// Broadcast the transaction
- await libXelisWallet!.broadcastTransaction(txHash: txHash);
+ await libXelis.broadcastTransaction(walletId, txHash: txHash);
return await updateSentCachedTxData(
txData: txData.copyWith(txid: txHash),
@@ -862,11 +850,11 @@ class XelisWallet extends LibXelisWallet {
switch (event) {
case NewTopoheight(:final height):
await handleNewTopoHeight(height);
- case NewAsset(:final asset):
- await handleNewAsset(asset);
+ case NewAsset():
+ await handleNewAsset(event);
case NewTransaction(:final transaction):
await handleNewTransaction(transaction);
- case BalanceChanged(:final event):
+ case BalanceChanged():
await handleBalanceChanged(event);
case Rescan(:final startTopoheight):
await handleRescan(startTopoheight);
@@ -892,7 +880,7 @@ class XelisWallet extends LibXelisWallet {
}
@override
- Future<void> handleNewTransaction(xelis_sdk.TransactionEntry tx) async {
+ Future<void> handleNewTransaction(TransactionEntryWrapper tx) async {
try {
final txList = [tx];
final newTxIds = await updateTransactions(
@@ -916,10 +904,10 @@ class XelisWallet extends LibXelisWallet {
}
@override
- Future<void> handleBalanceChanged(xelis_sdk.BalanceChangedEvent event) async {
+ Future<void> handleBalanceChanged(BalanceChanged event) async {
try {
- final asset = event.assetHash;
- if (asset == xelis_sdk.xelisAsset) {
+ final asset = event.asset;
+ if (asset == libXelis.xelisAsset) {
await updateBalance(newBalance: event.balance);
}
@@ -983,7 +971,7 @@ class XelisWallet extends LibXelisWallet {
}
@override
- Future<void> handleNewAsset(xelis_sdk.AssetData asset) async {
+ Future<void> handleNewAsset(NewAsset asset) async {
// TODO: Store asset information if needed
// TODO: Update UI/state for new asset
Logging.instance.d("New xelis asset detected: $asset");
@@ -993,7 +981,7 @@ class XelisWallet extends LibXelisWallet {
Future<void> refresh({int? topoheight}) async {
await refreshMutex.protect(() async {
try {
- final bool online = await libXelisWallet!.isOnline();
+ final bool online = await libXelis.isOnline(walletId);
if (online == true) {
await updateChainHeight(topoheight: topoheight);
await updateBalance();
diff --git a/lib/wallets/wallet/intermediate/lib_xelis_wallet.dart b/lib/wallets/wallet/intermediate/lib_xelis_wallet.dart
index e9604ea..4cec5e1 100644
--- a/lib/wallets/wallet/intermediate/lib_xelis_wallet.dart
+++ b/lib/wallets/wallet/intermediate/lib_xelis_wallet.dart
@@ -1,149 +1,18 @@
import 'dart:async';
-import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:isar_community/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 '../../../wl_gen/interfaces/lib_xelis_interface.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 {
@@ -155,19 +24,10 @@ abstract class LibXelisWallet<T extends ElectrumCurrency>
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) {
+ if (!libXelis.walletInstanceExists(walletId)) {
throw StateError('libXelisWallet not initialized');
}
}
@@ -216,66 +76,15 @@ abstract class LibXelisWallet<T extends ElectrumCurrency>
);
}
- 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> handleNewTransaction(TransactionEntryWrapper tx);
+ Future<void> handleBalanceChanged(BalanceChanged 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 {}
+ Future<void> handleNewAsset(NewAsset asset) async {}
@override
Future<void> refresh({int? topoheight});
@@ -283,10 +92,12 @@ abstract class LibXelisWallet<T extends ElectrumCurrency>
Future<void> connect() async {
final node = getCurrentNode();
try {
- _eventSubscription = convertRawEvents().listen(handleEvent);
+ checkInitialized();
+ _eventSubscription = libXelis.eventsStream(walletId).listen(handleEvent);
Logging.instance.i("Connecting to node: ${node.host}:${node.port}");
- await libXelisWallet!.onlineMode(
+ await libXelis.onlineMode(
+ walletId,
daemonAddress: "${node.host}:${node.port}",
);
await super.refresh();
@@ -356,7 +167,7 @@ abstract class LibXelisWallet<T extends ElectrumCurrency>
await _eventSubscription?.cancel();
_eventSubscription = null;
- await libXelisWallet?.offlineMode();
+ await libXelis.offlineMode(walletId);
await super.exit();
});
} finally {
@@ -413,7 +224,7 @@ extension XelisTableManagement on LibXelisWallet {
Logging.instance.i("Xelis: Generating large tables in background");
final tablePath = await getPrecomputedTablesPath();
- await x_wallet.updateTables(
+ await libXelis.updateTables(
precomputedTablesPath: tablePath,
l1Low: state.desiredSize.isLow,
);
diff --git a/lib/wl_gen/interfaces/lib_xelis_interface.dart b/lib/wl_gen/interfaces/lib_xelis_interface.dart
new file mode 100644
index 0000000..533b90f
--- /dev/null
+++ b/lib/wl_gen/interfaces/lib_xelis_interface.dart
@@ -0,0 +1,317 @@
+import 'package:flutter/foundation.dart';
+
+import '../../providers/progress_report/xelis_table_progress_provider.dart';
+import '../../wallets/crypto_currency/crypto_currency.dart';
+
+export '../generated/lib_xelis_interface_impl.dart';
+
+abstract class LibXelisInterface {
+ String get xelisAsset;
+
+ bool walletInstanceExists(String walletId);
+
+ Future<void> initRustLib();
+
+ Future<void> setupRustLogger();
+
+ void startListeningToRustLogs();
+
+ Stream<XelisTableProgressState> createProgressReportStream();
+
+ bool isAddressValid({required String address});
+
+ bool validateSeedWord(String word);
+
+ Stream<Event> eventsStream(String walletId);
+
+ Future<void> onlineMode(String walletId, {required String daemonAddress});
+ Future<void> offlineMode(String walletId);
+
+ Future<void> updateTables({
+ required String precomputedTablesPath,
+ required bool l1Low,
+ });
+
+ Future<String> getSeed(String walletId);
+
+ Future<void> createXelisWallet(
+ String walletId, {
+ required String name,
+ required String directory,
+ required String password,
+ required CryptoCurrencyNetwork network,
+ String? seed,
+ String? privateKey,
+ String? precomputedTablesPath,
+ bool? l1Low,
+ });
+
+ Future<void> openXelisWallet(
+ String walletId, {
+ required String name,
+ required String directory,
+ required String password,
+ required CryptoCurrencyNetwork network,
+ String? precomputedTablesPath,
+ bool? l1Low,
+ });
+
+ String getAddress(String walletId);
+
+ Future<String> getDaemonInfo(String walletId);
+
+ Future<bool> isOnline(String walletId);
+
+ Future<void> rescan(String walletId, {required BigInt topoheight});
+
+ Future<List<TransactionEntryWrapper>> allHistory(String walletId);
+
+ Future<void> broadcastTransaction(String walletId, {required String txHash});
+
+ Future<String> estimateFees(
+ String walletId, {
+ required List<WrappedTransfer> transfers,
+ });
+
+ Future<String> createTransfersTransaction(
+ String walletId, {
+ required List<WrappedTransfer> transfers,
+ });
+
+ Future<String> formatCoin(
+ String walletId, {
+ required BigInt atomicAmount,
+ String? assetHash,
+ });
+
+ Future<int> getAssetDecimals(String walletId, {required String asset});
+
+ Future<BigInt> getXelisBalanceRaw(String walletId);
+
+ Future<bool> hasXelisBalance(String walletId);
+}
+
+// =============================================================================
+// ============== stupid =======================================================
+class WrappedTransfer {
+ final double floatAmount;
+ final String strAddress;
+ final String assetHash;
+ final String? extraData;
+
+ const WrappedTransfer({
+ required this.floatAmount,
+ required this.strAddress,
+ required this.assetHash,
+ this.extraData,
+ });
+
+ @override
+ int get hashCode =>
+ floatAmount.hashCode ^
+ strAddress.hashCode ^
+ assetHash.hashCode ^
+ extraData.hashCode;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is WrappedTransfer &&
+ runtimeType == other.runtimeType &&
+ floatAmount == other.floatAmount &&
+ strAddress == other.strAddress &&
+ assetHash == other.assetHash &&
+ extraData == other.extraData;
+}
+
+class TransactionEntryWrapper {
+ final Object _value;
+
+ final EntryWrapper entryType;
+
+ final String hash;
+ final DateTime? timestamp;
+ final int topoheight;
+
+ TransactionEntryWrapper(
+ this._value, {
+ required this.entryType,
+ required this.hash,
+ required this.timestamp,
+ required this.topoheight,
+ });
+
+ T getValue<T>() => _value is T
+ ? _value as T
+ : throw Exception(
+ "Type mismatch: ${_value.runtimeType} is not ${T.runtimeType}",
+ );
+}
+
+sealed class EntryWrapper {
+ const EntryWrapper();
+}
+
+class CoinbaseEntryWrapper extends EntryWrapper {
+ final int reward;
+ const CoinbaseEntryWrapper({required this.reward});
+}
+
+class BurnEntryWrapper extends EntryWrapper {
+ final int amount;
+ final int fee;
+ final String asset;
+
+ const BurnEntryWrapper({
+ required this.amount,
+ required this.fee,
+ required this.asset,
+ });
+}
+
+class IncomingEntryWrapper extends EntryWrapper {
+ final String from;
+ final List<({int amount, String asset, Map<String, dynamic>? extraData})>
+ transfers;
+
+ const IncomingEntryWrapper({required this.from, required this.transfers});
+}
+
+class OutgoingEntryWrapper extends EntryWrapper {
+ final int nonce;
+ final int fee;
+ final List<
+ ({
+ String destination,
+ int amount,
+ String asset,
+ Map<String, dynamic>? extraData,
+ })
+ >
+ transfers;
+
+ const OutgoingEntryWrapper({
+ required this.nonce,
+ required this.fee,
+ required this.transfers,
+ });
+}
+
+class UnknownEntryWrapper extends EntryWrapper {}
+
+// =============================================================================
+
+// =============================================================================
+// ============== moved from lib_xelis_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,
+ };
+}
+
+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;
+ final String name;
+ final int decimals;
+ final int? maxSupply;
+
+ NewAsset(this.name, this.decimals, this.maxSupply);
+}
+
+final class NewTransaction extends Event {
+ // final xelis_sdk.TransactionEntry transaction;
+ final TransactionEntryWrapper transaction;
+ const NewTransaction(this.transaction);
+}
+
+final class BalanceChanged extends Event {
+ // final xelis_sdk.BalanceChangedEvent event;
+ final String asset;
+ final int balance;
+
+ const BalanceChanged(this.asset, this.balance);
+}
+
+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);
+}
+
+// =============================================================================
diff --git a/tool/wl_templates/XEL_lib_xelis_interface_impl.template.dart b/tool/wl_templates/XEL_lib_xelis_interface_impl.template.dart
new file mode 100644
index 0000000..08f1f9b
--- /dev/null
+++ b/tool/wl_templates/XEL_lib_xelis_interface_impl.template.dart
@@ -0,0 +1,411 @@
+//ON
+import 'dart:convert';
+
+import 'package:logger/logger.dart';
+import 'package:xelis_dart_sdk/xelis_dart_sdk.dart' as xelis_sdk;
+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/api/network.dart' as x_network;
+import 'package:xelis_flutter/src/api/seed_search_engine.dart' as x_seed;
+import 'package:xelis_flutter/src/api/utils.dart' as x_utils;
+import 'package:xelis_flutter/src/api/wallet.dart' as x_wallet;
+import 'package:xelis_flutter/src/frb_generated.dart' as xelis_rust;
+
+import '../../providers/progress_report/xelis_table_progress_provider.dart';
+import '../../utilities/logger.dart';
+import '../../wallets/crypto_currency/crypto_currency.dart';
+//END_ON
+import '../interfaces/lib_xelis_interface.dart';
+
+LibXelisInterface get libXelis => _getInterface();
+
+//OFF
+LibXelisInterface _getInterface() => throw Exception("XEL not enabled!");
+
+//END_OFF
+//ON
+LibXelisInterface _getInterface() => _MwebdServerInterfaceImpl();
+
+final class _MwebdServerInterfaceImpl extends LibXelisInterface {
+ final Map<String, x_wallet.XelisWallet> _wallets = {};
+
+ @override
+ String get xelisAsset => xelis_sdk.xelisAsset;
+
+ @override
+ bool walletInstanceExists(String walletId) => _wallets[walletId] != null;
+
+ @override
+ Future<void> initRustLib() => xelis_rust.RustLib.init();
+
+ @override
+ Future<void> setupRustLogger() => xelis_api.setUpRustLogger();
+
+ @override
+ 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");
+ },
+ );
+
+ @override
+ Stream<XelisTableProgressState> createProgressReportStream() {
+ double lastPrintedProgress = 0.0;
+ return xelis_api.createProgressReportStream().map((report) {
+ return report.when(
+ tableGeneration: (progress, step, _) {
+ final currentStep = XelisTableGenerationStep.fromString(step);
+ if ((progress - lastPrintedProgress).abs() >= 0.05 ||
+ currentStep != XelisTableGenerationStep.fromString(step) ||
+ progress >= 0.99) {
+ Logging.instance.d(
+ "Xelis Table Generation: $step - ${progress * 100.0}%",
+ );
+ lastPrintedProgress = progress;
+ }
+
+ return XelisTableProgressState(
+ tableProgress: progress,
+ currentStep: currentStep,
+ );
+ },
+ misc: (_) => const XelisTableProgressState(),
+ );
+ });
+ }
+
+ @override
+ bool isAddressValid({required String address}) =>
+ x_utils.isAddressValid(strAddress: address);
+
+ x_seed.SearchEngine? _xelisSeedSearch;
+ @override
+ bool validateSeedWord(String word) {
+ _xelisSeedSearch ??= x_seed.SearchEngine.init(
+ languageIndex: BigInt.from(0),
+ );
+
+ return _xelisSeedSearch!.search(query: word).isNotEmpty;
+ }
+
+ @override
+ Stream<Event> eventsStream(String walletId) async* {
+ final rawEventStream = _wallets[walletId]!.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:
+ final data = xelis_sdk.AssetData.fromJson(
+ json['data'] as Map<String, dynamic>,
+ );
+
+ yield NewAsset(data.name, data.decimals, data.maxSupply);
+ case xelis_sdk.WalletEvent.newTransaction:
+ final tx = xelis_sdk.TransactionEntry.fromJson(
+ json['data'] as Map<String, dynamic>,
+ );
+ yield NewTransaction(
+ TransactionEntryWrapper(
+ tx,
+ entryType: _entryTypeConversion(tx.txEntryType),
+ hash: tx.hash,
+ timestamp: tx.timestamp,
+ topoheight: tx.topoheight,
+ ),
+ );
+ case xelis_sdk.WalletEvent.balanceChanged:
+ final data = xelis_sdk.BalanceChangedEvent.fromJson(
+ json['data'] as Map<String, dynamic>,
+ );
+ yield BalanceChanged(data.assetHash, data.balance);
+ 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;
+ }
+ }
+ }
+
+ @override
+ Future<void> onlineMode(String walletId, {required String daemonAddress}) =>
+ _wallets[walletId]!.onlineMode(daemonAddress: daemonAddress);
+
+ @override
+ Future<void> offlineMode(String walletId) =>
+ _wallets[walletId]?.offlineMode() ?? Future.value();
+
+ @override
+ Future<void> updateTables({
+ required String precomputedTablesPath,
+ required bool l1Low,
+ }) => x_wallet.updateTables(
+ precomputedTablesPath: precomputedTablesPath,
+ l1Low: l1Low,
+ );
+
+ @override
+ Future<String> getSeed(String walletId) => _wallets[walletId]!.getSeed();
+
+ @override
+ Future<void> createXelisWallet(
+ String walletId, {
+ required String name,
+ required String directory,
+ required String password,
+ required CryptoCurrencyNetwork network,
+ String? seed,
+ String? privateKey,
+ String? precomputedTablesPath,
+ bool? l1Low,
+ }) async {
+ if (walletInstanceExists(walletId)) {
+ throw Exception(
+ "Attempted overwrite of existing wallet in cache on create",
+ );
+ }
+
+ _wallets[walletId] = await x_wallet.createXelisWallet(
+ name: name,
+ directory: directory,
+ password: password,
+ privateKey: privateKey,
+ seed: seed,
+ network: network.xelisNetwork,
+ precomputedTablesPath: precomputedTablesPath,
+ l1Low: l1Low,
+ );
+ }
+
+ @override
+ Future<void> openXelisWallet(
+ String walletId, {
+ required String name,
+ required String directory,
+ required String password,
+ required CryptoCurrencyNetwork network,
+ String? precomputedTablesPath,
+ bool? l1Low,
+ }) async {
+ if (walletInstanceExists(walletId)) {
+ throw Exception(
+ "Attempted overwrite of existing wallet in cache on open",
+ );
+ }
+
+ _wallets[walletId] = await x_wallet.openXelisWallet(
+ name: name,
+ directory: directory,
+ password: password,
+ network: network.xelisNetwork,
+ precomputedTablesPath: precomputedTablesPath,
+ l1Low: l1Low,
+ );
+ }
+
+ @override
+ String getAddress(String walletId) => _wallets[walletId]!.getAddressStr();
+
+ @override
+ Future<String> getDaemonInfo(String walletId) =>
+ _wallets[walletId]!.getDaemonInfo();
+
+ @override
+ Future<bool> isOnline(String walletId) => _wallets[walletId]!.isOnline();
+
+ @override
+ Future<void> rescan(String walletId, {required BigInt topoheight}) =>
+ _wallets[walletId]!.rescan(topoheight: topoheight);
+
+ @override
+ Future<List<TransactionEntryWrapper>> allHistory(String walletId) async =>
+ (await _wallets[walletId]!.allHistory()).map((e) {
+ final tx = _checkDecodeJsonStringTxEntry(e);
+ return TransactionEntryWrapper(
+ tx,
+ entryType: _entryTypeConversion(tx.txEntryType),
+ hash: tx.hash,
+ timestamp: tx.timestamp,
+ topoheight: tx.topoheight,
+ );
+ }).toList();
+
+ @override
+ Future<void> broadcastTransaction(
+ String walletId, {
+ required String txHash,
+ }) => _wallets[walletId]!.broadcastTransaction(txHash: txHash);
+
+ @override
+ Future<String> createTransfersTransaction(
+ String walletId, {
+ required List<WrappedTransfer> transfers,
+ }) => _wallets[walletId]!.createTransfersTransaction(
+ transfers: transfers
+ .map(
+ (e) => x_wallet.Transfer(
+ floatAmount: e.floatAmount,
+ strAddress: e.strAddress,
+ assetHash: e.assetHash,
+ extraData: e.extraData,
+ ),
+ )
+ .toList(),
+ );
+
+ @override
+ Future<String> estimateFees(
+ String walletId, {
+ required List<WrappedTransfer> transfers,
+ }) => _wallets[walletId]!.estimateFees(
+ transfers: transfers
+ .map(
+ (e) => x_wallet.Transfer(
+ floatAmount: e.floatAmount,
+ strAddress: e.strAddress,
+ assetHash: e.assetHash,
+ extraData: e.extraData,
+ ),
+ )
+ .toList(),
+ );
+
+ @override
+ Future<String> formatCoin(
+ String walletId, {
+ required BigInt atomicAmount,
+ String? assetHash,
+ }) => _wallets[walletId]!.formatCoin(
+ atomicAmount: atomicAmount,
+ assetHash: assetHash,
+ );
+
+ @override
+ Future<int> getAssetDecimals(String walletId, {required String asset}) =>
+ _wallets[walletId]!.getAssetDecimals(asset: asset);
+
+ @override
+ Future<BigInt> getXelisBalanceRaw(String walletId) =>
+ _wallets[walletId]!.getXelisBalanceRaw();
+
+ @override
+ Future<bool> hasXelisBalance(String walletId) =>
+ _wallets[walletId]!.hasXelisBalance();
+}
+
+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');
+ }
+ }
+}
+
+EntryWrapper _entryTypeConversion(xelis_sdk.TransactionEntryType entryType) {
+ if (entryType is xelis_sdk.CoinbaseEntry) {
+ return CoinbaseEntryWrapper(reward: entryType.reward);
+ } else if (entryType is xelis_sdk.BurnEntry) {
+ return BurnEntryWrapper(
+ amount: entryType.amount,
+ fee: entryType.fee,
+ asset: entryType.asset,
+ );
+ } else if (entryType is xelis_sdk.IncomingEntry) {
+ return IncomingEntryWrapper(
+ from: entryType.from,
+ transfers: entryType.transfers
+ .map(
+ (e) => (
+ amount: e.amount,
+ asset: e.asset,
+ extraData: e.extraData?.toJson(),
+ ),
+ )
+ .toList(),
+ );
+ } else if (entryType is xelis_sdk.OutgoingEntry) {
+ return OutgoingEntryWrapper(
+ nonce: entryType.nonce,
+ fee: entryType.fee,
+ transfers: entryType.transfers
+ .map(
+ (e) => (
+ destination: e.destination,
+ amount: e.amount,
+ asset: e.asset,
+ extraData: e.extraData?.toJson(),
+ ),
+ )
+ .toList(),
+ );
+ } else {
+ return UnknownEntryWrapper();
+ }
+}
+
+xelis_sdk.TransactionEntry _checkDecodeJsonStringTxEntry(String jsonString) {
+ final json = jsonDecode(jsonString);
+ if (json is Map) {
+ return xelis_sdk.TransactionEntry.fromJson(json.cast());
+ }
+
+ throw Exception("Not a Map on jsonDecode($jsonString)");
+}
+
+//END_ON
Why this scored 28/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.