separated xmw and wow libs. Dirty dirty minimal get-it-running mess
What changed, and why it matters
This commit is a large refactor that splits the Wownero (WOW) cryptocurrency support out of the shared Monero (XMR) library path and into its own dedicated library (cs_wownero). It updates wallet creation, address validation, fee estimation, and restore logic to use the new Wownero-specific code instead of the old combined Monero/Wownero code. The commit message calls it a 'dirty dirty minimal get-it-running mess,' suggesting it is a rough, temporary implementation rather than a polished security fix.
Treat this as a routine refactor with elevated review priority due to its size and the author's self-described 'mess' status. Reviewers should verify that Wownero wallet operations (create, restore, send, fee estimation, address validation, and sync) still behave correctly and that no Monero-specific logic accidentally remains active for Wownero. No immediate security patch is required, but regression testing is strongly advised before release.
Security signals we found
Large refactor separating shared XMR/WOW library paths
New dedicated cs_wownero dependency and interface introduced
CsCoin enum removed, reducing runtime coin-selection ambiguity
Commit message describes change as 'dirty dirty minimal get-it-running mess'
No explicit vulnerability, CVE, or security advisory referenced in commit
Evidence from the diff
The change separates the previously shared cs_monero interface into distinct cs_monero and cs_wownero interfaces. It adds a new LibWowneroWallet intermediate class, updates WowneroWallet to extend it, and removes the CsCoin enum that selected between Monero and Wownero at runtime. Pubspec dependencies are bumped to cs_monero 2.0.0 and new cs_wownero 2.0.0 packages. The diff is mostly mechanical: replacing csMonero calls with csWownero calls for Wownero-specific operations, and removing the csCoin parameter from the Monero interface. There is no explicit security bug being patched; it is an architectural/library separation.
Changed components
lib/main.dartlib/wallets/wallet/impl/wownero_wallet.dartlib/wallets/wallet/intermediate/lib_wownero_wallet.dartlib/wl_gen/interfaces/cs_monero_interface.dartlib/wl_gen/interfaces/cs_wownero_interface.dartpubspec.lockscripts/app_config/templates/pubspec.template.yamltool/wl_templates/WOW_cs_wownero_interface_impl.template.darttool/wl_templates/XMR_cs_monero_interface_impl.template.dartInspect captured patch +2446 / −190
diff --git a/lib/main.dart b/lib/main.dart
index 7bfba70..7b4d40f 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -76,6 +76,7 @@ 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/cs_wownero_interface.dart';
import 'wl_gen/interfaces/lib_xelis_interface.dart';
final openedFromSWBFileStringStateProvider = StateProvider<String?>(
@@ -161,10 +162,12 @@ void main(List<String> args) async {
DB.instance.hive.registerAdapter(lib_monero_compat.WalletTypeAdapter());
- if (AppConfig.coins.whereType<Monero>().isNotEmpty ||
- AppConfig.coins.whereType<Wownero>().isNotEmpty) {
+ if (AppConfig.coins.whereType<Monero>().isNotEmpty) {
csMonero.setUseCsMoneroLoggerInternal(kDebugMode);
}
+ if (AppConfig.coins.whereType<Wownero>().isNotEmpty) {
+ csWownero.setUseCsWowneroLoggerInternal(kDebugMode);
+ }
DB.instance.hive.init(
(await StackFileSystem.applicationHiveDirectory()).path,
diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart
index 1686eba..c44c5f2 100644
--- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart
+++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart
@@ -42,6 +42,7 @@ import '../../../../widgets/textfield_icon_button.dart';
import '../../../../widgets/toggle.dart';
import '../../../../wl_gen/interfaces/cs_monero_interface.dart';
import '../../../../wl_gen/interfaces/cs_salvium_interface.dart';
+import '../../../../wl_gen/interfaces/cs_wownero_interface.dart';
import '../../create_or_restore_wallet_view/sub_widgets/coin_image.dart';
import '../restore_view_only_wallet_view.dart';
import '../restore_wallet_view.dart';
@@ -213,10 +214,10 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
int height = 0;
if (date != null) {
if (widget.coin is Monero) {
- height = csMonero.getHeightByDate(date, csCoin: CsCoin.monero);
+ height = csMonero.getHeightByDate(date);
}
if (widget.coin is Wownero) {
- height = csMonero.getHeightByDate(date, csCoin: CsCoin.wownero);
+ height = csWownero.getHeightByDate(date);
}
if (widget.coin is Salvium) {
height = csSalvium.getHeightByDate(
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 50df152..e87305f 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
@@ -61,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/cs_wownero_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';
@@ -189,7 +190,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
}
}
if (widget.coin is Wownero) {
- final wowneroWordList = csMonero.getWowneroWordList(
+ final wowneroWordList = csWownero.getWowneroWordList(
"English",
widget.seedWordsLength,
);
diff --git a/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart b/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart
index 9494b66..b5063ae 100644
--- a/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart
+++ b/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart
@@ -48,6 +48,7 @@ import '../../../widgets/desktop/desktop_scaffold.dart';
import '../../../widgets/stack_dialog.dart';
import '../../../wl_gen/interfaces/cs_monero_interface.dart';
import '../../../wl_gen/interfaces/cs_salvium_interface.dart';
+import '../../../wl_gen/interfaces/cs_wownero_interface.dart';
import '../../home_view/home_view.dart';
import '../add_token_view/edit_wallet_tokens_view.dart';
import '../new_wallet_options/new_wallet_options_view.dart';
@@ -116,13 +117,11 @@ class _VerifyRecoveryPhraseViewState
if (widget.wallet.cryptoCurrency is Monero) {
height = csMonero.getHeightByDate(
DateTime.now().subtract(const Duration(days: 7)),
- csCoin: CsCoin.monero,
);
}
if (widget.wallet.cryptoCurrency is Wownero) {
- height = csMonero.getHeightByDate(
+ height = csWownero.getHeightByDate(
DateTime.now().subtract(const Duration(days: 7)),
- csCoin: CsCoin.wownero,
);
}
if (widget.wallet.cryptoCurrency is Salvium) {
diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart
index e7fcea3..e56f12c 100644
--- a/lib/pages/send_view/send_view.dart
+++ b/lib/pages/send_view/send_view.dart
@@ -75,6 +75,7 @@ import '../../widgets/stack_dialog.dart';
import '../../widgets/stack_text_field.dart';
import '../../widgets/textfield_icon_button.dart';
import '../../wl_gen/interfaces/cs_monero_interface.dart';
+import '../../wl_gen/interfaces/cs_wownero_interface.dart';
import '../address_book_views/address_book_view.dart';
import '../coin_control/coin_control_view.dart';
import 'confirm_transaction_view.dart';
@@ -575,6 +576,28 @@ class _SendViewState extends ConsumerState<SendView> {
throw ArgumentError("custom fee not available for monero");
}
+ fee = await wallet.estimateFeeFor(amount, BigInt.from(specialMoneroId));
+ cachedFees[amount] = ref
+ .read(pAmountFormatter(coin))
+ .format(fee, withUnitName: true, indicatePrecisionLoss: false);
+
+ return cachedFees[amount]!;
+ } else if (coin is Wownero) {
+ final int specialMoneroId;
+ switch (ref.read(feeRateTypeMobileStateProvider.state).state) {
+ case FeeRateType.fast:
+ specialMoneroId = csWownero.getTxPriorityHigh();
+ break;
+ case FeeRateType.average:
+ specialMoneroId = csWownero.getTxPriorityMedium();
+ break;
+ case FeeRateType.slow:
+ specialMoneroId = csWownero.getTxPriorityNormal();
+ break;
+ default:
+ throw ArgumentError("custom fee not available for monero");
+ }
+
fee = await wallet.estimateFeeFor(amount, BigInt.from(specialMoneroId));
cachedFees[amount] = ref
.read(pAmountFormatter(coin))
diff --git a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart
index 8c05fa9..c7112a5 100644
--- a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart
+++ b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart
@@ -29,6 +29,7 @@ import '../../../wallets/wallet/impl/firo_wallet.dart';
import '../../../wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart';
import '../../../widgets/animated_text.dart';
import '../../../wl_gen/interfaces/cs_monero_interface.dart';
+import '../../../wl_gen/interfaces/cs_wownero_interface.dart';
final feeSheetSessionCacheProvider =
ChangeNotifierProvider<FeeSheetSessionCache>((ref) {
@@ -88,12 +89,18 @@ class _TransactionFeeSelectionSheetState
if (widget.isToken == false) {
final wallet = ref.read(pWallets).getWallet(walletId);
- if (coin is Monero || coin is Wownero) {
+ if (coin is Monero) {
final fee = await wallet.estimateFeeFor(
amount,
BigInt.from(csMonero.getTxPriorityHigh()),
);
ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
+ } else if (coin is Wownero) {
+ final fee = await wallet.estimateFeeFor(
+ amount,
+ BigInt.from(csWownero.getTxPriorityHigh()),
+ );
+ ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
} else if (coin is Firo) {
final Amount fee;
switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
@@ -125,12 +132,18 @@ class _TransactionFeeSelectionSheetState
if (ref.read(feeSheetSessionCacheProvider).average[amount] == null) {
if (widget.isToken == false) {
final wallet = ref.read(pWallets).getWallet(walletId);
- if (coin is Monero || coin is Wownero) {
+ if (coin is Monero) {
final fee = await wallet.estimateFeeFor(
amount,
BigInt.from(csMonero.getTxPriorityMedium()),
);
ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
+ } else if (coin is Wownero) {
+ final fee = await wallet.estimateFeeFor(
+ amount,
+ BigInt.from(csWownero.getTxPriorityMedium()),
+ );
+ ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
} else if (coin is Firo) {
final Amount fee;
switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
@@ -161,12 +174,18 @@ class _TransactionFeeSelectionSheetState
if (ref.read(feeSheetSessionCacheProvider).slow[amount] == null) {
if (widget.isToken == false) {
final wallet = ref.read(pWallets).getWallet(walletId);
- if (coin is Monero || coin is Wownero) {
+ if (coin is Monero) {
final fee = await wallet.estimateFeeFor(
amount,
BigInt.from(csMonero.getTxPriorityNormal()),
);
ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
+ } else if (coin is Wownero) {
+ final fee = await wallet.estimateFeeFor(
+ amount,
+ BigInt.from(csWownero.getTxPriorityNormal()),
+ );
+ ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
} else if (coin is Firo) {
final Amount fee;
switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart
index f6f6b15..bfa5d0a 100644
--- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart
+++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart
@@ -12,6 +12,7 @@ import '../../../../utilities/text_styles.dart';
import '../../../../utilities/util.dart';
import '../../../../wallets/isar/providers/wallet_info_provider.dart';
import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
+import '../../../../wallets/wallet/intermediate/lib_wownero_wallet.dart';
import '../../../../widgets/background.dart';
import '../../../../widgets/conditional_parent.dart';
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
@@ -22,6 +23,7 @@ import '../../../../widgets/icon_widgets/x_icon.dart';
import '../../../../widgets/stack_text_field.dart';
import '../../../../widgets/textfield_icon_button.dart';
import '../../../../wl_gen/interfaces/cs_monero_interface.dart';
+import '../../../../wl_gen/interfaces/cs_wownero_interface.dart';
class EditRefreshHeightView extends ConsumerStatefulWidget {
const EditRefreshHeightView({super.key, required this.walletId});
@@ -55,10 +57,11 @@ class _EditRefreshHeightViewState extends ConsumerState<EditRefreshHeightView> {
newRestoreHeight: newHeight,
isar: ref.read(mainDBProvider).isar,
);
- final wallet =
- ref.read(pWallets).getWallet(widget.walletId) as LibMoneroWallet?;
- if (wallet?.wallet != null) {
- csMonero.setRefreshFromBlockHeight(wallet!.wallet!, newHeight);
+ final wallet = ref.read(pWallets).getWallet(widget.walletId);
+ if (wallet is LibMoneroWallet && wallet.wallet != null) {
+ csMonero.setRefreshFromBlockHeight(wallet.wallet!, newHeight);
+ } else if (wallet is LibWowneroWallet && wallet.wallet != null) {
+ csWownero.setRefreshFromBlockHeight(wallet.wallet!, newHeight);
}
} else {
errMessage = "Invalid height: ${_controller.text}";
@@ -96,11 +99,14 @@ class _EditRefreshHeightViewState extends ConsumerState<EditRefreshHeightView> {
void initState() {
super.initState();
_controller = TextEditingController();
- final wallet =
- ref.read(pWallets).getWallet(widget.walletId) as LibMoneroWallet?;
- if (wallet?.wallet != null) {
+ final wallet = ref.read(pWallets).getWallet(widget.walletId);
+ if (wallet is LibMoneroWallet && wallet.wallet != null) {
_controller.text = csMonero
- .getRefreshFromBlockHeight(wallet!.wallet!)
+ .getRefreshFromBlockHeight(wallet.wallet!)
+ .toString();
+ } else if (wallet is LibWowneroWallet && wallet.wallet != null) {
+ _controller.text = csWownero
+ .getRefreshFromBlockHeight(wallet.wallet!)
.toString();
} else {
_controller.text = ref
diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send_fee_form.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send_fee_form.dart
index 462262d..7215e6e 100644
--- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send_fee_form.dart
+++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send_fee_form.dart
@@ -22,6 +22,7 @@ import '../../../../widgets/desktop/desktop_fee_dialog.dart';
import '../../../../widgets/eth_fee_form.dart';
import '../../../../widgets/fee_slider.dart';
import '../../../../wl_gen/interfaces/cs_monero_interface.dart';
+import '../../../../wl_gen/interfaces/cs_wownero_interface.dart';
class DesktopSendFeeForm extends ConsumerStatefulWidget {
const DesktopSendFeeForm({
@@ -171,7 +172,9 @@ class _DesktopSendFeeFormState extends ConsumerState<DesktopSendFeeForm> {
final fee = await wallet.estimateFeeFor(
amount,
BigInt.from(
- csMonero.getTxPriorityMedium(),
+ coin is Monero
+ ? csMonero.getTxPriorityMedium()
+ : csWownero.getTxPriorityMedium(),
),
);
ref
diff --git a/lib/wallets/crypto_currency/coins/monero.dart b/lib/wallets/crypto_currency/coins/monero.dart
index 3a983cf..379c47d 100644
--- a/lib/wallets/crypto_currency/coins/monero.dart
+++ b/lib/wallets/crypto_currency/coins/monero.dart
@@ -52,7 +52,7 @@ class Monero extends CryptonoteCurrency {
}
switch (network) {
case CryptoCurrencyNetwork.main:
- return csMonero.validateAddress(address, 0, csCoin: CsCoin.monero);
+ return csMonero.validateAddress(address, 0);
default:
throw Exception("Unsupported network: $network");
}
diff --git a/lib/wallets/crypto_currency/coins/wownero.dart b/lib/wallets/crypto_currency/coins/wownero.dart
index 66d27c3..7d0fec4 100644
--- a/lib/wallets/crypto_currency/coins/wownero.dart
+++ b/lib/wallets/crypto_currency/coins/wownero.dart
@@ -1,7 +1,7 @@
import '../../../models/node_model.dart';
import '../../../utilities/default_nodes.dart';
import '../../../utilities/enums/derive_path_type_enum.dart';
-import '../../../wl_gen/interfaces/cs_monero_interface.dart';
+import '../../../wl_gen/interfaces/cs_wownero_interface.dart';
import '../crypto_currency.dart';
import '../intermediate/cryptonote_currency.dart';
@@ -52,7 +52,7 @@ class Wownero extends CryptonoteCurrency {
}
switch (network) {
case CryptoCurrencyNetwork.main:
- return csMonero.validateAddress(address, 0, csCoin: CsCoin.wownero);
+ return csWownero.validateAddress(address, 0);
default:
throw Exception("Unsupported network: $network");
}
diff --git a/lib/wallets/wallet/impl/monero_wallet.dart b/lib/wallets/wallet/impl/monero_wallet.dart
index 876bec9..935d5ad 100644
--- a/lib/wallets/wallet/impl/monero_wallet.dart
+++ b/lib/wallets/wallet/impl/monero_wallet.dart
@@ -35,19 +35,13 @@ class MoneroWallet extends LibMoneroWallet {
}
@override
- bool walletExists(String path) =>
- csMonero.walletExists(path, csCoin: CsCoin.monero);
+ bool walletExists(String path) => csMonero.walletExists(path);
@override
Future<WrappedWallet> loadWallet({
required String path,
required String password,
- }) => csMonero.loadWallet(
- walletId,
- path: path,
- password: password,
- csCoin: CsCoin.monero,
- );
+ }) => csMonero.loadWallet(walletId, path: path, password: password);
@override
Future<WrappedWallet> getCreatedWallet({
@@ -56,7 +50,6 @@ class MoneroWallet extends LibMoneroWallet {
required int wordCount,
required String seedOffset,
}) => csMonero.getCreatedWallet(
- csCoin: CsCoin.monero,
path: path,
password: password,
wordCount: wordCount,
@@ -76,7 +69,6 @@ class MoneroWallet extends LibMoneroWallet {
mnemonic: mnemonic,
height: height,
seedOffset: seedOffset,
- csCoin: CsCoin.monero,
walletId: walletId,
);
@@ -89,7 +81,6 @@ class MoneroWallet extends LibMoneroWallet {
int height = 0,
}) => csMonero.getRestoredFromViewKeyWallet(
walletId: walletId,
- csCoin: CsCoin.monero,
path: path,
password: password,
address: address,
diff --git a/lib/wallets/wallet/impl/wownero_wallet.dart b/lib/wallets/wallet/impl/wownero_wallet.dart
index 691f601..86f0183 100644
--- a/lib/wallets/wallet/impl/wownero_wallet.dart
+++ b/lib/wallets/wallet/impl/wownero_wallet.dart
@@ -5,14 +5,14 @@ import 'package:compat/compat.dart' as lib_monero_compat;
import '../../../models/isar/models/blockchain_data/address.dart';
import '../../../utilities/amount/amount.dart';
import '../../../utilities/enums/fee_rate_type_enum.dart';
-import '../../../wl_gen/interfaces/cs_monero_interface.dart';
import '../../../wl_gen/interfaces/cs_salvium_interface.dart'
show WrappedWallet;
+import '../../../wl_gen/interfaces/cs_wownero_interface.dart';
import '../../crypto_currency/crypto_currency.dart';
import '../../models/tx_data.dart';
-import '../intermediate/lib_monero_wallet.dart';
+import '../intermediate/lib_wownero_wallet.dart';
-class WowneroWallet extends LibMoneroWallet {
+class WowneroWallet extends LibWowneroWallet {
WowneroWallet(CryptoCurrencyNetwork network)
: super(Wownero(network), lib_monero_compat.WalletType.wownero);
@@ -66,7 +66,7 @@ class WowneroWallet extends LibMoneroWallet {
// unsure why this delay?
await Future<void>.delayed(const Duration(milliseconds: 500));
} catch (e) {
- approximateFee = await csMonero.estimateFee(
+ approximateFee = await csWownero.estimateFee(
feeRate.toInt(),
amount.raw,
wallet: wallet!,
@@ -86,19 +86,13 @@ class WowneroWallet extends LibMoneroWallet {
}
@override
- bool walletExists(String path) =>
- csMonero.walletExists(path, csCoin: CsCoin.wownero);
+ bool walletExists(String path) => csWownero.walletExists(path);
@override
Future<WrappedWallet> loadWallet({
required String path,
required String password,
- }) => csMonero.loadWallet(
- walletId,
- path: path,
- password: password,
- csCoin: CsCoin.wownero,
- );
+ }) => csWownero.loadWallet(walletId, path: path, password: password);
@override
Future<WrappedWallet> getCreatedWallet({
@@ -106,8 +100,7 @@ class WowneroWallet extends LibMoneroWallet {
required String password,
required int wordCount,
required String seedOffset,
- }) => csMonero.getCreatedWallet(
- csCoin: CsCoin.wownero,
+ }) => csWownero.getCreatedWallet(
path: path,
password: password,
wordCount: wordCount,
@@ -121,13 +114,12 @@ class WowneroWallet extends LibMoneroWallet {
required String mnemonic,
required String seedOffset,
int height = 0,
- }) => csMonero.getRestoredWallet(
+ }) => csWownero.getRestoredWallet(
path: path,
password: password,
mnemonic: mnemonic,
height: height,
seedOffset: seedOffset,
- csCoin: CsCoin.wownero,
walletId: walletId,
);
@@ -138,9 +130,8 @@ class WowneroWallet extends LibMoneroWallet {
required String address,
required String privateViewKey,
int height = 0,
- }) => csMonero.getRestoredFromViewKeyWallet(
+ }) => csWownero.getRestoredFromViewKeyWallet(
walletId: walletId,
- csCoin: CsCoin.wownero,
path: path,
password: password,
address: address,
diff --git a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart
index e5f91c4..38ca63e 100644
--- a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart
+++ b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart
@@ -338,7 +338,7 @@ abstract class LibMoneroWallet<T extends CryptonoteCurrency>
final path = await pathForWallet(name: walletId, type: compatType);
if (!(walletExists(path)) && isRestore != true) {
if (wordCount == null) {
- throw Exception("Missing word count for new xmr/wow wallet!");
+ throw Exception("Missing word count for new xmr wallet!");
}
try {
final password = generatePassword();
@@ -359,7 +359,7 @@ abstract class LibMoneroWallet<T extends CryptonoteCurrency>
isar: mainDB.isar,
);
- // special case for xmr/wow. Normally mnemonic + passphrase is saved
+ // special case for xmr. Normally mnemonic + passphrase is saved
// before wallet.init() is called
await secureStorageInterface.write(
key: Wallet.mnemonicKey(walletId: walletId),
@@ -830,7 +830,7 @@ abstract class LibMoneroWallet<T extends CryptonoteCurrency>
if (wallet == null) {
Logging.instance.w(
"onUTXOsChanged triggered while cs_monero wallet is null. If this "
- "occurs while not in a monero/wownero wallet this warning can be "
+ "occurs while not in a monero wallet this warning can be "
"ignored.",
);
return;
diff --git a/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart b/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart
new file mode 100644
index 0000000..cf47fb9
--- /dev/null
+++ b/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart
@@ -0,0 +1,1509 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'dart:math';
+
+import 'package:compat/compat.dart' as lib_monero_compat;
+import 'package:isar_community/isar.dart';
+import 'package:mutex/mutex.dart';
+import 'package:stack_wallet_backup/generate_password.dart';
+
+import '../../../app_config.dart';
+import '../../../db/hive/db.dart';
+import '../../../models/balance.dart';
+import '../../../models/input.dart';
+import '../../../models/isar/models/blockchain_data/address.dart';
+import '../../../models/isar/models/blockchain_data/transaction.dart';
+import '../../../models/isar/models/blockchain_data/utxo.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/keys/cw_key_data.dart';
+import '../../../models/keys/view_only_wallet_data.dart';
+import '../../../models/node_model.dart';
+import '../../../models/paymint/fee_object_model.dart';
+import '../../../services/event_bus/events/global/blocks_remaining_event.dart';
+import '../../../services/event_bus/events/global/refresh_percent_changed_event.dart';
+import '../../../services/event_bus/events/global/tor_connection_status_changed_event.dart';
+import '../../../services/event_bus/events/global/tor_status_changed_event.dart';
+import '../../../services/event_bus/events/global/updated_in_background_event.dart';
+import '../../../services/event_bus/events/global/wallet_sync_status_changed_event.dart';
+import '../../../services/event_bus/global_event_bus.dart';
+import '../../../services/tor_service.dart';
+import '../../../utilities/amount/amount.dart';
+import '../../../utilities/enums/fee_rate_type_enum.dart';
+import '../../../utilities/logger.dart';
+import '../../../utilities/stack_file_system.dart';
+import '../../../wl_gen/interfaces/cs_monero_interface.dart';
+import '../../../wl_gen/interfaces/cs_salvium_interface.dart';
+import '../../../wl_gen/interfaces/cs_wownero_interface.dart';
+import '../../crypto_currency/intermediate/cryptonote_currency.dart';
+import '../../isar/models/wallet_info.dart';
+import '../../models/tx_data.dart';
+import '../wallet.dart';
+import '../wallet_mixin_interfaces/multi_address_interface.dart';
+import '../wallet_mixin_interfaces/view_only_option_interface.dart';
+import 'cryptonote_wallet.dart';
+
+abstract class LibWowneroWallet<T extends CryptonoteCurrency>
+ extends CryptonoteWallet<T>
+ with ViewOnlyOptionInterface<T>
+ implements MultiAddressInterface<T> {
+ @override
+ int get isarTransactionVersion => 2;
+
+ WrappedWallet? wallet;
+
+ LibWowneroWallet(super.currency, this.compatType) {
+ final bus = GlobalEventBus.instance;
+
+ // Listen for tor status changes.
+ _torStatusListener = bus.on<TorConnectionStatusChangedEvent>().listen((
+ event,
+ ) async {
+ switch (event.newStatus) {
+ case TorConnectionStatus.connecting:
+ if (!_torConnectingLock.isLocked) {
+ await _torConnectingLock.acquire();
+ }
+ _requireMutex = true;
+ break;
+
+ case TorConnectionStatus.connected:
+ case TorConnectionStatus.disconnected:
+ if (_torConnectingLock.isLocked) {
+ _torConnectingLock.release();
+ }
+ _requireMutex = false;
+ break;
+ }
+ });
+
+ // Listen for tor preference changes.
+ _torPreferenceListener = bus.on<TorPreferenceChangedEvent>().listen((
+ event,
+ ) async {
+ await updateNode();
+ });
+
+ // Potentially dangerous hack. See comments in _startInit()
+ _startInit();
+ }
+ // cw based wallet listener to handle synchronization of utxo frozen states
+ late final StreamSubscription<List<UTXO>> _streamSub;
+ Future<void> _startInit() async {
+ // Delay required as `mainDB` is not initialized in constructor.
+ // This is a hack and could lead to a race condition.
+ Future.delayed(const Duration(seconds: 2), () {
+ _streamSub = mainDB.isar.utxos
+ .where()
+ .walletIdEqualTo(walletId)
+ .watch(fireImmediately: true)
+ .listen((utxos) async {
+ try {
+ await onUTXOsChanged(utxos);
+ await updateBalance(shouldUpdateUtxos: false);
+ } catch (e, s) {
+ Logging.instance.e("_startInit", error: e, stackTrace: s);
+ }
+ });
+ });
+ }
+
+ final lib_monero_compat.WalletType compatType;
+
+ lib_monero_compat.SyncStatus? get syncStatus => _syncStatus;
+ lib_monero_compat.SyncStatus? _syncStatus;
+ int _syncedCount = 0;
+ void _setSyncStatus(lib_monero_compat.SyncStatus status) {
+ if (status is lib_monero_compat.SyncedSyncStatus) {
+ if (_syncStatus is lib_monero_compat.SyncedSyncStatus) {
+ _syncedCount++;
+ }
+ } else {
+ _syncedCount = 0;
+ }
+
+ if (_syncedCount < 3) {
+ _syncStatus = status;
+ syncStatusChanged();
+ }
+ }
+
+ final prepareSendMutex = Mutex();
+ final estimateFeeMutex = Mutex();
+
+ bool _txRefreshLock = false;
+ int _lastCheckedHeight = -1;
+ int _txCount = 0;
+ int currentKnownChainHeight = 0;
+ double highestPercentCached = 0;
+
+ Future<WrappedWallet> loadWallet({
+ required String path,
+ required String password,
+ });
+
+ Future<WrappedWallet> getCreatedWallet({
+ required String path,
+ required String password,
+ required int wordCount,
+ required String seedOffset,
+ });
+
+ Future<WrappedWallet> getRestoredWallet({
+ required String path,
+ required String password,
+ required String mnemonic,
+ required String seedOffset,
+ int height = 0,
+ });
+
+ Future<WrappedWallet> getRestoredFromViewKeyWallet({
+ required String path,
+ required String password,
+ required String address,
+ required String privateViewKey,
+ int height = 0,
+ });
+
+ void invalidSeedLengthCheck(int length);
+
+ bool walletExists(String path);
+
+ String getTxKeyFor({required String txid}) {
+ if (wallet == null) {
+ throw Exception("Cannot get tx key in uninitialized LibWowneroWallet");
+ }
+ return csWownero.getTxKey(wallet!, txid);
+ }
+
+ void _setListener() {
+ if (wallet != null && !csWownero.hasListeners(wallet!)) {
+ csWownero.addListener(
+ wallet!,
+ CsWalletListener(
+ onSyncingUpdate: onSyncingUpdate,
+ onNewBlock: onNewBlock,
+ onBalancesChanged: onBalancesChanged,
+ onError: (e, s) {
+ Logging.instance.w("$e\n$s", error: e, stackTrace: s);
+ },
+ ),
+ );
+ }
+ }
+
+ @override
+ Future<void> open() async {
+ bool wasNull = false;
+
+ if (wallet == null) {
+ wasNull = true;
+ // LibWowneroWalletT?.close();
+ final path = await pathForWallet(name: walletId, type: compatType);
+
+ final String password;
+ try {
+ password = (await secureStorageInterface.read(
+ key: lib_monero_compat.libMoneroWalletPasswordKey(walletId),
+ ))!;
+ } catch (e, s) {
+ throw Exception("Password not found $e, $s");
+ }
+
+ wallet = await loadWallet(path: path, password: password);
+
+ _setListener();
+
+ await updateNode();
+ }
+
+ Address? currentAddress = await getCurrentReceivingAddress();
+ if (currentAddress == null) {
+ currentAddress = addressFor(index: 0);
+ await mainDB.updateOrPutAddresses([currentAddress]);
+ }
+ if (info.cachedReceivingAddress != currentAddress.value) {
+ await info.updateReceivingAddress(
+ newAddress: currentAddress.value,
+ isar: mainDB.isar,
+ );
+ }
+
+ if (wasNull) {
+ try {
+ _setSyncStatus(lib_monero_compat.ConnectingSyncStatus());
+ csWownero.startSyncing(wallet!);
+ } catch (_) {
+ _setSyncStatus(lib_monero_compat.FailedSyncStatus());
+ // TODO log
+ }
+ }
+ _setListener();
+ csWownero.startListeners(wallet!);
+ csWownero.startAutoSaving(wallet!);
+
+ unawaited(refresh());
+ }
+
+ @Deprecated("Only used in the case of older wallets")
+ lib_monero_compat.WalletInfo? getLibWowneroWalletInfo(String walletId) {
+ try {
+ return DB.instance.moneroWalletInfoBox.values.firstWhere(
+ (info) => info.id == lib_monero_compat.hiveIdFor(walletId, compatType),
+ );
+ } catch (_) {
+ return null;
+ }
+ }
+
+ Future<void> save() async {
+ if (!Platform.isWindows) {
+ final appRoot = await StackFileSystem.applicationRootDirectory();
+ await lib_monero_compat.backupWalletFiles(
+ name: walletId,
+ type: compatType,
+ appRoot: appRoot,
+ );
+ }
+ await csWownero.save(wallet!);
+ }
+
+ Address addressFor({required int index, int account = 0}) {
+ final address = csWownero.getAddress(
+ wallet!,
+ accountIndex: account,
+ addressIndex: index,
+ );
+
+ if (address.contains("111")) {
+ throw Exception("111 address found!");
+ }
+
+ final newReceivingAddress = Address(
+ walletId: walletId,
+ derivationIndex: index,
+ derivationPath: null,
+ value: address,
+ publicKey: [],
+ type: AddressType.cryptonote,
+ subType: AddressSubType.receiving,
+ );
+
+ return newReceivingAddress;
+ }
+
+ Future<CWKeyData?> getKeys() async {
+ final oldInfo = getLibWowneroWalletInfo(walletId);
+ if (wallet == null || (oldInfo != null && oldInfo.name != walletId)) {
+ return null;
+ }
+ try {
+ return CWKeyData(
+ walletId: walletId,
+ publicViewKey: csWownero.getPublicViewKey(wallet!),
+ privateViewKey: csWownero.getPrivateViewKey(wallet!),
+ publicSpendKey: csWownero.getPublicSpendKey(wallet!),
+ privateSpendKey: csWownero.getPrivateSpendKey(wallet!),
+ );
+ } catch (e, s) {
+ Logging.instance.f("getKeys failed: ", error: e, stackTrace: s);
+ return CWKeyData(
+ walletId: walletId,
+ publicViewKey: "ERROR",
+ privateViewKey: "ERROR",
+ publicSpendKey: "ERROR",
+ privateSpendKey: "ERROR",
+ );
+ }
+ }
+
+ Future<(String, String)>
+ hackToCreateNewViewOnlyWalletDataFromNewlyCreatedWalletThisFunctionShouldNotBeCalledUnlessYouKnowWhatYouAreDoing() async {
+ final path = await pathForWallet(name: walletId, type: compatType);
+ final String password;
+ try {
+ password = (await secureStorageInterface.read(
+ key: lib_monero_compat.libMoneroWalletPasswordKey(walletId),
+ ))!;
+ } catch (e, s) {
+ throw Exception("Password not found $e, $s");
+ }
+ wallet = await loadWallet(path: path, password: password);
+ return (
+ csWownero.getAddress(wallet!),
+ csWownero.getPrivateViewKey(wallet!),
+ );
+ }
+
+ @override
+ Future<void> init({bool? isRestore, int? wordCount}) async {
+ final path = await pathForWallet(name: walletId, type: compatType);
+ if (!(walletExists(path)) && isRestore != true) {
+ if (wordCount == null) {
+ throw Exception("Missing word count for new xmr/wow wallet!");
+ }
+ try {
+ final password = generatePassword();
+ await secureStorageInterface.write(
+ key: lib_monero_compat.libMoneroWalletPasswordKey(walletId),
+ value: password,
+ );
+
+ final wallet = await getCreatedWallet(
+ path: path,
+ password: password,
+ wordCount: wordCount,
+ seedOffset: "", // default for non restored wallets for now
+ );
+
+ await info.updateRestoreHeight(
+ newRestoreHeight: csWownero.getRefreshFromBlockHeight(wallet),
+ isar: mainDB.isar,
+ );
+
+ // special case for xmr/wow. Normally mnemonic + passphrase is saved
+ // before wallet.init() is called
+ await secureStorageInterface.write(
+ key: Wallet.mnemonicKey(walletId: walletId),
+ value: csWownero.getSeed(wallet),
+ );
+ await secureStorageInterface.write(
+ key: Wallet.mnemonicPassphraseKey(walletId: walletId),
+ value: "",
+ );
+ } catch (e, s) {
+ Logging.instance.f("", error: e, stackTrace: s);
+ }
+ await updateNode();
+ }
+
+ return super.init();
+ }
+
+ @override
+ Future<void> recover({required bool isRescan}) async {
+ if (isRescan) {
+ await refreshMutex.protect(() async {
+ // clear blockchain info
+ await mainDB.deleteWalletBlockchainData(walletId);
+
+ highestPercentCached = 0;
+ unawaited(csWownero.rescanBlockchain(wallet!));
+ csWownero.startSyncing(wallet!);
+ // unawaited(save());
+ });
+ unawaited(refresh());
+ return;
+ }
+
+ if (isViewOnly) {
+ await recoverViewOnly();
+ return;
+ }
+
+ await refreshMutex.protect(() async {
+ final mnemonic = await getMnemonic();
+ final seedOffset = await getMnemonicPassphrase();
+ final seedLength = mnemonic.trim().split(" ").length;
+
+ invalidSeedLengthCheck(seedLength);
+
+ try {
+ final height = max(info.restoreHeight, 0);
+
+ await info.updateRestoreHeight(
+ newRestoreHeight: height,
+ isar: mainDB.isar,
+ );
+
+ final String name = walletId;
+
+ final path = await pathForWallet(name: name, type: compatType);
+
+ try {
+ final password = generatePassword();
+ await secureStorageInterface.write(
+ key: lib_monero_compat.libMoneroWalletPasswordKey(walletId),
+ value: password,
+ );
+
+ final wallet = await getRestoredWallet(
+ path: path,
+ password: password,
+ mnemonic: mnemonic,
+ height: height,
+ seedOffset: seedOffset,
+ );
+
+ if (this.wallet != null) {
+ await exit();
+ }
+
+ this.wallet = wallet;
+
+ _setListener();
+
+ final newReceivingAddress =
+ await getCurrentReceivingAddress() ??
+ Address(
+ walletId: walletId,
+ derivationIndex: 0,
+ derivationPath: null,
+ value: csWownero.getAddress(this.wallet!),
+ publicKey: [],
+ type: AddressType.cryptonote,
+ subType: AddressSubType.receiving,
+ );
+
+ await mainDB.updateOrPutAddresses([newReceivingAddress]);
+ await info.updateReceivingAddress(
+ newAddress: newReceivingAddress.value,
+ isar: mainDB.isar,
+ );
+ } catch (e, s) {
+ Logging.instance.f("", error: e, stackTrace: s);
+ rethrow;
+ }
+ await updateNode();
+ _setListener();
+
+ // LibWowneroWallet?.setRecoveringFromSeed(isRecovery: true);
+ unawaited(csWownero.rescanBlockchain(wallet!));
+ csWownero.startSyncing(wallet!);
+
+ // await save();
+ csWownero.startListeners(wallet!);
+ csWownero.startAutoSaving(wallet!);
+ } catch (e, s) {
+ Logging.instance.e(
+ "Exception rethrown from recoverFromMnemonic(): ",
+ error: e,
+ stackTrace: s,
+ );
+ rethrow;
+ }
+ });
+ }
+
+ // dumb temporary hack
+ bool _canPing = false;
+
+ @override
+ Future<bool> pingCheck() {
+ if (_canPing) {
+ return csWownero.isConnectedToDaemon(wallet!);
+ } else {
+ return Future.value(false);
+ }
+ }
+
+ @override
+ Future<void> updateNode() async {
+ final node = getCurrentNode();
+
+ if (_torNodeMismatchGuard(node)) {
+ throw Exception("TOR – clearnet mismatch");
+ }
+
+ final host = node.host.endsWith(".onion")
+ ? node.host
+ : Uri.parse(node.host).host;
+ final ({InternetAddress host, int port})? proxy =
+ AppConfig.hasFeature(AppFeature.tor) && prefs.useTor && !node.forceNoTor
+ ? TorService.sharedInstance.getProxyInfo()
+ : null;
+
+ _setSyncStatus(lib_monero_compat.ConnectingSyncStatus());
+ try {
+ if (_requireMutex) {
+ await _torConnectingLock.protect(() async {
+ await csWownero.connect(
+ wallet!,
+ daemonAddress: "$host:${node.port}",
+ daemonUsername: node.loginName,
+ daemonPassword: await node.getPassword(secureStorageInterface),
+ trusted: node.trusted ?? false,
+ useSSL: node.useSSL,
+ socksProxyAddress: node.forceNoTor
+ ? null
+ : proxy == null
+ ? null
+ : "${proxy.host.address}:${proxy.port}",
+ );
+ });
+ } else {
+ await csWownero.connect(
+ wallet!,
+ daemonAddress: "$host:${node.port}",
+ daemonUsername: node.loginName,
+ daemonPassword: await node.getPassword(secureStorageInterface),
+ trusted: node.trusted ?? false,
+ useSSL: node.useSSL,
+ socksProxyAddress: node.forceNoTor
+ ? null
+ : proxy == null
+ ? null
+ : "${proxy.host.address}:${proxy.port}",
+ );
+ }
+ csWownero.startSyncing(wallet!);
+ csWownero.startListeners(wallet!);
+ csWownero.startAutoSaving(wallet!);
+
+ _setSyncStatus(lib_monero_compat.ConnectedSyncStatus());
+ } catch (e, s) {
+ _setSyncStatus(lib_monero_compat.FailedSyncStatus());
+ Logging.instance.e(
+ "Exception caught in $runtimeType.updateNode(): ",
+ error: e,
+ stackTrace: s,
+ );
+ }
+
+ return;
+ }
+
+ @override
+ Future<void> updateTransactions() async {
+ if (wallet == null) {
+ return;
+ }
+
+ final localTxids = await mainDB.isar.transactionV2s
+ .where()
+ .walletIdEqualTo(walletId)
+ .filter()
+ .heightGreaterThan(0)
+ .txidProperty()
+ .findAll();
+
+ final allTxids = await csWownero.getAllTxids(wallet!, refresh: true);
+
+ final txidsToFetch = allTxids.toSet().difference(localTxids.toSet());
+
+ if (txidsToFetch.isEmpty) {
+ return;
+ }
+
+ final transactions = await csWownero.getTxs(
+ wallet!,
+ txids: txidsToFetch,
+ refresh: false,
+ );
+
+ final allOutputs = await csWownero.getOutputs(
+ wallet!,
+ includeSpent: true,
+ refresh: true,
+ );
+
+ // final cachedTransactions =
+ // DB.instance.get<dynamic>(boxName: walletId, key: 'latest_tx_model')
+ // as TransactionData?;
+ // int latestTxnBlockHeight =
+ // DB.instance.get<dynamic>(boxName: walletId, key: "storedTxnDataHeight")
+ // as int? ??
+ // 0;
+ //
+ // final txidsList = DB.instance
+ // .get<dynamic>(boxName: walletId, key: "cachedTxids") as List? ??
+ // [];
+ //
+ // final Set<String> cachedTxids = Set<String>.from(txidsList);
+
+ // TODO: filter to skip cached + confirmed txn processing in next step
+ // final unconfirmedCachedTransactions =
+ // cachedTransactions?.getAllTransactions() ?? {};
+ // unconfirmedCachedTransactions
+ // .removeWhere((key, value) => value.confirmedStatus);
+ //
+ // if (cachedTransactions != null) {
+ // for (final tx in allTxHashes.toList(growable: false)) {
+ // final txHeight = tx["height"] as int;
+ // if (txHeight > 0 &&
+ // txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) {
+ // if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) {
+ // allTxHashes.remove(tx);
+ // }
+ // }
+ // }
+ // }
+
+ final List<TransactionV2> txns = [];
+
+ for (final tx in transactions) {
+ final associatedOutputs = allOutputs.where((e) => e.hash == tx.hash);
+ final List<InputV2> inputs = [];
+ final List<OutputV2> outputs = [];
+ TransactionType type;
+ if (!tx.isSpend) {
+ type = TransactionType.incoming;
+ for (final output in associatedOutputs) {
+ outputs.add(
+ OutputV2.isarCantDoRequiredInDefaultConstructor(
+ scriptPubKeyHex: "",
+ valueStringSats: output.value.toString(),
+ addresses: [output.address],
+ walletOwns: true,
+ ),
+ );
+ }
+ } else {
+ type = TransactionType.outgoing;
+ for (final output in associatedOutputs) {
+ inputs.add(
+ InputV2.isarCantDoRequiredInDefaultConstructor(
+ scriptSigHex: null,
+ scriptSigAsm: null,
+ sequence: null,
+ outpoint: null,
+ addresses: [output.address],
+ valueStringSats: output.value.toString(),
+ witness: null,
+ innerRedeemScriptAsm: null,
+ coinbase: null,
+ walletOwns: true,
+ ),
+ );
+ }
+ }
+
+ final txn = TransactionV2(
+ walletId: walletId,
+ blockHash: null, // not exposed via current cs_monero
+ hash: tx.hash,
+ txid: tx.hash,
+ timestamp: (tx.timeStamp.millisecondsSinceEpoch ~/ 1000),
+ height: tx.blockHeight,
+ inputs: inputs,
+ outputs: outputs,
+ version: -1, // not exposed via current cs_monero
+ type: type,
+ subType: TransactionSubType.none,
+ otherData: jsonEncode({
+ TxV2OdKeys.overrideFee: Amount(
+ rawValue: tx.fee,
+ fractionDigits: cryptoCurrency.fractionDigits,
+ ).toJsonString(),
+ TxV2OdKeys.moneroAmount: Amount(
+ rawValue: tx.amount,
+ fractionDigits: cryptoCurrency.fractionDigits,
+ ).toJsonString(),
+ TxV2OdKeys.moneroAccountIndex: tx.accountIndex,
+ TxV2OdKeys.isMoneroTransaction: true,
+ }),
+ );
+
+ txns.add(txn);
+ }
+
+ await mainDB.updateOrPutTransactionV2s(txns);
+ }
+
+ Future<Amount> get availableBalance async {
+ try {
+ return Amount(
+ rawValue: csWownero.getUnlockedBalance(wallet!)!,
+ fractionDigits: cryptoCurrency.fractionDigits,
+ );
+ } catch (_) {
+ return info.cachedBalance.spendable;
+ }
+ }
+
+ Future<Amount> get totalBalance async {
+ try {
+ final full = csWownero.getBalance(wallet!);
+ if (full != null) {
+ return Amount(
+ rawValue: full,
+ fractionDigits: cryptoCurrency.fractionDigits,
+ );
+ } else {
+ final transactions = await csWownero.getAllTxs(wallet!, refresh: true);
+ BigInt transactionBalance = BigInt.zero;
+ for (final tx in transactions) {
+ if (!tx.isSpend) {
+ transactionBalance += tx.amount;
+ } else {
+ transactionBalance += -tx.amount - tx.fee;
+ }
+ }
+
+ return Amount(
+ rawValue: transactionBalance,
+ fractionDigits: cryptoCurrency.fractionDigits,
+ );
+ }
+ } catch (_) {
+ return info.cachedBalance.total;
+ }
+ }
+
+ @override
+ Future<void> exit() async {
+ Logging.instance.i("exit called on $wallet!");
+ if (wallet != null) {
+ csWownero.stopAutoSaving(wallet!);
+ csWownero.stopListeners(wallet!);
+ csWownero.stopSyncing(wallet!);
+ await csWownero.save(wallet!);
+ }
+ }
+
+ Future<String> pathForWalletDir({
+ required String name,
+ required lib_monero_compat.WalletType type,
+ }) async {
+ final Directory root = await StackFileSystem.applicationRootDirectory();
+ return lib_monero_compat.pathForWalletDir(
+ name: name,
+ type: type.name.toLowerCase(),
+ appRoot: root,
+ );
+ }
+
+ Future<String> pathForWallet({
+ required String name,
+ required lib_monero_compat.WalletType type,
+ }) async => await pathForWalletDir(
+ name: name,
+ type: type,
+ ).then((path) => '$path/$name');
+
+ void onSyncingUpdate({
+ required int syncHeight,
+ required int nodeHeight,
+ String? message,
+ }) {
+ if (nodeHeight > 0 && syncHeight >= 0) {
+ currentKnownChainHeight = nodeHeight;
+ updateChainHeight();
+ final blocksLeft = nodeHeight - syncHeight;
+ final lib_monero_compat.SyncStatus status;
+ if (blocksLeft < 100) {
+ status = lib_monero_compat.SyncedSyncStatus();
+
+ // if (!_hasSyncAfterStartup) {
+ // _hasSyncAfterStartup = true;
+ // await save();
+ // }
+ //
+ // if (walletInfo.isRecovery!) {
+ // await setAsRecovered();
+ // }
+ } else {
+ final percent = syncHeight / currentKnownChainHeight;
+
+ status = lib_monero_compat.SyncingSyncStatus(
+ blocksLeft,
+ percent,
+ currentKnownChainHeight,
+ );
+ }
+
+ _setSyncStatus(status);
+ _refreshTxDataHelper();
+ }
+ }
+
+ void onBalancesChanged({
+ required BigInt newBalance,
+ required BigInt newUnlockedBalance,
+ }) async {
+ try {
+ await updateBalance();
+ await updateTransactions();
+ } catch (e, s) {
+ Logging.instance.w("onBalancesChanged(): ", error: e, stackTrace: s);
+ }
+ }
+
+ void onNewBlock(int nodeHeight) async {
+ try {
+ await updateTransactions();
+ } catch (e, s) {
+ Logging.instance.w("onNewBlock(): ", error: e, stackTrace: s);
+ }
+ }
+
+ final _utxosUpdateLock = Mutex();
+ Future<void> onUTXOsChanged(List<UTXO> utxos) async {
+ if (wallet == null) {
+ Logging.instance.w(
+ "onUTXOsChanged triggered while cs_monero wallet is null. If this "
+ "occurs while not in a monero/wownero wallet this warning can be "
+ "ignored.",
+ );
+ return;
+ }
+
+ await _utxosUpdateLock.protect(() async {
+ final cwUtxos = await csWownero.getOutputs(wallet!, refresh: true);
+
+ // bool changed = false;
+
+ for (final cw in cwUtxos) {
+ final match = utxos.where(
+ (e) =>
+ e.keyImage != null &&
+ e.keyImage!.isNotEmpty &&
+ e.keyImage == cw.keyImage,
+ );
+
+ if (match.isNotEmpty) {
+ final u = match.first;
+
+ if (u.isBlocked) {
+ if (!cw.isFrozen) {
+ await csWownero.freezeOutput(wallet!, cw.keyImage);
+ // changed = true;
+ }
+ } else {
+ if (cw.isFrozen) {
+ await csWownero.thawOutput(wallet!, cw.keyImage);
+ // changed = true;
+ }
+ }
+ }
+ }
+
+ // if (changed) {
+ // await LibWowneroWallet?.updateUTXOs();
+ // }
+ });
+ }
+
+ void onNewTransaction() {
+ // TODO: [prio=low] get rid of UpdatedInBackgroundEvent and move to
+ // adding the v2 tx to the db which would update ui automagically since the
+ // db is watched by the ui
+ // call this here?
+ GlobalEventBus.instance.fire(
+ UpdatedInBackgroundEvent(
+ "New data found in $walletId ${info.name} in background!",
+ walletId,
+ ),
+ );
+ }
+
+ void syncStatusChanged() async {
+ final _syncStatus = syncStatus;
+
+ if (_syncStatus != null) {
+ if (_syncStatus.progress() == 1 && refreshMutex.isLocked) {
+ refreshMutex.release();
+ }
+
+ WalletSyncStatus? status;
+ xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(true);
+
+ if (_syncStatus is lib_monero_compat.SyncingSyncStatus) {
+ final int blocksLeft = _syncStatus.blocksLeft;
+
+ // ensure at least 1 to prevent math errors
+ final int height = max(1, _syncStatus.height);
+
+ final nodeHeight = height + blocksLeft;
+ currentKnownChainHeight = nodeHeight;
+
+ // final percent = height / nodeHeight;
+ final percent = _syncStatus.ptc;
+
+ final highest = max(highestPercentCached, percent);
+
+ final unchanged = highest == highestPercentCached;
+ if (unchanged) {
+ return;
+ }
+
+ // update cached
+ if (highestPercentCached < percent) {
+ highestPercentCached = percent;
+ }
+
+ GlobalEventBus.instance.fire(
+ RefreshPercentChangedEvent(highest, walletId),
+ );
+ GlobalEventBus.instance.fire(
+ BlocksRemainingEvent(blocksLeft, walletId),
+ );
+ } else if (_syncStatus is lib_monero_compat.SyncedSyncStatus) {
+ status = WalletSyncStatus.synced;
+ } else if (_syncStatus is lib_monero_compat.NotConnectedSyncStatus) {
+ status = WalletSyncStatus.unableToSync;
+ xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(false);
+ } else if (_syncStatus is lib_monero_compat.StartingSyncStatus) {
+ status = WalletSyncStatus.syncing;
+ GlobalEventBus.instance.fire(
+ RefreshPercentChangedEvent(highestPercentCached, walletId),
+ );
+ } else if (_syncStatus is lib_monero_compat.FailedSyncStatus) {
+ status = WalletSyncStatus.unableToSync;
+ xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(false);
+ } else if (_syncStatus is lib_monero_compat.ConnectingSyncStatus) {
+ status = WalletSyncStatus.syncing;
+ GlobalEventBus.instance.fire(
+ RefreshPercentChangedEvent(highestPercentCached, walletId),
+ );
+ } else if (_syncStatus is lib_monero_compat.ConnectedSyncStatus) {
+ status = WalletSyncStatus.syncing;
+ GlobalEventBus.instance.fire(
+ RefreshPercentChangedEvent(highestPercentCached, walletId),
+ );
+ } else if (_syncStatus is lib_monero_compat.LostConnectionSyncStatus) {
+ status = WalletSyncStatus.unableToSync;
+ xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(false);
+ }
+
+ if (status != null) {
+ GlobalEventBus.instance.fire(
+ WalletSyncStatusChangedEvent(status, walletId, info.coin),
+ );
+ }
+ }
+ }
+
+ @override
+ Future<void> checkSaveInitialReceivingAddress() async {
+ // this doesn't work without opening the wallet first which takes a while
+ }
+
+ // ============ Private ======================================================
+ Future<void> _refreshTxDataHelper() async {
+ if (_txRefreshLock) return;
+ _txRefreshLock = true;
+
+ final _syncStatus = syncStatus;
+
+ if (_syncStatus != null &&
+ _syncStatus is lib_monero_compat.SyncingSyncStatus) {
+ final int blocksLeft = _syncStatus.blocksLeft;
+ final tenKChange = blocksLeft ~/ 10000;
+
+ // only refresh transactions periodically during a sync
+ if (_lastCheckedHeight == -1 || tenKChange < _lastCheckedHeight) {
+ _lastCheckedHeight = tenKChange;
+ await _refreshTxData();
+ }
+ } else {
+ await _refreshTxData();
+ }
+
+ _txRefreshLock = false;
+ }
+
+ Future<void> _refreshTxData() async {
+ await updateTransactions();
+ final count = await mainDB.getTransactions(walletId).count();
+
+ if (count > _txCount) {
+ _txCount = count;
+ await updateBalance();
+ GlobalEventBus.instance.fire(
+ UpdatedInBackgroundEvent(
+ "New transaction data found in $walletId ${info.name}!",
+ walletId,
+ ),
+ );
+ }
+ }
+
+ bool _torNodeMismatchGuard(NodeModel node) {
+ _canPing = true; // Reset.
+
+ final bool mismatch =
+ (prefs.useTor && node.clearnetEnabled && !node.torEnabled) ||
+ (!prefs.useTor && !node.clearnetEnabled && node.torEnabled);
+
+ if (mismatch) {
+ _canPing = false;
+ if (wallet != null) {
+ csWownero.stopAutoSaving(wallet!);
+ csWownero.stopListeners(wallet!);
+ csWownero.stopSyncing(wallet!);
+ }
+ _setSyncStatus(lib_monero_compat.FailedSyncStatus());
+ }
+
+ return mismatch; // Caller decides whether to throw.
+ }
+
+ // ============ Overrides ====================================================
+
+ @override
+ FilterOperation? get changeAddressFilterOperation => null;
+
+ @override
+ FilterOperation? get receivingAddressFilterOperation => null;
+
+ @override
+ Future<bool> updateUTXOs() async {
+ final List<UTXO> outputArray = [];
+ final utxos = wallet == null
+ ? <CsOutput>[]
+ : await csWownero.getOutputs(wallet!, refresh: true);
+ for (final rawUTXO in utxos) {
+ if (!rawUTXO.spent) {
+ final current = await mainDB.isar.utxos
+ .where()
+ .walletIdEqualTo(walletId)
+ .filter()
+ .voutEqualTo(rawUTXO.vout)
+ .and()
+ .txidEqualTo(rawUTXO.hash)
+ .findFirst();
+ final tx = await mainDB.isar.transactionV2s
+ .where()
+ .walletIdEqualTo(walletId)
+ .filter()
+ .txidEqualTo(rawUTXO.hash)
+ .findFirst();
+
+ final otherDataMap = {
+ UTXOOtherDataKeys.keyImage: rawUTXO.keyImage,
+ UTXOOtherDataKeys.spent: rawUTXO.spent,
+ };
+
+ final utxo = UTXO(
+ address: rawUTXO.address,
+ walletId: walletId,
+ txid: rawUTXO.hash,
+ vout: rawUTXO.vout,
+ value: rawUTXO.value.toInt(),
+ name: current?.name ?? "",
+ isBlocked: current?.isBlocked ?? rawUTXO.isFrozen,
+ blockedReason: current?.blockedReason ?? "",
+ isCoinbase: rawUTXO.coinbase,
+ blockHash: "",
+ blockHeight:
+ tx?.height ?? (rawUTXO.height > 0 ? rawUTXO.height : null),
+ blockTime: tx?.timestamp,
+ otherData: jsonEncode(otherDataMap),
+ );
+
+ outputArray.add(utxo);
+ }
+ }
+
+ await mainDB.updateUTXOs(walletId, outputArray);
+
+ return true;
+ }
+
+ @override
+ Future<void> updateBalance({bool shouldUpdateUtxos = true}) async {
+ if (shouldUpdateUtxos) {
+ await updateUTXOs();
+ }
+
+ final total = await totalBalance;
+ final available = await availableBalance;
+
+ final balance = Balance(
+ total: total,
+ spendable: available,
+ blockedTotal: Amount(
+ rawValue: BigInt.zero,
+ fractionDigits: cryptoCurrency.fractionDigits,
+ ),
+ pendingSpendable: total - available,
+ );
+
+ await info.updateBalance(newBalance: balance, isar: mainDB.isar);
+ }
+
+ @override
+ Future<void> refresh() async {
+ // Awaiting this lock could be dangerous.
+ // Since refresh is periodic (generally)
+ if (refreshMutex.isLocked) {
+ return;
+ }
+
+ final node = getCurrentNode();
+
+ if (_torNodeMismatchGuard(node)) {
+ throw Exception("TOR – clearnet mismatch");
+ }
+
+ // this acquire should be almost instant due to above check.
+ // Slight possibility of race but should be irrelevant
+ await refreshMutex.acquire();
+
+ csWownero.startSyncing(wallet!);
+ _setSyncStatus(lib_monero_compat.StartingSyncStatus());
+
+ await updateTransactions();
+ await updateBalance();
+
+ if (info.otherData[WalletInfoKeys.reuseAddress] != true) {
+ await checkReceivingAddressForTransactions();
+ }
+
+ if (refreshMutex.isLocked) {
+ refreshMutex.release();
+ }
+
+ final synced = wallet != null && await csWownero.isSynced(wallet!);
+
+ if (synced) {
+ _setSyncStatus(lib_monero_compat.SyncedSyncStatus());
+ }
+ }
+
+ @override
+ Future<void> generateNewReceivingAddress() async {
+ try {
+ final currentReceiving = await getCurrentReceivingAddress();
+
+ final newReceivingIndex = currentReceiving == null
+ ? 0
+ : currentReceiving.derivationIndex + 1;
+
+ final newReceivingAddress = addressFor(index: newReceivingIndex);
+
+ // Add that new receiving address
+ await mainDB.putAddress(newReceivingAddress);
+ await info.updateReceivingAddress(
+ newAddress: newReceivingAddress.value,
+ isar: mainDB.isar,
+ );
+ } catch (e, s) {
+ Logging.instance.e(
+ "Exception in generateNewAddress(): ",
+ error: e,
+ stackTrace: s,
+ );
+ }
+ }
+
+ @override
+ Future<void> checkReceivingAddressForTransactions() async {
+ if (info.otherData[WalletInfoKeys.reuseAddress] == true) {
+ try {
+ throw Exception();
+ } catch (_, s) {
+ Logging.instance.e(
+ "checkReceivingAddressForTransactions called but reuse address flag set: $s",
+ error: e,
+ stackTrace: s,
+ );
+ }
+ }
+
+ try {
+ int highestIndex = -1;
+ final entries = await csWownero.getAllTxs(wallet!, refresh: true);
+ for (final element in entries) {
+ if (!element.isSpend) {
+ final int curAddressIndex = element.addressIndexes.isEmpty
+ ? 0
+ : element.addressIndexes.reduce(max);
+ if (curAddressIndex > highestIndex) {
+ highestIndex = curAddressIndex;
+ }
+ }
+ }
+
+ // Check the new receiving index
+ final currentReceiving = await getCurrentReceivingAddress();
+ final curIndex = currentReceiving?.derivationIndex ?? -1;
+
+ if (highestIndex >= curIndex) {
+ // First increment the receiving index
+ final newReceivingIndex = curIndex + 1;
+
+ // Use new index to derive a new receiving address
+ final newReceivingAddress = addressFor(index: newReceivingIndex);
+
+ final existing = await mainDB
+ .getAddresses(walletId)
+ .filter()
+ .valueEqualTo(newReceivingAddress.value)
+ .findFirst();
+ if (existing == null) {
+ // Add that new change address
+ await mainDB.putAddress(newReceivingAddress);
+ } else {
+ // we need to update the address
+ await mainDB.updateAddress(existing, newReceivingAddress);
+ }
+ if (info.otherData[WalletInfoKeys.reuseAddress] != true) {
+ // keep checking until address with no tx history is set as current
+ await checkReceivingAddressForTransactions();
+ }
+ }
+ } on SocketException catch (se, s) {
+ Logging.instance.e(
+ "SocketException caught in _checkReceivingAddressForTransactions(): $se\n$s",
+ error: e,
+ stackTrace: s,
+ );
+ return;
+ } catch (e, s) {
+ Logging.instance.e(
+ "Exception rethrown from _checkReceivingAddressForTransactions(): ",
+ error: e,
+ stackTrace: s,
+ );
+ rethrow;
+ }
+ }
+
+ // TODO: this needs some work. Prio's may need to be changed as well as estimated blocks
+ @override
+ Future<FeeObject> get fees async => FeeObject(
+ numberOfBlocksFast: 10,
+ numberOfBlocksAverage: 15,
+ numberOfBlocksSlow: 20,
+ fast: BigInt.from(csWownero.getTxPriorityHigh()),
+ medium: BigInt.from(csWownero.getTxPriorityMedium()),
+ slow: BigInt.from(csWownero.getTxPriorityNormal()),
+ );
+
+ @override
+ Future<void> updateChainHeight() async {
+ await info.updateCachedChainHeight(
+ newHeight: currentKnownChainHeight,
+ isar: mainDB.isar,
+ );
+ }
+
+ @override
+ Future<void> checkChangeAddressForTransactions() async {
+ // do nothing
+ }
+
+ @override
+ Future<void> generateNewChangeAddress() async {
+ // do nothing
+ }
+
+ @override
+ Future<TxData> prepareSend({required TxData txData}) async {
+ try {
+ final feeRate = txData.feeRateType;
+ if (feeRate is FeeRateType) {
+ final int feePriority;
+ switch (feeRate) {
+ case FeeRateType.fast:
+ feePriority = csWownero.getTxPriorityHigh();
+ break;
+ case FeeRateType.average:
+ feePriority = csWownero.getTxPriorityMedium();
+ break;
+ case FeeRateType.slow:
+ feePriority = csWownero.getTxPriorityNormal();
+ break;
+ default:
+ throw ArgumentError("Invalid use of custom fee");
+ }
+
+ try {
+ final bool sweep;
+
+ if (txData.utxos == null) {
+ final balance = await availableBalance;
+ sweep = txData.amount! == balance;
+ } else {
+ final totalInputsValue = txData.utxos!
+ .map((e) => e.value)
+ .fold(BigInt.zero, (p, e) => p + e);
+ sweep = txData.amount!.raw == totalInputsValue;
+ }
+
+ // TODO: test this one day
+ // cs_monero may not support this yet properly
+ if (sweep && txData.recipients!.length > 1) {
+ throw Exception("Send all not supported with multiple recipients");
+ }
+
+ final List<CsRecipient> outputs = [];
+ for (final recipient in txData.recipients!) {
+ final output = CsRecipient(recipient.address, recipient.amount.raw);
+
+ outputs.add(output);
+ }
+
+ if (outputs.isEmpty) {
+ throw Exception("No recipients provided");
+ }
+
+ final height = await chainHeight;
+ final inputs = txData.utxos?.whereType<StandardInput>().toList();
+
+ return await prepareSendMutex.protect(() async {
+ final CsPendingTransaction pendingTransaction;
+ if (outputs.length == 1) {
+ pendingTransaction = await csWownero.createTx(
+ wallet!,
+ minConfirms: cryptoCurrency.minConfirms,
+ currentHeight: height,
+ output: outputs.first,
+ sweep: sweep,
+ priority: feePriority,
+ preferredInputs: inputs,
+ accountIndex: 0, // sw only uses account 0 at this time
+ );
+ } else {
+ pendingTransaction = await csWownero.createTxMultiDest(
+ wallet!,
+ minConfirms: cryptoCurrency.minConfirms,
+ currentHeight: height,
+ outputs: outputs,
+ priority: feePriority,
+ preferredInputs: inputs,
+ sweep: sweep,
+ accountIndex: 0, // sw only uses account 0 at this time
+ );
+ }
+
+ final realFee = Amount(
+ rawValue: pendingTransaction.fee,
+ fractionDigits: cryptoCurrency.fractionDigits,
+ );
+
+ return txData.copyWith(
+ fee: realFee,
+ pendingTransaction: pendingTransaction,
+ );
+ });
+ } catch (e) {
+ rethrow;
+ }
+ } else {
+ throw ArgumentError("Invalid fee rate argument provided!");
+ }
+ } catch (e, s) {
+ Logging.instance.i(
+ "Exception rethrown from prepare send(): ",
+ error: e,
+ stackTrace: s,
+ );
+
+ if (e.toString().contains("Incorrect unlocked balance")) {
+ throw Exception("Insufficient balance!");
+ } else {
+ throw Exception("Transaction failed with error: $e");
+ }
+ }
+ }
+
+ @override
+ Future<TxData> confirmSend({required TxData txData}) async {
+ try {
+ try {
+ await csWownero.commitTx(wallet!, txData.pendingTransaction!);
+
+ Logging.instance.d(
+ "transaction ${txData.pendingTransaction!.txid} has been sent",
+ );
+ return txData.copyWith(txid: txData.pendingTransaction!.txid);
+ } catch (e, s) {
+ Logging.instance.e(
+ "${info.name} ${compatType.name.toLowerCase()} confirmSend: ",
+ error: e,
+ stackTrace: s,
+ );
+ rethrow;
+ }
+ } catch (e, s) {
+ Logging.instance.e(
+ "Exception rethrown from confirmSend(): ",
+ error: e,
+ stackTrace: s,
+ );
+ rethrow;
+ }
+ }
+
+ // ============== View only ==================================================
+
+ @override
+ Future<void> recoverViewOnly() async {
+ await refreshMutex.protect(() async {
+ final data =
+ await getViewOnlyWalletData() as CryptonoteViewOnlyWalletData;
+
+ try {
+ final height = max(info.restoreHeight, 0);
+
+ await info.updateRestoreHeight(
+ newRestoreHeight: height,
+ isar: mainDB.isar,
+ );
+
+ final String name = walletId;
+
+ final path = await pathForWallet(name: name, type: compatType);
+
+ final password = generatePassword();
+ await secureStorageInterface.write(
+ key: lib_monero_compat.libMoneroWalletPasswordKey(walletId),
+ value: password,
+ );
+
+ final wallet = await getRestoredFromViewKeyWallet(
+ path: path,
+ password: password,
+ address: data.address,
+ privateViewKey: data.privateViewKey,
+ height: height,
+ );
+
+ if (this.wallet == null) {
+ await exit();
+ }
+ this.wallet = wallet;
+
+ _setListener();
+
+ final newReceivingAddress =
+ await getCurrentReceivingAddress() ??
+ Address(
+ walletId: walletId,
+ derivationIndex: 0,
+ derivationPath: null,
+ value: csWownero.getAddress(this.wallet!),
+ publicKey: [],
+ type: AddressType.cryptonote,
+ subType: AddressSubType.receiving,
+ );
+
+ await mainDB.updateOrPutAddresses([newReceivingAddress]);
+ await info.updateReceivingAddress(
+ newAddress: newReceivingAddress.value,
+ isar: mainDB.isar,
+ );
+
+ await updateNode();
+ _setListener();
+
+ unawaited(csWownero.rescanBlockchain(this.wallet!));
+ csWownero.startSyncing(this.wallet!);
+
+ // await save();
+ csWownero.startListeners(this.wallet!);
+ csWownero.startAutoSaving(this.wallet!);
+ } catch (e, s) {
+ Logging.instance.e(
+ "Exception rethrown from recoverViewOnly(): ",
+ error: e,
+ stackTrace: s,
+ );
+ rethrow;
+ }
+ });
+ }
+
+ // ============== Private ====================================================
+
+ StreamSubscription<TorConnectionStatusChangedEvent>? _torStatusListener;
+ StreamSubscription<TorPreferenceChangedEvent>? _torPreferenceListener;
+
+ final Mutex _torConnectingLock = Mutex();
+ bool _requireMutex = false;
+}
diff --git a/lib/widgets/desktop/desktop_fee_dialog.dart b/lib/widgets/desktop/desktop_fee_dialog.dart
index 82f92a5..5f9d163 100644
--- a/lib/widgets/desktop/desktop_fee_dialog.dart
+++ b/lib/widgets/desktop/desktop_fee_dialog.dart
@@ -16,6 +16,7 @@ import '../../wallets/crypto_currency/crypto_currency.dart';
import '../../wallets/isar/providers/eth/current_token_wallet_provider.dart';
import '../../wallets/wallet/impl/firo_wallet.dart';
import '../../wl_gen/interfaces/cs_monero_interface.dart';
+import '../../wl_gen/interfaces/cs_wownero_interface.dart';
import '../animated_text.dart';
import '../conditional_parent.dart';
import 'desktop_dialog.dart';
@@ -60,12 +61,18 @@ class _DesktopFeeDialogState extends ConsumerState<DesktopFeeDialog> {
if (widget.isToken == false) {
final wallet = ref.read(pWallets).getWallet(walletId);
- if (coin is Monero || coin is Wownero) {
+ if (coin is Monero) {
final fee = await wallet.estimateFeeFor(
amount,
BigInt.from(csMonero.getTxPriorityHigh()),
);
ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
+ } else if (coin is Wownero) {
+ final fee = await wallet.estimateFeeFor(
+ amount,
+ BigInt.from(csWownero.getTxPriorityHigh()),
+ );
+ ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
} else if (coin is Firo) {
final Amount fee;
switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
@@ -110,12 +117,18 @@ class _DesktopFeeDialogState extends ConsumerState<DesktopFeeDialog> {
if (widget.isToken == false) {
final wallet = ref.read(pWallets).getWallet(walletId);
- if (coin is Monero || coin is Wownero) {
+ if (coin is Monero) {
final fee = await wallet.estimateFeeFor(
amount,
BigInt.from(csMonero.getTxPriorityMedium()),
);
ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
+ } else if (coin is Wownero) {
+ final fee = await wallet.estimateFeeFor(
+ amount,
+ BigInt.from(csWownero.getTxPriorityMedium()),
+ );
+ ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
} else if (coin is Firo) {
final Amount fee;
switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
@@ -160,12 +173,18 @@ class _DesktopFeeDialogState extends ConsumerState<DesktopFeeDialog> {
if (widget.isToken == false) {
final wallet = ref.read(pWallets).getWallet(walletId);
- if (coin is Monero || coin is Wownero) {
+ if (coin is Monero) {
final fee = await wallet.estimateFeeFor(
amount,
BigInt.from(csMonero.getTxPriorityNormal()),
);
ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
+ } else if (coin is Wownero) {
+ final fee = await wallet.estimateFeeFor(
+ amount,
+ BigInt.from(csWownero.getTxPriorityNormal()),
+ );
+ ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
} else if (coin is Firo) {
final Amount fee;
switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
diff --git a/lib/wl_gen/interfaces/cs_monero_interface.dart b/lib/wl_gen/interfaces/cs_monero_interface.dart
index a676bd1..898a4a4 100644
--- a/lib/wl_gen/interfaces/cs_monero_interface.dart
+++ b/lib/wl_gen/interfaces/cs_monero_interface.dart
@@ -13,7 +13,7 @@ abstract class CsMoneroInterface {
int getTxPriorityMedium();
int getTxPriorityNormal();
- bool walletExists(String path, {required CsCoin csCoin});
+ bool walletExists(String path);
Future<int> estimateFee(
int rate,
@@ -23,7 +23,6 @@ abstract class CsMoneroInterface {
Future<WrappedWallet> loadWallet(
String walletId, {
- required CsCoin csCoin,
required String path,
required String password,
});
@@ -35,7 +34,6 @@ abstract class CsMoneroInterface {
});
Future<WrappedWallet> getCreatedWallet({
- required CsCoin csCoin,
required String path,
required String password,
required int wordCount,
@@ -44,7 +42,6 @@ abstract class CsMoneroInterface {
Future<WrappedWallet> getRestoredWallet({
required String walletId,
- required CsCoin csCoin,
required String path,
required String password,
required String mnemonic,
@@ -54,7 +51,6 @@ abstract class CsMoneroInterface {
Future<WrappedWallet> getRestoredFromViewKeyWallet({
required String walletId,
- required CsCoin csCoin,
required String path,
required String password,
required String address,
@@ -153,17 +149,14 @@ abstract class CsMoneroInterface {
Future<void> thawOutput(WrappedWallet wallet, String keyImage);
List<String> getMoneroWordList(String language);
- List<String> getWowneroWordList(String language, int seedLength);
- int getHeightByDate(DateTime date, {required CsCoin csCoin});
+ int getHeightByDate(DateTime date);
- bool validateAddress(String address, int network, {required CsCoin csCoin});
+ bool validateAddress(String address, int network);
String getSeed(WrappedWallet wallet);
}
-enum CsCoin { monero, wownero }
-
// forwarding class
final class CsWalletListener {
CsWalletListener({
diff --git a/lib/wl_gen/interfaces/cs_wownero_interface.dart b/lib/wl_gen/interfaces/cs_wownero_interface.dart
new file mode 100644
index 0000000..2e317d9
--- /dev/null
+++ b/lib/wl_gen/interfaces/cs_wownero_interface.dart
@@ -0,0 +1,159 @@
+import '../../models/input.dart';
+import 'cs_monero_interface.dart';
+import 'cs_salvium_interface.dart' show WrappedWallet;
+
+export '../generated/cs_wownero_interface_impl.dart';
+
+abstract class CsWowneroInterface {
+ const CsWowneroInterface();
+
+ void setUseCsWowneroLoggerInternal(bool enable);
+
+ // tx prio forwarding
+ int getTxPriorityHigh();
+ int getTxPriorityMedium();
+ int getTxPriorityNormal();
+
+ bool walletExists(String path);
+
+ Future<int> estimateFee(
+ int rate,
+ BigInt amount, {
+ required WrappedWallet wallet,
+ });
+
+ Future<WrappedWallet> loadWallet(
+ String walletId, {
+ required String path,
+ required String password,
+ });
+
+ String getAddress(
+ WrappedWallet wallet, {
+ int accountIndex = 0,
+ int addressIndex = 0,
+ });
+
+ Future<WrappedWallet> getCreatedWallet({
+ required String path,
+ required String password,
+ required int wordCount,
+ required String seedOffset,
+ });
+
+ Future<WrappedWallet> getRestoredWallet({
+ required String walletId,
+ required String path,
+ required String password,
+ required String mnemonic,
+ required String seedOffset,
+ int height = 0,
+ });
+
+ Future<WrappedWallet> getRestoredFromViewKeyWallet({
+ required String walletId,
+ required String path,
+ required String password,
+ required String address,
+ required String privateViewKey,
+ int height = 0,
+ });
+
+ String getTxKey(WrappedWallet wallet, String txid);
+
+ Future<void> save(WrappedWallet wallet);
+
+ String getPublicViewKey(WrappedWallet wallet);
+ String getPrivateViewKey(WrappedWallet wallet);
+ String getPublicSpendKey(WrappedWallet wallet);
+ String getPrivateSpendKey(WrappedWallet wallet);
+
+ Future<bool> isSynced(WrappedWallet wallet);
+ void startSyncing(WrappedWallet wallet);
+ void stopSyncing(WrappedWallet wallet);
+
+ void startAutoSaving(WrappedWallet wallet);
+ void stopAutoSaving(WrappedWallet wallet);
+
+ bool hasListeners(WrappedWallet wallet);
+ void addListener(WrappedWallet wallet, CsWalletListener listener);
+ void startListeners(WrappedWallet wallet);
+ void stopListeners(WrappedWallet wallet);
+
+ Future<bool> rescanBlockchain(WrappedWallet wallet);
+ Future<bool> isConnectedToDaemon(WrappedWallet wallet);
+
+ int getRefreshFromBlockHeight(WrappedWallet wallet);
+ void setRefreshFromBlockHeight(WrappedWallet wallet, int height);
+
+ Future<void> connect(
+ WrappedWallet wallet, {
+ required String daemonAddress,
+ required bool trusted,
+ String? daemonUsername,
+ String? daemonPassword,
+ bool useSSL = false,
+ bool isLightWallet = false,
+ String? socksProxyAddress,
+ });
+
+ Future<List<String>> getAllTxids(
+ WrappedWallet wallet, {
+ bool refresh = false,
+ });
+
+ BigInt? getBalance(WrappedWallet wallet, {int accountIndex = 0});
+ BigInt? getUnlockedBalance(WrappedWallet wallet, {int accountIndex = 0});
+
+ Future<List<CsTransaction>> getAllTxs(
+ WrappedWallet wallet, {
+ bool refresh = false,
+ });
+
+ Future<List<CsTransaction>> getTxs(
+ WrappedWallet wallet, {
+ required Set<String> txids,
+ bool refresh = false,
+ });
+
+ Future<CsPendingTransaction> createTx(
+ WrappedWallet wallet, {
+ required CsRecipient output,
+ required int priority,
+ required bool sweep,
+ List<StandardInput>? preferredInputs,
+ required int accountIndex,
+ required int minConfirms,
+ required int currentHeight,
+ });
+
+ Future<CsPendingTransaction> createTxMultiDest(
+ WrappedWallet wallet, {
+ required List<CsRecipient> outputs,
+ required int priority,
+ required bool sweep,
+ List<StandardInput>? preferredInputs,
+ required int accountIndex,
+ required int minConfirms,
+ required int currentHeight,
+ });
+
+ Future<void> commitTx(WrappedWallet wallet, CsPendingTransaction tx);
+
+ Future<List<CsOutput>> getOutputs(
+ WrappedWallet wallet, {
+ bool refresh = false,
+ bool includeSpent = false,
+ });
+
+ Future<void> freezeOutput(WrappedWallet wallet, String keyImage);
+ Future<void> thawOutput(WrappedWallet wallet, String keyImage);
+
+ List<String> getWowneroWordList(String language, int seedLength);
+
+ int getHeightByDate(DateTime date);
+
+ bool validateAddress(String address, int network);
+
+ String getSeed(WrappedWallet wallet);
+}
diff --git a/pubspec.lock b/pubspec.lock
index 67468d4..efbd5ce 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -439,74 +439,74 @@ packages:
dependency: "direct main"
description:
name: cs_monero
- sha256: "7cfbcd25135a0710ad096678160d7668abed8979838165f06975adbe6bbec215"
+ sha256: "2bc89f862b4a4bc5312999a35d266db035d2e8760736662e148f07d2ab36e43d"
url: "https://pub.dev"
source: hosted
- version: "1.1.1"
+ version: "2.0.0"
cs_monero_flutter_libs:
dependency: "direct main"
description:
name: cs_monero_flutter_libs
- sha256: "47d716adc7b668653e359df785702d1213245f2fab6efa930a70b87e4cba23ae"
+ sha256: "759272ed87908572c0b7bb47edae2c49744bb981a632fc6156526a33a4d17f74"
url: "https://pub.dev"
source: hosted
- version: "1.1.1"
+ version: "2.0.0"
cs_monero_flutter_libs_android:
dependency: transitive
description:
name: cs_monero_flutter_libs_android
- sha256: "4b9d1117e63352d27bd0cb7115fc20d6212bd02a7e6ec3cd8ab2b37fddfb21eb"
+ sha256: eafe9b72370f92135e94ba8f25acf8776300d0442e0fdedc31c0ce715aa80daa
url: "https://pub.dev"
source: hosted
- version: "1.1.0"
+ version: "1.1.1"
cs_monero_flutter_libs_android_arm64_v8a:
dependency: transitive
description:
name: cs_monero_flutter_libs_android_arm64_v8a
- sha256: cbb8704dcc1d02581a820b99188c97acaa140eaefedee9ce7d17910e24e5530f
+ sha256: d754cc1effdefdf8d1bf016fe69288f5a9cdde83269b5c153d4ac191dd18fa30
url: "https://pub.dev"
source: hosted
- version: "1.1.0"
+ version: "1.2.0"
cs_monero_flutter_libs_android_armeabi_v7a:
dependency: transitive
description:
name: cs_monero_flutter_libs_android_armeabi_v7a
- sha256: dc276544b169553a8a63855beaa6c2cf8180af68fb335ab1b629f2fa9370e123
+ sha256: "21c00dcbd506a737750dd228a36395b3f6a9b142ecc9032ee60a746fca80d731"
url: "https://pub.dev"
source: hosted
- version: "1.1.0"
+ version: "1.2.0"
cs_monero_flutter_libs_android_x86_64:
dependency: transitive
description:
name: cs_monero_flutter_libs_android_x86_64
- sha256: fb02563c07d3fb4804925ec66446e26389ca2d92659493b72a6cf106765fa321
+ sha256: d13fb62be52f44d0fa3aedeaabc33809284c4452728f4dfb9b4147038cae6fa2
url: "https://pub.dev"
source: hosted
- version: "1.1.0"
+ version: "1.2.0"
cs_monero_flutter_libs_ios:
dependency: transitive
description:
name: cs_monero_flutter_libs_ios
- sha256: "6fbe1590b0633f42c906dfada1db8e3ce4f8899eae8728a4bb9b696dc7fb5155"
+ sha256: "04673ca9a46f77ad0493f9dcd1cfe8e93ceacb78a2c46a0e733d0c6925231552"
url: "https://pub.dev"
source: hosted
- version: "1.1.1"
+ version: "1.2.0"
cs_monero_flutter_libs_linux:
dependency: transitive
description:
name: cs_monero_flutter_libs_linux
- sha256: "394a58f4efefd3857f1f3da03f21e33f1c2ca5141936db7a843e77286ffaa89e"
+ sha256: "4390b77d529cae362ed14ba4b5906c4e0f9bb4da5f3c8a0963f27c3e9c3197ff"
url: "https://pub.dev"
source: hosted
- version: "1.1.0"
+ version: "1.2.0"
cs_monero_flutter_libs_macos:
dependency: transitive
description:
name: cs_monero_flutter_libs_macos
- sha256: e00616ab86a0ea18b3360dbae8d862b83fa450b1a83355647e34e8c64696a6c7
+ sha256: "1cf88d9d04f327b577d245b579b15e75ad4c43ab7f1996bd369ddc0fa84aec53"
url: "https://pub.dev"
source: hosted
- version: "1.1.0"
+ version: "1.2.0"
cs_monero_flutter_libs_platform_interface:
dependency: transitive
description:
@@ -519,10 +519,10 @@ packages:
dependency: transitive
description:
name: cs_monero_flutter_libs_windows
- sha256: de265ed544a4edb9e778e88b56ccee098a1ad38cd4c4536a985f05d3dde95a23
+ sha256: "3be0fe15bdcb6d1619b70d8dde52eaa02fdd028bd3cc522a4c472742f72a09a7"
url: "https://pub.dev"
source: hosted
- version: "1.1.0"
+ version: "1.2.0"
cs_salvium:
dependency: "direct main"
description:
@@ -535,10 +535,10 @@ packages:
dependency: "direct main"
description:
name: cs_salvium_flutter_libs
- sha256: "2aea1bbb6e6b69ac0a8e4dace2efc50507a10651ad9bec862f6a5ccd06a76578"
+ sha256: "05a9f9e3f8cb539a310419d49270492e84d0f89bccb4c31512c854b1fe1f1c5f"
url: "https://pub.dev"
source: hosted
- version: "2.0.0"
+ version: "2.0.1"
cs_salvium_flutter_libs_android:
dependency: transitive
description:
@@ -575,10 +575,10 @@ packages:
dependency: transitive
description:
name: cs_salvium_flutter_libs_ios
- sha256: "4dc2447255f1c8997b6d26e72577e30ceab7f4622620549dc9de9eb8dccac35c"
+ sha256: aa474e7da65ba36e23afc4936ffbe39328808619fbdac44dacad9aa3aafb1b08
url: "https://pub.dev"
source: hosted
- version: "2.0.0"
+ version: "2.0.1"
cs_salvium_flutter_libs_linux:
dependency: transitive
description:
@@ -591,10 +591,10 @@ packages:
dependency: transitive
description:
name: cs_salvium_flutter_libs_macos
- sha256: "428e4eead3d507112cb6f0b70f69bc43430b3db60f0b4d731e0d6a6fab0b69bb"
+ sha256: "988077e7affc6443a1b665bac6df3b39269cc1352375cb805bd6d26aac82b46f"
url: "https://pub.dev"
source: hosted
- version: "2.0.0"
+ version: "2.0.1"
cs_salvium_flutter_libs_platform_interface:
dependency: transitive
description:
@@ -611,6 +611,94 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
+ cs_wownero:
+ dependency: "direct main"
+ description:
+ name: cs_wownero
+ sha256: "9ff7a6be0f4524c6b9e5ca1d223df98e9455c7fe3b06f0b519280a175795e925"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.0"
+ cs_wownero_flutter_libs:
+ dependency: "direct main"
+ description:
+ name: cs_wownero_flutter_libs
+ sha256: "68b6c682c6cce0915418aa6b01b137ef77652932f8b3fb1bc868bfd20d15c562"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.0"
+ cs_wownero_flutter_libs_android:
+ dependency: transitive
+ description:
+ name: cs_wownero_flutter_libs_android
+ sha256: "14fe0666999d078bcd91ca499a9e9395dd270211eedb2250c533cbd036cb328b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ cs_wownero_flutter_libs_android_arm64_v8a:
+ dependency: transitive
+ description:
+ name: cs_wownero_flutter_libs_android_arm64_v8a
+ sha256: "19f7e17ce7adf4615685f92b106c7f588dee80bb4768931c2505d2761a9fa06c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ cs_wownero_flutter_libs_android_armeabi_v7a:
+ dependency: transitive
+ description:
+ name: cs_wownero_flutter_libs_android_armeabi_v7a
+ sha256: "1b7dc845674c938259dcbce6b9d6e6c305c98c2ff9b83803b00ea0f1268dfb28"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ cs_wownero_flutter_libs_android_x86_64:
+ dependency: transitive
+ description:
+ name: cs_wownero_flutter_libs_android_x86_64
+ sha256: c318ce80ef418d53aeef3698c89c0497394269311f8c5b75f160e0f81610f9d9
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ cs_wownero_flutter_libs_ios:
+ dependency: transitive
+ description:
+ name: cs_wownero_flutter_libs_ios
+ sha256: f80dd0164902565d4fd0058da5be446a3ea5eaee2ca8651289a35d7fc93f3ca4
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ cs_wownero_flutter_libs_linux:
+ dependency: transitive
+ description:
+ name: cs_wownero_flutter_libs_linux
+ sha256: b60a700f0ef676405bfa67793fb7431f7d2ffbaccc1f6ad69001da0c2e0a07b0
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ cs_wownero_flutter_libs_macos:
+ dependency: transitive
+ description:
+ name: cs_wownero_flutter_libs_macos
+ sha256: e703975e6a6f698b01e07b238953547391faa4e930f09733d01f1346ee788fc7
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ cs_wownero_flutter_libs_platform_interface:
+ dependency: transitive
+ description:
+ name: cs_wownero_flutter_libs_platform_interface
+ sha256: "6a3bda9bcf5a904b36cbd0817e7ae8b7a64693e6f532f1783513e93c64436e6f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.0"
+ cs_wownero_flutter_libs_windows:
+ dependency: transitive
+ description:
+ name: cs_wownero_flutter_libs_windows
+ sha256: fe7485863a6e83e31581cef36c62d3507a9ea36c73d56842b4fee059f349cb49
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
csslib:
dependency: transitive
description:
diff --git a/scripts/app_config/configure_stack_wallet.sh b/scripts/app_config/configure_stack_wallet.sh
index cbcead4..6a4ad28 100755
--- a/scripts/app_config/configure_stack_wallet.sh
+++ b/scripts/app_config/configure_stack_wallet.sh
@@ -28,6 +28,7 @@ dart "${APP_PROJECT_ROOT_DIR}/tool/process_pubspec_deps.dart" \
MWC \
MWEBD \
XMR \
+ WOW \
SAL \
TOR \
EPIC \
@@ -41,6 +42,7 @@ dart "${APP_PROJECT_ROOT_DIR}/tool/gen_interfaces.dart" \
MWC \
MWEBD \
XMR \
+ WOW \
SAL \
TOR \
EPIC \
diff --git a/scripts/app_config/templates/pubspec.template.yaml b/scripts/app_config/templates/pubspec.template.yaml
index 5bbaf75..e76d533 100644
--- a/scripts/app_config/templates/pubspec.template.yaml
+++ b/scripts/app_config/templates/pubspec.template.yaml
@@ -61,13 +61,18 @@ dependencies:
# %%END_ENABLE_TOR%%
# %%ENABLE_XMR%%
-# cs_monero: 1.1.1
-# cs_monero_flutter_libs: 1.1.1
+# cs_monero: 2.0.0
+# cs_monero_flutter_libs: 2.0.0
# %%END_ENABLE_XMR%%
+# %%ENABLE_WOW%%
+# cs_wownero: 2.0.0
+# cs_wownero_flutter_libs: 2.0.0
+# %%END_ENABLE_WOW%%
+
# %%ENABLE_SAL%%
# cs_salvium: ^2.0.0
-# cs_salvium_flutter_libs: ^2.0.0
+# cs_salvium_flutter_libs: ^2.0.1
# %%END_ENABLE_SAL%%
# %%ENABLE_MWEBD%%
diff --git a/tool/wl_templates/WOW_cs_wownero_interface_impl.template.dart b/tool/wl_templates/WOW_cs_wownero_interface_impl.template.dart
new file mode 100644
index 0000000..51c5ef0
--- /dev/null
+++ b/tool/wl_templates/WOW_cs_wownero_interface_impl.template.dart
@@ -0,0 +1,510 @@
+//ON
+import 'package:cs_wownero/cs_wownero.dart' as lib_wownero;
+import 'package:cs_wownero/src/deprecated/get_height_by_date.dart'
+ as cs_wownero_deprecated;
+import 'package:cs_wownero/src/ffi_bindings/wownero_wallet_bindings.dart'
+ as wow_wallet_ffi;
+
+//END_ON
+import '../../models/input.dart';
+import '../interfaces/cs_monero_interface.dart';
+import '../interfaces/cs_salvium_interface.dart' show WrappedWallet;
+import '../interfaces/cs_wownero_interface.dart';
+
+CsWowneroInterface get csWownero => _getInterface();
+
+//OFF
+CsWowneroInterface _getInterface() => throw Exception("WOW not enabled!");
+
+//END_OFF
+//ON
+CsWowneroInterface _getInterface() => const _CsWowneroInterfaceImpl();
+
+class _CsWowneroInterfaceImpl extends CsWowneroInterface {
+ const _CsWowneroInterfaceImpl();
+
+ @override
+ void setUseCsWowneroLoggerInternal(bool enable) =>
+ lib_wownero.Logging.useLogger = enable;
+
+ @override
+ bool walletExists(String path) =>
+ lib_wownero.WowneroWallet.isWalletExist(path);
+
+ @override
+ Future<int> estimateFee(
+ int rate,
+ BigInt amount, {
+ required WrappedWallet wallet,
+ }) {
+ lib_wownero.TransactionPriority priority;
+ switch (rate) {
+ case 1:
+ priority = lib_wownero.TransactionPriority.low;
+ break;
+ case 2:
+ priority = lib_wownero.TransactionPriority.medium;
+ break;
+ case 3:
+ priority = lib_wownero.TransactionPriority.high;
+ break;
+ case 4:
+ priority = lib_wownero.TransactionPriority.last;
+ break;
+ case 0:
+ default:
+ priority = lib_wownero.TransactionPriority.normal;
+ break;
+ }
+
+ return wallet.get<lib_wownero.Wallet>().estimateFee(
+ priority,
+ amount.toInt(),
+ );
+ }
+
+ @override
+ Future<WrappedWallet> loadWallet(
+ String walletId, {
+ required String path,
+ required String password,
+ }) async {
+ return WrappedWallet(
+ await lib_wownero.WowneroWallet.loadWallet(
+ path: path,
+ password: password,
+ ),
+ );
+ }
+
+ @override
+ int getTxPriorityHigh() => lib_wownero.TransactionPriority.high.value;
+
+ @override
+ int getTxPriorityMedium() => lib_wownero.TransactionPriority.medium.value;
+
+ @override
+ int getTxPriorityNormal() => lib_wownero.TransactionPriority.normal.value;
+
+ @override
+ String getAddress(
+ WrappedWallet wallet, {
+ int accountIndex = 0,
+ int addressIndex = 0,
+ }) => wallet
+ .get<lib_wownero.Wallet>()
+ .getAddress(accountIndex: accountIndex, addressIndex: addressIndex)
+ .value;
+
+ @override
+ Future<WrappedWallet> getCreatedWallet({
+ required String path,
+ required String password,
+ required int wordCount,
+ required String seedOffset,
+ }) async {
+ final type = switch (wordCount) {
+ 16 => lib_wownero.WowneroSeedType.sixteen,
+ 25 => lib_wownero.WowneroSeedType.twentyFive,
+ _ => throw Exception("Invalid mnemonic word count: $wordCount"),
+ };
+
+ final wallet = await lib_wownero.WowneroWallet.create(
+ path: path,
+ password: password,
+ seedType: type,
+ seedOffset: seedOffset,
+ );
+
+ return WrappedWallet(wallet);
+ }
+
+ @override
+ Future<WrappedWallet> getRestoredWallet({
+ required String walletId,
+
+ required String path,
+ required String password,
+ required String mnemonic,
+ required String seedOffset,
+ int height = 0,
+ }) async {
+ return WrappedWallet(
+ await lib_wownero.WowneroWallet.restoreWalletFromSeed(
+ path: path,
+ password: password,
+ seed: mnemonic,
+ restoreHeight: height,
+ seedOffset: seedOffset,
+ ),
+ );
+ }
+
+ @override
+ Future<WrappedWallet> getRestoredFromViewKeyWallet({
+ required String walletId,
+
+ required String path,
+ required String password,
+ required String address,
+ required String privateViewKey,
+ int height = 0,
+ }) async {
+ return WrappedWallet(
+ await lib_wownero.WowneroWallet.createViewOnlyWallet(
+ path: path,
+ password: password,
+ address: address,
+ viewKey: privateViewKey,
+ restoreHeight: height,
+ ),
+ );
+ }
+
+ @override
+ String getTxKey(WrappedWallet wallet, String txid) =>
+ wallet.get<lib_wownero.Wallet>().getTxKey(txid);
+
+ @override
+ Future<void> save(WrappedWallet wallet) =>
+ wallet.get<lib_wownero.Wallet>().save();
+
+ @override
+ String getPublicViewKey(WrappedWallet wallet) =>
+ wallet.get<lib_wownero.Wallet>().getPublicViewKey();
+
+ @override
+ String getPrivateViewKey(WrappedWallet wallet) =>
+ wallet.get<lib_wownero.Wallet>().getPrivateViewKey();
+
+ @override
+ String getPublicSpendKey(WrappedWallet wallet) =>
+ wallet.get<lib_wownero.Wallet>().getPublicSpendKey();
+
+ @override
+ String getPrivateSpendKey(WrappedWallet wallet) =>
+ wallet.get<lib_wownero.Wallet>().getPrivateSpendKey();
+
+ @override
+ Future<bool> isSynced(WrappedWallet wallet) =>
+ wallet.get<lib_wownero.Wallet>().isSynced();
+
+ @override
+ void startSyncing(WrappedWallet wallet) =>
+ wallet.get<lib_wownero.Wallet>().startSyncing();
+
+ @override
+ void stopSyncing(WrappedWallet wallet) =>
+ wallet.get<lib_wownero.Wallet>().stopSyncing();
+
+ @override
+ void startAutoSaving(WrappedWallet wallet) =>
+ wallet.get<lib_wownero.Wallet>().startAutoSaving();
+
+ @override
+ void stopAutoSaving(WrappedWallet wallet) =>
+ wallet.get<lib_wownero.Wallet>().stopAutoSaving();
+
+ @override
+ bool hasListeners(WrappedWallet wallet) =>
+ wallet.get<lib_wownero.Wallet>().getListeners().isNotEmpty;
+
+ @override
+ void addListener(WrappedWallet wallet, CsWalletListener listener) =>
+ wallet.get<lib_wownero.Wallet>().addListener(
+ lib_wownero.WalletListener(
+ onSyncingUpdate: listener.onSyncingUpdate,
+ onNewBlock: listener.onNewBlock,
+ onBalancesChanged: listener.onBalancesChanged,
+ onError: listener.onError,
+ ),
+ );
+
+ @override
+ void startListeners(WrappedWallet wallet) =>
+ wallet.get<lib_wownero.Wallet>().startListeners();
+
+ @override
+ void stopListeners(WrappedWallet wallet) =>
+ wallet.get<lib_wownero.Wallet>().stopListeners();
+
+ @override
+ int getRefreshFromBlockHeight(WrappedWallet wallet) =>
+ wallet.get<lib_wownero.Wallet>().getRefreshFromBlockHeight();
+
+ @override
+ void setRefreshFromBlockHeight(WrappedWallet wallet, int height) =>
+ wallet.get<lib_wownero.Wallet>().setRefreshFromBlockHeight(height);
+
+ @override
+ Future<bool> rescanBlockchain(WrappedWallet wallet) =>
+ wallet.get<lib_wownero.Wallet>().rescanBlockchain();
+
+ @override
+ Future<bool> isConnectedToDaemon(WrappedWallet wallet) =>
+ wallet.get<lib_wownero.Wallet>().isConnectedToDaemon();
+
+ @override
+ Future<void> connect(
+ WrappedWallet wallet, {
+ required String daemonAddress,
+ required bool trusted,
+ String? daemonUsername,
+ String? daemonPassword,
+ bool useSSL = false,
+ bool isLightWallet = false,
+ String? socksProxyAddress,
+ }) async {
+ await wallet.get<lib_wownero.Wallet>().connect(
+ daemonAddress: daemonAddress,
+ trusted: trusted,
+ daemonUsername: daemonUsername,
+ daemonPassword: daemonPassword,
+ useSSL: useSSL,
+ socksProxyAddress: socksProxyAddress,
+ isLightWallet: isLightWallet,
+ );
+ }
+
+ @override
+ Future<List<String>> getAllTxids(
+ WrappedWallet wallet, {
+ bool refresh = false,
+ }) => wallet.get<lib_wownero.Wallet>().getAllTxids(refresh: refresh);
+
+ @override
+ BigInt? getBalance(WrappedWallet wallet, {int accountIndex = 0}) =>
+ wallet.get<lib_wownero.Wallet>().getBalance(accountIndex: accountIndex);
+
+ @override
+ BigInt? getUnlockedBalance(WrappedWallet wallet, {int accountIndex = 0}) =>
+ wallet.get<lib_wownero.Wallet>().getUnlockedBalance(
+ accountIndex: accountIndex,
+ );
+
+ @override
+ Future<List<CsTransaction>> getAllTxs(
+ WrappedWallet wallet, {
+ bool refresh = false,
+ }) async {
+ final transactions = await wallet.get<lib_wownero.Wallet>().getAllTxs(
+ refresh: refresh,
+ );
+ return transactions
+ .map(
+ (e) => CsTransaction(
+ displayLabel: e.displayLabel,
+ description: e.description,
+ fee: e.fee,
+ confirmations: e.confirmations,
+ blockHeight: e.blockHeight,
+ accountIndex: e.accountIndex,
+ addressIndexes: e.addressIndexes,
+ paymentId: e.paymentId,
+ amount: e.amount,
+ isSpend: e.isSpend,
+ hash: e.hash,
+ key: e.key,
+ timeStamp: e.timeStamp,
+ minConfirms: e.minConfirms.value,
+ ),
+ )
+ .toList();
+ }
+
+ @override
+ Future<List<CsTransaction>> getTxs(
+ WrappedWallet wallet, {
+ required Set<String> txids,
+ bool refresh = false,
+ }) async {
+ final transactions = await wallet.get<lib_wownero.Wallet>().getTxs(
+ txids: txids,
+ refresh: refresh,
+ );
+ return transactions
+ .map(
+ (e) => CsTransaction(
+ displayLabel: e.displayLabel,
+ description: e.description,
+ fee: e.fee,
+ confirmations: e.confirmations,
+ blockHeight: e.blockHeight,
+ accountIndex: e.accountIndex,
+ addressIndexes: e.addressIndexes,
+ paymentId: e.paymentId,
+ amount: e.amount,
+ isSpend: e.isSpend,
+ hash: e.hash,
+ key: e.key,
+ timeStamp: e.timeStamp,
+ minConfirms: e.minConfirms.value,
+ ),
+ )
+ .toList();
+ }
+
+ @override
+ Future<CsPendingTransaction> createTx(
+ WrappedWallet wallet, {
+ required CsRecipient output,
+ required int priority,
+ required bool sweep,
+ List<StandardInput>? preferredInputs,
+ required int accountIndex,
+ required int minConfirms,
+ required int currentHeight,
+ }) async {
+ final pending = await wallet.get<lib_wownero.Wallet>().createTx(
+ output: lib_wownero.Recipient(
+ address: output.address,
+ amount: output.amount,
+ ),
+ paymentId: "",
+ sweep: sweep,
+ priority: lib_wownero.TransactionPriority.values.firstWhere(
+ (e) => e.value == priority,
+ ),
+ preferredInputs: preferredInputs
+ ?.map(
+ (e) => lib_wownero.Output(
+ address: e.address!,
+ hash: e.utxo.txid,
+ keyImage: e.utxo.keyImage!,
+ value: e.value,
+ isFrozen: e.utxo.isBlocked,
+ isUnlocked:
+ e.utxo.blockHeight != null &&
+ (currentHeight - (e.utxo.blockHeight ?? 0)) >= minConfirms,
+ height: e.utxo.blockHeight ?? 0,
+ vout: e.utxo.vout,
+ spent: e.utxo.used ?? false,
+ spentHeight: null, // doesn't matter here
+ coinbase: e.utxo.isCoinbase,
+ ),
+ )
+ .toList(),
+ accountIndex: accountIndex,
+ );
+
+ return CsPendingTransaction(
+ pending,
+ pending.amount,
+ pending.fee,
+ pending.txid,
+ );
+ }
+
+ @override
+ Future<CsPendingTransaction> createTxMultiDest(
+ WrappedWallet wallet, {
+ required List<CsRecipient> outputs,
+ required int priority,
+ required bool sweep,
+ List<StandardInput>? preferredInputs,
+ required int accountIndex,
+ required int minConfirms,
+ required int currentHeight,
+ }) async {
+ final pending = await wallet.get<lib_wownero.Wallet>().createTxMultiDest(
+ outputs: outputs
+ .map(
+ (e) => lib_wownero.Recipient(address: e.address, amount: e.amount),
+ )
+ .toList(),
+ paymentId: "",
+ sweep: sweep,
+ priority: lib_wownero.TransactionPriority.values.firstWhere(
+ (e) => e.value == priority,
+ ),
+ preferredInputs: preferredInputs
+ ?.map(
+ (e) => lib_wownero.Output(
+ address: e.address!,
+ hash: e.utxo.txid,
+ keyImage: e.utxo.keyImage!,
+ value: e.value,
+ isFrozen: e.utxo.isBlocked,
+ isUnlocked:
+ e.utxo.blockHeight != null &&
+ (currentHeight - (e.utxo.blockHeight ?? 0)) >= minConfirms,
+ height: e.utxo.blockHeight ?? 0,
+ vout: e.utxo.vout,
+ spent: e.utxo.used ?? false,
+ spentHeight: null, // doesn't matter here
+ coinbase: e.utxo.isCoinbase,
+ ),
+ )
+ .toList(),
+ accountIndex: accountIndex,
+ );
+
+ return CsPendingTransaction(
+ pending,
+ pending.amount,
+ pending.fee,
+ pending.txid,
+ );
+ }
+
+ @override
+ Future<void> commitTx(WrappedWallet wallet, CsPendingTransaction tx) => wallet
+ .get<lib_wownero.Wallet>()
+ .commitTx(tx.value as lib_wownero.PendingTransaction);
+
+ @override
+ Future<List<CsOutput>> getOutputs(
+ WrappedWallet wallet, {
+ bool refresh = false,
+ bool includeSpent = false,
+ }) async {
+ final outputs = await wallet.get<lib_wownero.Wallet>().getOutputs(
+ includeSpent: includeSpent,
+ refresh: refresh,
+ );
+
+ return outputs
+ .map(
+ (e) => CsOutput(
+ address: e.address,
+ hash: e.hash,
+ keyImage: e.keyImage,
+ value: e.value,
+ isFrozen: e.isFrozen,
+ isUnlocked: e.isUnlocked,
+ height: e.height,
+ spentHeight: e.spentHeight,
+ vout: e.vout,
+ spent: e.spent,
+ coinbase: e.coinbase,
+ ),
+ )
+ .toList();
+ }
+
+ @override
+ Future<void> freezeOutput(WrappedWallet wallet, String keyImage) =>
+ wallet.get<lib_wownero.Wallet>().freezeOutput(keyImage);
+
+ @override
+ Future<void> thawOutput(WrappedWallet wallet, String keyImage) =>
+ wallet.get<lib_wownero.Wallet>().thawOutput(keyImage);
+
+ @override
+ List<String> getWowneroWordList(String language, int seedLength) =>
+ lib_wownero.getWowneroWordList(language, seedWordsLength: seedLength);
+
+ @override
+ int getHeightByDate(DateTime date) =>
+ cs_wownero_deprecated.getWowneroHeightByDate(date: date);
+
+ @override
+ bool validateAddress(String address, int network) =>
+ wow_wallet_ffi.validateAddress(address, network);
+
+ @override
+ String getSeed(WrappedWallet wallet) =>
+ wallet.get<lib_wownero.Wallet>().getSeed();
+}
+
+//END_ON
diff --git a/tool/wl_templates/XMR_cs_monero_interface_impl.template.dart b/tool/wl_templates/XMR_cs_monero_interface_impl.template.dart
index af0016e..f1d782c 100644
--- a/tool/wl_templates/XMR_cs_monero_interface_impl.template.dart
+++ b/tool/wl_templates/XMR_cs_monero_interface_impl.template.dart
@@ -4,8 +4,6 @@ import 'package:cs_monero/src/deprecated/get_height_by_date.dart'
as cs_monero_deprecated;
import 'package:cs_monero/src/ffi_bindings/monero_wallet_bindings.dart'
as xmr_wallet_ffi;
-import 'package:cs_monero/src/ffi_bindings/wownero_wallet_bindings.dart'
- as wow_wallet_ffi;
//END_ON
import '../../models/input.dart';
@@ -15,7 +13,7 @@ import '../interfaces/cs_salvium_interface.dart' show WrappedWallet;
CsMoneroInterface get csMonero => _getInterface();
//OFF
-CsMoneroInterface _getInterface() => throw Exception("XMR/WOW not enabled!");
+CsMoneroInterface _getInterface() => throw Exception("XMR not enabled!");
//END_OFF
//ON
@@ -29,10 +27,7 @@ class _CsMoneroInterfaceImpl extends CsMoneroInterface {
lib_monero.Logging.useLogger = enable;
@override
- bool walletExists(String path, {required CsCoin csCoin}) => switch (csCoin) {
- CsCoin.monero => lib_monero.MoneroWallet.isWalletExist(path),
- CsCoin.wownero => lib_monero.WowneroWallet.isWalletExist(path),
- };
+ bool walletExists(String path) => lib_monero.MoneroWallet.isWalletExist(path);
@override
Future<int> estimateFee(
@@ -69,21 +64,12 @@ class _CsMoneroInterfaceImpl extends CsMoneroInterface {
@override
Future<WrappedWallet> loadWallet(
String walletId, {
- required CsCoin csCoin,
required String path,
required String password,
}) async {
- return WrappedWallet(switch (csCoin) {
- CsCoin.monero => await lib_monero.MoneroWallet.loadWallet(
- path: path,
- password: password,
- ),
-
- CsCoin.wownero => await lib_monero.WowneroWallet.loadWallet(
- path: path,
- password: password,
- ),
- });
+ return WrappedWallet(
+ await lib_monero.MoneroWallet.loadWallet(path: path, password: password),
+ );
}
@override
@@ -107,45 +93,23 @@ class _CsMoneroInterfaceImpl extends CsMoneroInterface {
@override
Future<WrappedWallet> getCreatedWallet({
- required CsCoin csCoin,
required String path,
required String password,
required int wordCount,
required String seedOffset,
}) async {
- final lib_monero.Wallet wallet;
-
- switch (csCoin) {
- case CsCoin.monero:
- final type = switch (wordCount) {
- 16 => lib_monero.MoneroSeedType.sixteen,
- 25 => lib_monero.MoneroSeedType.twentyFive,
- _ => throw Exception("Invalid mnemonic word count: $wordCount"),
- };
-
- wallet = await lib_monero.MoneroWallet.create(
- path: path,
- password: password,
- seedType: type,
- seedOffset: seedOffset,
- );
- break;
-
- case CsCoin.wownero:
- final type = switch (wordCount) {
- 16 => lib_monero.WowneroSeedType.sixteen,
- 25 => lib_monero.WowneroSeedType.twentyFive,
- _ => throw Exception("Invalid mnemonic word count: $wordCount"),
- };
-
- wallet = await lib_monero.WowneroWallet.create(
- path: path,
- password: password,
- seedType: type,
- seedOffset: seedOffset,
- );
- break;
- }
+ final type = switch (wordCount) {
+ 16 => lib_monero.MoneroSeedType.sixteen,
+ 25 => lib_monero.MoneroSeedType.twentyFive,
+ _ => throw Exception("Invalid mnemonic word count: $wordCount"),
+ };
+
+ final wallet = await lib_monero.MoneroWallet.create(
+ path: path,
+ password: password,
+ seedType: type,
+ seedOffset: seedOffset,
+ );
return WrappedWallet(wallet);
}
@@ -153,59 +117,41 @@ class _CsMoneroInterfaceImpl extends CsMoneroInterface {
@override
Future<WrappedWallet> getRestoredWallet({
required String walletId,
- required CsCoin csCoin,
required String path,
required String password,
required String mnemonic,
required String seedOffset,
int height = 0,
}) async {
- return WrappedWallet(switch (csCoin) {
- CsCoin.monero => await lib_monero.MoneroWallet.restoreWalletFromSeed(
- path: path,
- password: password,
- seed: mnemonic,
- restoreHeight: height,
- seedOffset: seedOffset,
- ),
-
- CsCoin.wownero => await lib_monero.WowneroWallet.restoreWalletFromSeed(
+ return WrappedWallet(
+ await lib_monero.MoneroWallet.restoreWalletFromSeed(
path: path,
password: password,
seed: mnemonic,
restoreHeight: height,
seedOffset: seedOffset,
),
- });
+ );
}
@override
Future<WrappedWallet> getRestoredFromViewKeyWallet({
required String walletId,
- required CsCoin csCoin,
required String path,
required String password,
required String address,
required String privateViewKey,
int height = 0,
}) async {
- return WrappedWallet(switch (csCoin) {
- CsCoin.monero => await lib_monero.MoneroWallet.createViewOnlyWallet(
- path: path,
- password: password,
- address: address,
- viewKey: privateViewKey,
- restoreHeight: height,
- ),
-
- CsCoin.wownero => await lib_monero.WowneroWallet.createViewOnlyWallet(
+ return WrappedWallet(
+ await lib_monero.MoneroWallet.createViewOnlyWallet(
path: path,
password: password,
address: address,
viewKey: privateViewKey,
restoreHeight: height,
),
- });
+ );
}
@override
@@ -542,24 +488,12 @@ class _CsMoneroInterfaceImpl extends CsMoneroInterface {
lib_monero.getMoneroWordList(language);
@override
- List<String> getWowneroWordList(String language, int seedLength) =>
- lib_monero.getWowneroWordList(language, seedWordsLength: seedLength);
-
- @override
- int getHeightByDate(DateTime date, {required CsCoin csCoin}) =>
- switch (csCoin) {
- CsCoin.monero => cs_monero_deprecated.getMoneroHeightByDate(date: date),
- CsCoin.wownero => cs_monero_deprecated.getWowneroHeightByDate(
- date: date,
- ),
- };
+ int getHeightByDate(DateTime date) =>
+ cs_monero_deprecated.getMoneroHeightByDate(date: date);
@override
- bool validateAddress(String address, int network, {required CsCoin csCoin}) =>
- switch (csCoin) {
- CsCoin.monero => xmr_wallet_ffi.validateAddress(address, network),
- CsCoin.wownero => wow_wallet_ffi.validateAddress(address, network),
- };
+ bool validateAddress(String address, int network) =>
+ xmr_wallet_ffi.validateAddress(address, network);
@override
String getSeed(WrappedWallet wallet) =>
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.