What changed, and why it matters
This commit is a work-in-progress refactor that changes how the Mimblewimblecoin (MWC) wallet code talks to its underlying library. Instead of calling the library directly, it introduces a generated interface layer so the same wallet code can work whether or not the MWC feature is enabled at build time. There is no obvious security bug in the diff itself; it is a build-time feature-toggle and code-organization change.
No immediate security action is required. Reviewers should verify that the generated interface correctly forwards all arguments and return values, that the build-time gating does not accidentally leave the real `flutter_libmwc` dependency active when MWC is disabled, and that the generated files are reproducible and reviewed in CI artifacts.
Security signals we found
No direct security fix or vulnerability patch is visible in the diff.
The change is architectural: introducing a generated abstraction layer for coin-specific native library dependencies.
The MWC dependency is now gated by build-time markers in `pubspec.template.yaml`.
The generated interface file is excluded from static analysis (`analysis_options.yaml`) and git (`.gitignore`), which is normal for generated code but reduces auditability of the final artifact.
Evidence from the diff
The commit replaces direct imports and calls to package:flutter_libmwc/lib.dart in mimblewimblecoin_wallet.dart with a new generated abstraction lib/wl_gen/interfaces/libmwc_interface.dart. It adds two build tools (tool/gen_interfaces.dart and tool/process_pubspec_deps.dart) and a template (tool/wl_templates/MWC_libmwc_interface_impl.template.dart) that produce a conditional implementation. The configure script now enables/disables MWC and other coins in pubspec.yaml and generates the corresponding interface implementations. The wallet code also adapts to a slightly different transaction/message model shape (e.g., tx.messages?.messages becomes tx.messages?.length and tx.messages?.first.message).
Changed components
lib/wallets/wallet/impl/mimblewimblecoin_wallet.dartlib/wl_gen/interfaces/libmwc_interface.dartscripts/app_config/configure_stack_wallet.shscripts/app_config/templates/pubspec.template.yamltool/gen_interfaces.darttool/process_pubspec_deps.darttool/wl_templates/MWC_libmwc_interface_impl.template.dartInspect captured patch +829 / −91
diff --git a/.gitignore b/.gitignore
index 30fb5a6..e360da5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -112,3 +112,6 @@ crypto_plugins/cs_monero/built_outputs
crypto_plugins/cs_monero/build
crypto_plugins/*.diff
/devtools_options.yaml
+
+# generated interfaces
+lib/wl_gen/generated/
\ No newline at end of file
diff --git a/analysis_options.yaml b/analysis_options.yaml
index db030aa..1252b9a 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -18,6 +18,8 @@ analyzer:
- "crypto_plugins/**"
- "bin/cache/**"
- "lib/generated_plugin_registrant.dart"
+ - "lib/wl_gen/generated/**"
+ - '**/*.template.dart'
# For more information see:
# https://dart.dev/guides/language/analysis-options#enabling-additional-type-checks
diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart
index 8eec728..3850cb7 100644
--- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart
+++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart
@@ -4,9 +4,6 @@ import 'dart:io';
import 'package:decimal/decimal.dart';
import 'package:flutter/foundation.dart';
-import 'package:flutter_libmwc/lib.dart' as mimblewimblecoin;
-import 'package:flutter_libmwc/models/transaction.dart'
- as mimblewimblecoin_models;
import 'package:isar_community/isar.dart';
import 'package:mutex/mutex.dart';
import 'package:stack_wallet_backup/generate_password.dart';
@@ -33,6 +30,7 @@ import '../../../utilities/flutter_secure_storage_interface.dart';
import '../../../utilities/logger.dart';
import '../../../utilities/stack_file_system.dart';
import '../../../utilities/test_mwcmqs_connection.dart';
+import '../../../wl_gen/interfaces/libmwc_interface.dart';
import '../../crypto_currency/crypto_currency.dart';
import '../../models/tx_data.dart';
import '../intermediate/bip39_wallet.dart';
@@ -109,10 +107,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
if (password == null) {
throw Exception('Wallet password not found');
}
- final opened = await mimblewimblecoin.Libmwc.openWallet(
- config: config,
- password: password,
- );
+ final opened = await libMwc.openWallet(config: config, password: password);
await secureStorageInterface.write(
key: '${walletId}_wallet',
value: opened,
@@ -123,10 +118,11 @@ class MimblewimblecoinWallet extends Bip39Wallet {
/// Returns an empty String on success, error message on failure.
Future<String> cancelPendingTransactionAndPost(String txSlateId) async {
try {
- final String wallet =
- (await secureStorageInterface.read(key: '${walletId}_wallet'))!;
+ final String wallet = (await secureStorageInterface.read(
+ key: '${walletId}_wallet',
+ ))!;
- final result = await mimblewimblecoin.Libmwc.cancelTransaction(
+ final result = await libMwc.cancelTransaction(
wallet: wallet,
transactionId: txSlateId,
);
@@ -177,7 +173,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
final handle = await _ensureWalletOpen();
// Generate S1 slate JSON.
- final s1Json = await mimblewimblecoin.Libmwc.txInit(
+ final s1Json = await libMwc.txInit(
wallet: handle,
amount: amount.raw.toInt(),
minimumConfirmations:
@@ -187,7 +183,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
);
// Encode to slatepack.
- final encoded = await mimblewimblecoin.Libmwc.encodeSlatepack(
+ final encoded = await libMwc.encodeSlatepack(
slateJson: s1Json,
recipientAddress: recipientAddress,
encrypt: encrypt,
@@ -260,15 +256,12 @@ class MimblewimblecoinWallet extends Bip39Wallet {
final handle = await secureStorageInterface.read(
key: '${walletId}_wallet',
);
- final result =
- handle != null
- ? await mimblewimblecoin.Libmwc.decodeSlatepackWithWallet(
- wallet: handle,
- slatepack: slatepack,
- )
- : await mimblewimblecoin.Libmwc.decodeSlatepack(
- slatepack: slatepack,
- );
+ final result = handle != null
+ ? await libMwc.decodeSlatepackWithWallet(
+ wallet: handle,
+ slatepack: slatepack,
+ )
+ : await libMwc.decodeSlatepack(slatepack: slatepack);
return SlatepackDecodeResult(
success: true,
@@ -289,19 +282,19 @@ class MimblewimblecoinWallet extends Bip39Wallet {
final handle = await _ensureWalletOpen();
// Decode to get slate JSON and sender address.
- final decoded = await mimblewimblecoin.Libmwc.decodeSlatepackWithWallet(
+ final decoded = await libMwc.decodeSlatepackWithWallet(
wallet: handle,
slatepack: slatepack,
);
// Receive and get updated slate JSON.
- final received = await mimblewimblecoin.Libmwc.txReceiveDetailed(
+ final received = await libMwc.txReceiveDetailed(
wallet: handle,
slateJson: decoded.slateJson,
);
// Encode response back to sender if address available.
- final encoded = await mimblewimblecoin.Libmwc.encodeSlatepack(
+ final encoded = await libMwc.encodeSlatepack(
slateJson: received.slateJson,
recipientAddress: decoded.senderAddress,
encrypt: decoded.senderAddress != null,
@@ -328,13 +321,13 @@ class MimblewimblecoinWallet extends Bip39Wallet {
final handle = await _ensureWalletOpen();
// Decode to get slate JSON.
- final decoded = await mimblewimblecoin.Libmwc.decodeSlatepackWithWallet(
+ final decoded = await libMwc.decodeSlatepackWithWallet(
wallet: handle,
slatepack: slatepack,
);
// Finalize transaction.
- final finalized = await mimblewimblecoin.Libmwc.txFinalize(
+ final finalized = await libMwc.txFinalize(
wallet: handle,
slateJson: decoded.slateJson,
);
@@ -358,7 +351,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
final wallet = await secureStorageInterface.read(
key: '${walletId}_wallet',
);
- mimblewimblecoin.Libmwc.startMwcMqsListener(
+ libMwc.startMwcMqsListener(
wallet: wallet!,
mwcmqsConfig: mwcmqsConfig.toString(),
);
@@ -371,7 +364,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
/// Stop MWCMQS listener.
Future<void> stopSlatepackListener() async {
try {
- mimblewimblecoin.Libmwc.stopMwcMqsListener();
+ libMwc.stopMwcMqsListener();
} catch (e, s) {
Logging.instance.e('Failed to stop slatepack listener: $e\n$s');
}
@@ -379,7 +372,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
/// Validate MWC address.
bool validateMwcAddress(String address) {
- return mimblewimblecoin.Libmwc.validateSendAddress(address: address);
+ return libMwc.validateSendAddress(address: address);
}
/// Detect if an address is a slatepack.
@@ -422,15 +415,12 @@ class MimblewimblecoinWallet extends Bip39Wallet {
);
// Decode the slatepack
- final decoded =
- wallet != null
- ? await mimblewimblecoin.Libmwc.decodeSlatepackWithWallet(
- wallet: wallet,
- slatepack: slatepack,
- )
- : await mimblewimblecoin.Libmwc.decodeSlatepack(
- slatepack: slatepack,
- );
+ final decoded = wallet != null
+ ? await libMwc.decodeSlatepackWithWallet(
+ wallet: wallet,
+ slatepack: slatepack,
+ )
+ : await libMwc.decodeSlatepack(slatepack: slatepack);
// Parse the slate JSON to extract metadata
final slateData = jsonDecode(decoded.slateJson);
@@ -610,7 +600,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
final wallet = await secureStorageInterface.read(key: '${walletId}_wallet');
try {
final available = info.cachedBalance.spendable.raw.toInt();
- final transactionFees = await mimblewimblecoin.Libmwc.getTransactionFees(
+ final transactionFees = await libMwc.getTransactionFees(
wallet: wallet!,
amount: satoshiAmount,
minimumConfirmations: cryptoCurrency.minConfirms,
@@ -619,8 +609,9 @@ class MimblewimblecoinWallet extends Bip39Wallet {
int realFee = 0;
try {
- realFee =
- (Decimal.parse(transactionFees.fee.toString())).toBigInt().toInt();
+ realFee = (Decimal.parse(
+ transactionFees.fee.toString(),
+ )).toBigInt().toInt();
} catch (e, s) {
//todo: come back to this
debugPrint("$e $s");
@@ -639,7 +630,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
if (!syncMutex.isLocked) {
await syncMutex.protect(() async {
// How does getWalletBalances start syncing????
- await mimblewimblecoin.Libmwc.getWalletBalances(
+ await libMwc.getWalletBalances(
wallet: wallet!,
refreshFromNode: refreshFromNode,
minimumConfirmations: 10,
@@ -661,7 +652,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
_allWalletBalances() async {
final wallet = await secureStorageInterface.read(key: '${walletId}_wallet');
const refreshFromNode = 0;
- return await mimblewimblecoin.Libmwc.getWalletBalances(
+ return await libMwc.getWalletBalances(
wallet: wallet!,
refreshFromNode: refreshFromNode,
minimumConfirmations: cryptoCurrency.minConfirms,
@@ -729,7 +720,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
) async {
final wallet = await secureStorageInterface.read(key: '${walletId}_wallet');
- final walletAddress = await mimblewimblecoin.Libmwc.getAddressInfo(
+ final walletAddress = await libMwc.getAddressInfo(
wallet: wallet!,
index: index,
);
@@ -752,7 +743,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
Future<void> _startScans() async {
try {
//First stop the current listener
- mimblewimblecoin.Libmwc.stopMwcMqsListener();
+ libMwc.stopMwcMqsListener();
final wallet = await secureStorageInterface.read(
key: '${walletId}_wallet',
);
@@ -774,7 +765,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
"chainHeight: $chainHeight, lastScannedBlock: $lastScannedBlock",
);
- final int nextScannedBlock = await mimblewimblecoin.Libmwc.scanOutputs(
+ final int nextScannedBlock = await libMwc.scanOutputs(
wallet: wallet!,
startHeight: lastScannedBlock,
numberOfBlocks: scanChunkSize,
@@ -809,7 +800,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
Logging.instance.i("STARTING WALLET LISTENER ....");
final wallet = await secureStorageInterface.read(key: '${walletId}_wallet');
final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig();
- mimblewimblecoin.Libmwc.startMwcMqsListener(
+ libMwc.startMwcMqsListener(
wallet: wallet!,
mwcmqsConfig: mwcmqsConfig.toString(),
);
@@ -877,7 +868,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
final String stringConfig = await _getConfig();
final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig();
//if (!_logsInitialized) {
- // await mimblewimblecoin.Libmwc.initLogs(config: stringConfig);
+ // await libMwc.initLogs(config: stringConfig);
// _logsInitialized = true; // Set flag to true after initializing
// }
await secureStorageInterface.write(
@@ -895,7 +886,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
final String name = walletId;
- await mimblewimblecoin.Libmwc.initializeNewWallet(
+ await libMwc.initializeNewWallet(
config: stringConfig,
mnemonic: mnemonicString,
password: password,
@@ -903,7 +894,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
);
//Open wallet
- encodedWallet = await mimblewimblecoin.Libmwc.openWallet(
+ encodedWallet = await libMwc.openWallet(
config: stringConfig,
password: password,
);
@@ -937,14 +928,14 @@ class MimblewimblecoinWallet extends Bip39Wallet {
try {
final config = await _getRealConfig();
//if (!_logsInitialized) {
- // await mimblewimblecoin.Libmwc.initLogs(config: config);
+ // await libMwc.initLogs(config: config);
// _logsInitialized = true; // Set flag to true after initializing
//}
final password = await secureStorageInterface.read(
key: '${walletId}_password',
);
- final walletOpen = await mimblewimblecoin.Libmwc.openWallet(
+ final walletOpen = await libMwc.openWallet(
config: config,
password: password!,
);
@@ -991,7 +982,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
if (receiverAddress.startsWith("http://") ||
receiverAddress.startsWith("https://")) {
- transaction = await mimblewimblecoin.Libmwc.txHttpSend(
+ transaction = await libMwc.txHttpSend(
wallet: wallet!,
selectionStrategyIsAll: 0,
minimumConfirmations: cryptoCurrency.minConfirms,
@@ -1000,7 +991,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
address: txData.recipients!.first.address,
);
} else if (receiverAddress.startsWith("mwcmqs://")) {
- transaction = await mimblewimblecoin.Libmwc.createTransaction(
+ transaction = await libMwc.createTransaction(
wallet: wallet!,
amount: txData.recipients!.first.amount.raw.toInt(),
address: txData.recipients!.first.address,
@@ -1129,7 +1120,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
value: mwcmqsConfig.toString(),
);
- await mimblewimblecoin.Libmwc.recoverWallet(
+ await libMwc.recoverWallet(
config: stringConfig,
password: password,
mnemonic: await getMnemonic(),
@@ -1153,7 +1144,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
);
//Open Wallet
- final walletOpen = await mimblewimblecoin.Libmwc.openWallet(
+ final walletOpen = await libMwc.openWallet(
config: stringConfig,
password: password,
);
@@ -1331,20 +1322,19 @@ class MimblewimblecoinWallet extends Bip39Wallet {
);
const refreshFromNode = 1;
- final myAddresses =
- await mainDB
- .getAddresses(walletId)
- .filter()
- .typeEqualTo(AddressType.mimbleWimble)
- .and()
- .subTypeEqualTo(AddressSubType.receiving)
- .and()
- .valueIsNotEmpty()
- .valueProperty()
- .findAll();
+ final myAddresses = await mainDB
+ .getAddresses(walletId)
+ .filter()
+ .typeEqualTo(AddressType.mimbleWimble)
+ .and()
+ .subTypeEqualTo(AddressSubType.receiving)
+ .and()
+ .valueIsNotEmpty()
+ .valueProperty()
+ .findAll();
final myAddressesSet = myAddresses.toSet();
- final transactions = await mimblewimblecoin.Libmwc.getTransactions(
+ final transactions = await libMwc.getTransactions(
wallet: wallet!,
refreshFromNode: refreshFromNode,
);
@@ -1357,13 +1347,12 @@ class MimblewimblecoinWallet extends Bip39Wallet {
Logging.instance.i("tx: $tx");
final isIncoming =
- tx.txType == mimblewimblecoin_models.TransactionType.TxReceived ||
- tx.txType ==
- mimblewimblecoin_models.TransactionType.TxReceivedCancelled;
+ libMwc.txTypeIsReceived(tx.txType) ||
+ libMwc.txTypeIsReceiveCancelled(tx.txType);
final slateId = tx.txSlateId;
final commitId = slatesToCommits[slateId]?['commitId'] as String?;
- final numberOfMessages = tx.messages?.messages.length;
- final onChainNote = tx.messages?.messages[0].message;
+ final numberOfMessages = tx.messages?.length;
+ final onChainNote = tx.messages?.first.message;
final addressFrom = slatesToCommits[slateId]?["from"] as String?;
final addressTo = slatesToCommits[slateId]?["to"] as String?;
@@ -1446,15 +1435,12 @@ class MimblewimblecoinWallet extends Bip39Wallet {
"slateId": slateId,
"onChainNote": onChainNote,
"isCancelled":
- tx.txType ==
- mimblewimblecoin_models.TransactionType.TxSentCancelled ||
- tx.txType ==
- mimblewimblecoin_models.TransactionType.TxReceivedCancelled,
- "overrideFee":
- Amount(
- rawValue: BigInt.from(fee),
- fractionDigits: cryptoCurrency.fractionDigits,
- ).toJsonString(),
+ libMwc.txTypeIsSentCancelled(tx.txType) ||
+ libMwc.txTypeIsReceiveCancelled(tx.txType),
+ "overrideFee": Amount(
+ rawValue: BigInt.from(fee),
+ fractionDigits: cryptoCurrency.fractionDigits,
+ ).toJsonString(),
};
final txn = TransactionV2(
@@ -1531,9 +1517,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
@override
Future<void> updateChainHeight() async {
final config = await _getRealConfig();
- final latestHeight = await mimblewimblecoin.Libmwc.getChainHeight(
- config: config,
- );
+ final latestHeight = await libMwc.getChainHeight(config: config);
await info.updateCachedChainHeight(
newHeight: latestHeight,
isar: mainDB.isar,
@@ -1605,10 +1589,7 @@ Future<String> deleteMimblewimblecoinWallet({
return "Tried to delete non existent mimblewimblecoin wallet file with walletId=$walletId";
} else {
try {
- return mimblewimblecoin.Libmwc.deleteWallet(
- wallet: wallet,
- config: config!,
- );
+ return libMwc.deleteWallet(wallet: wallet, config: config!);
} catch (e, s) {
Logging.instance.e("$e\n$s");
return "deleteMimblewimblecoinWallet($walletId) failed...";
diff --git a/lib/wl_gen/interfaces/libmwc_interface.dart b/lib/wl_gen/interfaces/libmwc_interface.dart
new file mode 100644
index 0000000..60536cf
--- /dev/null
+++ b/lib/wl_gen/interfaces/libmwc_interface.dart
@@ -0,0 +1,198 @@
+export '../generated/libmwc_interface_impl.dart';
+
+abstract class LibMwcInterface {
+ const LibMwcInterface();
+
+ bool txTypeIsReceived(Enum value);
+ bool txTypeIsReceiveCancelled(Enum value);
+ bool txTypeIsSentCancelled(Enum value);
+
+ Future<String> initializeNewWallet({
+ required String config,
+ required String mnemonic,
+ required String password,
+ required String name,
+ });
+
+ Future<String> openWallet({required String config, required String password});
+
+ Future<void> recoverWallet({
+ required String config,
+ required String password,
+ required String mnemonic,
+ required String name,
+ });
+
+ Future<({String commitId, String slateId})> txHttpSend({
+ required String wallet,
+ required int selectionStrategyIsAll,
+ required int minimumConfirmations,
+ required String message,
+ required int amount,
+ required String address,
+ });
+
+ Future<({String commitId, String slateId})> createTransaction({
+ required String wallet,
+ required int amount,
+ required String address,
+ required int secretKeyIndex,
+ required String mwcmqsConfig,
+ required int minimumConfirmations,
+ required String note,
+ });
+
+ Future<String> cancelTransaction({
+ required String wallet,
+ required String transactionId,
+ });
+
+ Future<String> txInit({
+ required String wallet,
+ required int amount,
+ int minimumConfirmations = 1,
+ bool selectionStrategyIsAll = false,
+ String message = '',
+ });
+
+ Future<List<MwcTransaction>> getTransactions({
+ required String wallet,
+ required int refreshFromNode,
+ });
+
+ Future<({String? recipientAddress, String slatepack, bool wasEncrypted})>
+ encodeSlatepack({
+ required String slateJson,
+ String? recipientAddress,
+ bool encrypt = false,
+ String? wallet,
+ });
+ Future<
+ ({
+ String? recipientAddress,
+ String? senderAddress,
+ String slateJson,
+ bool wasEncrypted,
+ })
+ >
+ decodeSlatepackWithWallet({
+ required String wallet,
+ required String slatepack,
+ });
+
+ Future<
+ ({
+ String? recipientAddress,
+ String? senderAddress,
+ String slateJson,
+ bool wasEncrypted,
+ })
+ >
+ decodeSlatepack({required String slatepack});
+
+ Future<({String commitId, String slateId, String slateJson})>
+ txReceiveDetailed({required String wallet, required String slateJson});
+
+ Future<({String commitId, String slateId})> txFinalize({
+ required String wallet,
+ required String slateJson,
+ });
+
+ void startMwcMqsListener({
+ required String wallet,
+ required String mwcmqsConfig,
+ });
+
+ void stopMwcMqsListener();
+
+ bool validateSendAddress({required String address});
+
+ Future<({int fee, bool strategyUseAll, int total})> getTransactionFees({
+ required String wallet,
+ required int amount,
+ required int minimumConfirmations,
+ required int available,
+ });
+
+ Future<
+ ({
+ double awaitingFinalization,
+ double pending,
+ double spendable,
+ double total,
+ })
+ >
+ getWalletBalances({
+ required String wallet,
+ required int refreshFromNode,
+ required int minimumConfirmations,
+ });
+
+ Future<String> getAddressInfo({required String wallet, required int index});
+
+ Future<int> scanOutputs({
+ required String wallet,
+ required int startHeight,
+ required int numberOfBlocks,
+ });
+
+ Future<int> getChainHeight({required String config});
+
+ Future<String> deleteWallet({required String wallet, required String config});
+}
+
+class MwcTransaction {
+ final String parentKeyId;
+ final int id;
+ final String? txSlateId;
+ final Enum txType;
+ final String creationTs;
+ final String confirmationTs;
+ final bool confirmed;
+ final int numInputs;
+ final int numOutputs;
+ final String amountCredited;
+ final String amountDebited;
+ final String? fee;
+ final String? ttlCutoffHeight;
+ final List<MwcMessage>? messages;
+ final String? storedTx;
+ final String? kernelExcess;
+ final int? kernelLookupMinHeight;
+ final String? paymentProof;
+
+ MwcTransaction({
+ required this.parentKeyId,
+ required this.id,
+ this.txSlateId,
+ required this.txType,
+ required this.creationTs,
+ required this.confirmationTs,
+ required this.confirmed,
+ required this.numInputs,
+ required this.numOutputs,
+ required this.amountCredited,
+ required this.amountDebited,
+ this.fee,
+ this.ttlCutoffHeight,
+ this.messages,
+ this.storedTx,
+ this.kernelExcess,
+ this.kernelLookupMinHeight,
+ this.paymentProof,
+ });
+}
+
+class MwcMessage {
+ final String id;
+ final String publicKey;
+ final String? message;
+ final String? messageSig;
+
+ MwcMessage({
+ required this.id,
+ required this.publicKey,
+ this.message,
+ this.messageSig,
+ });
+}
diff --git a/scripts/app_config/configure_stack_wallet.sh b/scripts/app_config/configure_stack_wallet.sh
index b407b7d..b4f503d 100755
--- a/scripts/app_config/configure_stack_wallet.sh
+++ b/scripts/app_config/configure_stack_wallet.sh
@@ -23,6 +23,31 @@ else
sed -i "s/description: PLACEHOLDER/description: ${NEW_NAME}/g" "${PUBSPEC_FILE}"
fi
+dart "${APP_PROJECT_ROOT_DIR}/tool/process_pubspec_deps.dart" \
+ "${PUBSPEC_FILE}" \
+ MWC \
+ MWEBD \
+ XMR \
+ SAL \
+ TOR \
+ EPIC \
+ FIRO \
+ XEL \
+ FROST
+
+dart "${APP_PROJECT_ROOT_DIR}/tool/gen_interfaces.dart" \
+ "${APP_PROJECT_ROOT_DIR}/tool/wl_templates" \
+ "${APP_PROJECT_ROOT_DIR}/lib/wl_gen/generated" \
+ MWC \
+ MWEBD \
+ XMR \
+ SAL \
+ TOR \
+ EPIC \
+ FIRO \
+ XEL \
+ FROST
+
pushd "${APP_PROJECT_ROOT_DIR}"
BUILT_COMMIT_HASH=$(git log -1 --pretty=format:"%H")
popd
diff --git a/scripts/app_config/templates/pubspec.template.yaml b/scripts/app_config/templates/pubspec.template.yaml
index 78f73ad..98a63db 100644
--- a/scripts/app_config/templates/pubspec.template.yaml
+++ b/scripts/app_config/templates/pubspec.template.yaml
@@ -47,8 +47,10 @@ dependencies:
flutter_libepiccash:
path: ./crypto_plugins/flutter_libepiccash
- flutter_libmwc:
- path: ./crypto_plugins/flutter_libmwc
+# %%ENABLE_MWC%%
+# flutter_libmwc:
+# path: ./crypto_plugins/flutter_libmwc
+# %%END_ENABLE_MWC%%
bitcoindart:
git:
diff --git a/tool/gen_interfaces.dart b/tool/gen_interfaces.dart
new file mode 100644
index 0000000..fc48494
--- /dev/null
+++ b/tool/gen_interfaces.dart
@@ -0,0 +1,83 @@
+import 'dart:io';
+
+void main(List<String> args) {
+ if (args.length < 2) {
+ print(
+ 'Usage: dart process_pubspec_deps.dart <templatesDir> <generatedDir> <marker1> [marker2 ...]',
+ );
+ exit(1);
+ }
+
+ final generatedDir = Directory(args[1]);
+ if (!generatedDir.existsSync()) generatedDir.createSync();
+
+ final templatesDir = args[0];
+
+ final List<String> onFilePrefixes = args.length > 2 ? args.sublist(2) : [];
+
+ final templates = Directory(templatesDir).listSync().whereType<File>();
+
+ print(templates.map((e) => e.uri.pathSegments.last));
+
+ for (final template in templates) {
+ final templateName = template.uri.pathSegments.last;
+
+ final prefix = templateName.split("_").first;
+
+ final outfileUri = generatedDir.uri.resolve(
+ templateName
+ .replaceFirst("${prefix}_", "")
+ .replaceFirst(".template.", "."),
+ );
+
+ _genFile(
+ outfileUri.toFilePath(),
+ template,
+ onFilePrefixes.contains(prefix),
+ );
+ }
+}
+
+void _genFile(String outputFilePath, File template, bool on) {
+ final lines = template.readAsLinesSync();
+
+ // add empty line and gen warning
+
+ final List<String> updatedLines = [
+ "// GENERATED CODE - DO NOT MODIFY BY HAND",
+ "",
+ ];
+
+ final keepMarker = on ? "ON" : "OFF";
+ final dropMarker = on ? "OFF" : "ON";
+
+ bool? keepLine;
+
+ for (final line in lines) {
+ if (line.trim() == "//$keepMarker") {
+ keepLine = true;
+ continue;
+ }
+
+ if (line.trim() == "//$dropMarker") {
+ keepLine = false;
+ continue;
+ }
+
+ if (line.trim().startsWith("//END_")) {
+ keepLine = null;
+ continue;
+ }
+
+ if (keepLine != false) {
+ updatedLines.add(line);
+ }
+ }
+
+ // add empty line
+ if (updatedLines.last.isNotEmpty) updatedLines.add("");
+
+ File(outputFilePath).writeAsStringSync(updatedLines.join('\n'));
+
+ print("Done gen of $template => $outputFilePath");
+}
diff --git a/tool/process_pubspec_deps.dart b/tool/process_pubspec_deps.dart
new file mode 100644
index 0000000..279dfe2
--- /dev/null
+++ b/tool/process_pubspec_deps.dart
@@ -0,0 +1,96 @@
+import 'dart:io';
+
+void main(List<String> args) {
+ if (args.isEmpty) {
+ print(
+ 'Usage: dart process_pubspec_deps.dart <file> <marker1> [marker2 ...]',
+ );
+ exit(1);
+ }
+
+ if (args.length == 1) {
+ print('No markers specified. Nothing to do.');
+ exit(0);
+ }
+
+ _process(args[0], args.sublist(1));
+}
+
+void _process(final String filePath, final List<String> enableCoinMarkers) {
+ final lines = File(filePath).readAsLinesSync();
+
+ String? activeMarker;
+ final List<String> updatedLines = [];
+
+ for (final line in lines) {
+ bool markerSet = false;
+
+ // Check if line starts any marker block
+ for (final marker in enableCoinMarkers) {
+ if (line.contains('# %%ENABLE_$marker%%')) {
+ activeMarker = marker;
+ markerSet = true;
+ updatedLines.add(line);
+ break;
+ }
+ }
+
+ if (markerSet) continue;
+
+ // Check if line ends the active marker block
+ if (activeMarker != null &&
+ line.contains('# %%END_ENABLE_$activeMarker%%')) {
+ activeMarker = null;
+ updatedLines.add(line);
+ continue;
+ }
+
+ // If inside an active marker block, uncomment line
+ if (activeMarker != null) {
+ updatedLines.add(line.replaceFirst("#", ""));
+ } else {
+ updatedLines.add(line);
+ }
+ }
+
+ File(filePath).writeAsStringSync(updatedLines.join('\n'));
+ print(
+ 'Updated pubspec.yaml with enabled blocks: ${enableCoinMarkers.join(", ")}',
+ );
+}
+
+void _genFile(String outputFilePath, String template, bool on) {
+ final lines = File(template).readAsLinesSync();
+
+ // add empty line and gen warning
+
+ final List<String> updatedLines = [
+ "// GENERATED CODE - DO NOT MODIFY BY HAND",
+ "",
+ ];
+
+ final keepMarker = on ? "ON" : "OFF";
+
+ bool? keepLine;
+
+ for (final line in lines) {
+ if (line.trim() == "//$keepMarker") {
+ keepLine = true;
+ continue;
+ }
+
+ if (line.trim() == "//END_$keepMarker") {
+ keepLine = false;
+ continue;
+ }
+
+ if (keepLine != false) {
+ updatedLines.add(line);
+ }
+ }
+
+ // add empty line
+ if (updatedLines.last.isNotEmpty) updatedLines.add("");
+
+ File(outputFilePath).writeAsStringSync(updatedLines.join('\n'));
+}
diff --git a/tool/wl_templates/MWC_libmwc_interface_impl.template.dart b/tool/wl_templates/MWC_libmwc_interface_impl.template.dart
new file mode 100644
index 0000000..356c071
--- /dev/null
+++ b/tool/wl_templates/MWC_libmwc_interface_impl.template.dart
@@ -0,0 +1,348 @@
+//ON
+import 'package:flutter_libmwc/lib.dart' as mimblewimblecoin;
+import 'package:flutter_libmwc/models/transaction.dart'
+ as mimblewimblecoin_models;
+
+//END_ON
+import '../interfaces/libmwc_interface.dart';
+
+LibMwcInterface get libMwc => _getLibMwc();
+
+//OFF
+LibMwcInterface _getLibMwc() => throw Exception("MWC not enabled!");
+
+//END_OFF
+
+//ON
+LibMwcInterface _getLibMwc() => const _LibMwcInterfaceImpl();
+
+final class _LibMwcInterfaceImpl extends LibMwcInterface {
+ const _LibMwcInterfaceImpl();
+
+ @override
+ Future<String> cancelTransaction({
+ required String wallet,
+ required String transactionId,
+ }) {
+ return mimblewimblecoin.Libmwc.cancelTransaction(
+ wallet: wallet,
+ transactionId: transactionId,
+ );
+ }
+
+ @override
+ Future<({String commitId, String slateId})> createTransaction({
+ required String wallet,
+ required int amount,
+ required String address,
+ required int secretKeyIndex,
+ required String mwcmqsConfig,
+ required int minimumConfirmations,
+ required String note,
+ }) {
+ return mimblewimblecoin.Libmwc.createTransaction(
+ wallet: wallet,
+ amount: amount,
+ address: address,
+ secretKeyIndex: secretKeyIndex,
+ mwcmqsConfig: mwcmqsConfig,
+ minimumConfirmations: minimumConfirmations,
+ note: note,
+ );
+ }
+
+ @override
+ Future<
+ ({
+ String? recipientAddress,
+ String? senderAddress,
+ String slateJson,
+ bool wasEncrypted,
+ })
+ >
+ decodeSlatepack({required String slatepack}) {
+ return mimblewimblecoin.Libmwc.decodeSlatepack(slatepack: slatepack);
+ }
+
+ @override
+ Future<
+ ({
+ String? recipientAddress,
+ String? senderAddress,
+ String slateJson,
+ bool wasEncrypted,
+ })
+ >
+ decodeSlatepackWithWallet({
+ required String wallet,
+ required String slatepack,
+ }) {
+ return mimblewimblecoin.Libmwc.decodeSlatepackWithWallet(
+ wallet: wallet,
+ slatepack: slatepack,
+ );
+ }
+
+ @override
+ Future<String> deleteWallet({
+ required String wallet,
+ required String config,
+ }) {
+ return mimblewimblecoin.Libmwc.deleteWallet(wallet: wallet, config: config);
+ }
+
+ @override
+ Future<({String? recipientAddress, String slatepack, bool wasEncrypted})>
+ encodeSlatepack({
+ required String slateJson,
+ String? recipientAddress,
+ bool encrypt = false,
+ String? wallet,
+ }) {
+ return mimblewimblecoin.Libmwc.encodeSlatepack(
+ slateJson: slateJson,
+ recipientAddress: recipientAddress,
+ encrypt: encrypt,
+ wallet: wallet,
+ );
+ }
+
+ @override
+ Future<String> getAddressInfo({required String wallet, required int index}) {
+ return mimblewimblecoin.Libmwc.getAddressInfo(wallet: wallet, index: index);
+ }
+
+ @override
+ Future<int> getChainHeight({required String config}) {
+ return mimblewimblecoin.Libmwc.getChainHeight(config: config);
+ }
+
+ @override
+ Future<({int fee, bool strategyUseAll, int total})> getTransactionFees({
+ required String wallet,
+ required int amount,
+ required int minimumConfirmations,
+ required int available,
+ }) {
+ return mimblewimblecoin.Libmwc.getTransactionFees(
+ wallet: wallet,
+ amount: amount,
+ minimumConfirmations: minimumConfirmations,
+ available: available,
+ );
+ }
+
+ @override
+ Future<List<MwcTransaction>> getTransactions({
+ required String wallet,
+ required int refreshFromNode,
+ }) async {
+ final transactions = await mimblewimblecoin.Libmwc.getTransactions(
+ wallet: wallet,
+ refreshFromNode: refreshFromNode,
+ );
+
+ return transactions
+ .map(
+ (e) => MwcTransaction(
+ parentKeyId: e.parentKeyId,
+ id: e.id,
+ txType: e.txType,
+ creationTs: e.creationTs,
+ confirmationTs: e.confirmationTs,
+ confirmed: e.confirmed,
+ numInputs: e.numInputs,
+ numOutputs: e.numOutputs,
+ amountCredited: e.amountCredited,
+ amountDebited: e.amountDebited,
+ txSlateId: e.txSlateId,
+ fee: e.fee,
+ ttlCutoffHeight: e.ttlCutoffHeight,
+ messages: e.messages?.messages
+ .map(
+ (f) => MwcMessage(
+ id: f.id,
+ publicKey: f.publicKey,
+ message: f.message,
+ messageSig: f.messageSig,
+ ),
+ )
+ .toList(),
+ storedTx: e.storedTx,
+ kernelExcess: e.kernelExcess,
+ kernelLookupMinHeight: e.kernelLookupMinHeight,
+ paymentProof: e.paymentProof,
+ ),
+ )
+ .toList();
+ }
+
+ @override
+ Future<
+ ({
+ double awaitingFinalization,
+ double pending,
+ double spendable,
+ double total,
+ })
+ >
+ getWalletBalances({
+ required String wallet,
+ required int refreshFromNode,
+ required int minimumConfirmations,
+ }) {
+ return mimblewimblecoin.Libmwc.getWalletBalances(
+ wallet: wallet,
+ refreshFromNode: refreshFromNode,
+ minimumConfirmations: minimumConfirmations,
+ );
+ }
+
+ @override
+ Future<String> initializeNewWallet({
+ required String config,
+ required String mnemonic,
+ required String password,
+ required String name,
+ }) {
+ return mimblewimblecoin.Libmwc.initializeNewWallet(
+ config: config,
+ mnemonic: mnemonic,
+ password: password,
+ name: name,
+ );
+ }
+
+ @override
+ Future<String> openWallet({
+ required String config,
+ required String password,
+ }) {
+ return mimblewimblecoin.Libmwc.openWallet(
+ config: config,
+ password: password,
+ );
+ }
+
+ @override
+ Future<void> recoverWallet({
+ required String config,
+ required String password,
+ required String mnemonic,
+ required String name,
+ }) {
+ return mimblewimblecoin.Libmwc.recoverWallet(
+ config: config,
+ password: password,
+ mnemonic: mnemonic,
+ name: name,
+ );
+ }
+
+ @override
+ Future<int> scanOutputs({
+ required String wallet,
+ required int startHeight,
+ required int numberOfBlocks,
+ }) {
+ return mimblewimblecoin.Libmwc.scanOutputs(
+ wallet: wallet,
+ startHeight: startHeight,
+ numberOfBlocks: numberOfBlocks,
+ );
+ }
+
+ @override
+ void startMwcMqsListener({
+ required String wallet,
+ required String mwcmqsConfig,
+ }) {
+ return mimblewimblecoin.Libmwc.startMwcMqsListener(
+ wallet: wallet,
+ mwcmqsConfig: mwcmqsConfig,
+ );
+ }
+
+ @override
+ void stopMwcMqsListener() {
+ return mimblewimblecoin.Libmwc.stopMwcMqsListener();
+ }
+
+ @override
+ Future<({String commitId, String slateId})> txFinalize({
+ required String wallet,
+ required String slateJson,
+ }) {
+ return mimblewimblecoin.Libmwc.txFinalize(
+ wallet: wallet,
+ slateJson: slateJson,
+ );
+ }
+
+ @override
+ Future<({String commitId, String slateId})> txHttpSend({
+ required String wallet,
+ required int selectionStrategyIsAll,
+ required int minimumConfirmations,
+ required String message,
+ required int amount,
+ required String address,
+ }) {
+ return mimblewimblecoin.Libmwc.txHttpSend(
+ wallet: wallet,
+ selectionStrategyIsAll: selectionStrategyIsAll,
+ minimumConfirmations: minimumConfirmations,
+ message: message,
+ amount: amount,
+ address: address,
+ );
+ }
+
+ @override
+ Future<String> txInit({
+ required String wallet,
+ required int amount,
+ int minimumConfirmations = 1,
+ bool selectionStrategyIsAll = false,
+ String message = '',
+ }) {
+ return mimblewimblecoin.Libmwc.txInit(
+ wallet: wallet,
+ amount: amount,
+ message: message,
+ minimumConfirmations: minimumConfirmations,
+ selectionStrategyIsAll: selectionStrategyIsAll,
+ );
+ }
+
+ @override
+ Future<({String commitId, String slateId, String slateJson})>
+ txReceiveDetailed({required String wallet, required String slateJson}) {
+ return mimblewimblecoin.Libmwc.txReceiveDetailed(
+ wallet: wallet,
+ slateJson: slateJson,
+ );
+ }
+
+ @override
+ bool txTypeIsReceiveCancelled(Enum value) {
+ return value == mimblewimblecoin_models.TransactionType.TxReceivedCancelled;
+ }
+
+ @override
+ bool txTypeIsReceived(Enum value) {
+ return value == mimblewimblecoin_models.TransactionType.TxReceived;
+ }
+
+ @override
+ bool txTypeIsSentCancelled(Enum value) {
+ return value == mimblewimblecoin_models.TransactionType.TxSentCancelled;
+ }
+
+ @override
+ bool validateSendAddress({required String address}) {
+ return mimblewimblecoin.Libmwc.validateSendAddress(address: address);
+ }
+}
+
+//END_ON
Why this scored 11/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.