diff --git a/.gitmodules b/.gitmodules index d0c1810aa1..bc5d65314d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,10 @@ -[submodule "crypto_plugins/flutter_libepiccash"] - path = crypto_plugins/flutter_libepiccash - url = https://github.com/cypherstack/flutter_libepiccash.git [submodule "crypto_plugins/frostdart"] path = crypto_plugins/frostdart url = https://github.com/cypherstack/frostdart [submodule "crypto_plugins/flutter_libmwc"] path = crypto_plugins/flutter_libmwc url = https://github.com/cypherstack/flutter_libmwc +[submodule "crypto_plugins/flutter_libepiccash"] + path = crypto_plugins/flutter_libepiccash + url = https://github.com/who-biz/flutter_libepiccash.git + branch = ebox-cancel-tx diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index f4a55aa9e5..3cd76e2643 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit f4a55aa9e5b6066428402291ed228aa0dd921534 +Subproject commit 3cd76e2643d0b4773d7bfcaf2f26056702dfef06 diff --git a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart index 721d9a11c9..cff16acbb5 100644 --- a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart +++ b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart @@ -106,6 +106,7 @@ class TransactionV2 { int? get numberOfMessages => _getFromOtherData(key: TxV2OdKeys.numberOfMessages) as int?; String? get slateId => _getFromOtherData(key: TxV2OdKeys.slateId) as String?; + String? get epicboxId => _getFromOtherData(key: TxV2OdKeys.epicboxId) as String?; String? get onChainNote => _getFromOtherData(key: TxV2OdKeys.onChainNote) as String?; bool get isCancelled => @@ -409,6 +410,7 @@ abstract final class TxV2OdKeys { static const isMimblewimblecoinTransaction = "isMimblewimblecoinTransaction"; static const numberOfMessages = "numberOfMessages"; static const slateId = "slateId"; + static const epicboxId = "epicboxId"; static const onChainNote = "onChainNote"; static const isCancelled = "isCancelled"; static const contractAddress = "contractAddress"; diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index cb1f5f8ad6..04c53205af 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -44,45 +44,46 @@ class AddressUtils { static Map _parseUri(String uri) { final Map result = {}; try { - final u = Uri.parse(uri); - if (u.hasScheme) { - result["scheme"] = u.scheme.toLowerCase(); + final Uri parsedUri = Uri.parse(uri); + + if (parsedUri.hasScheme) { + final String scheme = parsedUri.scheme.toLowerCase(); + result["scheme"] = scheme; // Handle different URI formats. if (result["scheme"] == "bitcoin" || result["scheme"] == "bitcoincash") { - result["address"] = u.path; + result["address"] = parsedUri.path; } else if (result["scheme"] == "monero") { // Monero addresses can contain '?' which Uri.parse interprets as query start. - final addressEnd = uri.indexOf( - '?', - 7, - ); // 7 is the length of "monero:". - if (addressEnd != -1) { - result["address"] = uri.substring(7, addressEnd); - } else { - result["address"] = uri.substring(7); - } + final int addressEnd = uri.indexOf( + "?", + 7, // 7 is the length of "monero:". + ); } else { // Default case, treat path as address. - result["address"] = u.path; + result["address"] = parsedUri.path; } + } else { + // Plain address, including an Epicbox/Grinbox address containing '@'. + result["address"] = parsedUri.path; + } - // Parse query parameters. - result.addAll(_parseQueryParameters(u.queryParameters)); + // Parse query parameters. + result.addAll(_parseQueryParameters(parsedUri.queryParameters)); - // Handle Monero-specific fragment (tx_description). - if (u.fragment.isNotEmpty && result["scheme"] == "monero") { - result["tx_description"] = Uri.decodeComponent(u.fragment); - } + // Handle Monero-specific fragment (tx_description). + if (parsedUri.fragment.isNotEmpty && result["scheme"] == "monero") { + result["tx_description"] = Uri.decodeComponent(parsedUri.fragment); } } catch (e, s) { Logging.instance.d( - "Exception caught in parseUri($uri): $e", + "Exception caught in _parseUri($uri): $e", error: e, stackTrace: s, ); } + return result; } @@ -137,6 +138,7 @@ class AddressUtils { static PaymentUriData? parsePaymentUri(String uri, {Logging? logging}) { // hacky check its not just a bcash, ecash, or xel address final parts = uri.split(":"); + if (parts.length == 2) { if ([ "xel", @@ -153,20 +155,25 @@ class AddressUtils { final Map parsedData = _parseUri(uri); // Normalize the URI scheme. - final String scheme = parsedData['scheme'] ?? ''; - parsedData.remove('scheme'); + final String scheme = parsedData["scheme"] ?? ""; + parsedData.remove("scheme"); // Filter out unrecognized parameters. - final filteredParams = _filterParams(parsedData); + final String? address = parsedData["address"]; + + if (address == null || address.trim().isEmpty) { + return null; + } + + final Map filteredParams = _filterParams(parsedData); return PaymentUriData( scheme: scheme, - address: parsedData['address']!.trim(), - amount: filteredParams['amount'] ?? filteredParams['tx_amount'], - label: filteredParams['label'] ?? filteredParams['recipient_name'], - message: filteredParams['message'] ?? filteredParams['tx_description'], - paymentId: filteredParams['tx_payment_id'], - // Specific to Monero + address: address.trim(), + amount: filteredParams["amount"] ?? filteredParams["tx_amount"], + label: filteredParams["label"] ?? filteredParams["recipient_name"], + message: filteredParams["message"] ?? filteredParams["tx_description"], + paymentId: filteredParams["tx_payment_id"], additionalParams: filteredParams, ); } catch (e, s) { diff --git a/lib/wallets/wallet/impl/epiccash_wallet.dart b/lib/wallets/wallet/impl/epiccash_wallet.dart index 3746a4836e..e2e7f434e7 100644 --- a/lib/wallets/wallet/impl/epiccash_wallet.dart +++ b/lib/wallets/wallet/impl/epiccash_wallet.dart @@ -138,9 +138,12 @@ class EpiccashWallet extends Bip39Wallet { throw Exception('Wallet not initialized'); } - final result = await libEpic.cancelTransaction( + final epicboxConfig = await getEpicBoxConfig(); + final result = await libEpic.cancelEpicboxTransaction( wallet: _wallet!, - transactionId: txSlateId, + methodIsEpicbox: true, + epicboxConfig: epicboxConfig.toString(), + txSlateId: txSlateId, ); Logging.instance.d("cancel $txSlateId result: $result"); return result; @@ -1386,10 +1389,22 @@ class EpiccashWallet extends Bip39Wallet { final slatesToCommits = info.epicData?.slatesToCommits ?? {}; for (final tx in transactions) { + + Logging.instance.w( + "EPIC TX " + "id=${tx.id} " + "slate=${tx.txSlateId} " + "epicbox_tx_id=${tx.txEpicboxId} " + "type=${tx.txType} " + "sentCancelled=${libEpic.txTypeIsSentCancelled(tx.txType)} " + "receiveCancelled=${libEpic.txTypeIsReceiveCancelled(tx.txType)}", + ); + final isIncoming = libEpic.txTypeIsReceived(tx.txType) || libEpic.txTypeIsReceiveCancelled(tx.txType); final slateId = tx.txSlateId; + final epicboxId = tx.txEpicboxId; final commitId = slatesToCommits[slateId]?['commitId'] as String?; final numberOfMessages = tx.messages?.length; final onChainNote = tx.messages?.first.message; @@ -1451,6 +1466,7 @@ class EpiccashWallet extends Bip39Wallet { "isEpiccashTransaction": true, "numberOfMessages": numberOfMessages, "slateId": slateId, + "epicboxId": epicboxId, "onChainNote": onChainNote, "isCancelled": libEpic.txTypeIsSentCancelled(tx.txType) || diff --git a/lib/wl_gen/interfaces/libepiccash_interface.dart b/lib/wl_gen/interfaces/libepiccash_interface.dart index cdbbb19cee..dbc25054c8 100644 --- a/lib/wl_gen/interfaces/libepiccash_interface.dart +++ b/lib/wl_gen/interfaces/libepiccash_interface.dart @@ -63,9 +63,13 @@ abstract class LibEpicCashInterface { required String slateJson, }); - Future cancelTransaction({ + Future cancelEpicboxTransaction({ required DynamicObject wallet, - required String transactionId, + required bool methodIsEpicbox, + String? epicboxConfig, + int? txId, + String? txSlateId, + String? txEpicboxId, }); Future> getTransactions({ @@ -135,6 +139,7 @@ class EpicTransaction { final String parentKeyId; final int id; final String? txSlateId; + final String? txEpicboxId; final Enum txType; final String creationTs; final String confirmationTs; @@ -155,6 +160,7 @@ class EpicTransaction { required this.parentKeyId, required this.id, this.txSlateId, + this.txEpicboxId, required this.txType, required this.creationTs, required this.confirmationTs, @@ -177,6 +183,7 @@ class EpicTransaction { return 'EpicTransaction(' 'id: $id, ' 'txSlateId: $txSlateId, ' + 'txEpicboxId: $txEpicboxId, ' 'type: $txType, ' 'confirmed: $confirmed, ' 'inputs: $numInputs, ' diff --git a/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart b/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart index 4e78631bdc..95108704d4 100644 --- a/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart +++ b/tool/wl_templates/EPIC_libepiccash_interface_impl.template.dart @@ -21,12 +21,25 @@ final class _LibEpicCashInterfaceImpl extends LibEpicCashInterface { const _LibEpicCashInterfaceImpl(); @override - Future cancelTransaction({ + Future cancelEpicboxTransaction({ required DynamicObject wallet, - required String transactionId, - }) { - return wallet.get().cancelTransaction( - transactionId: transactionId, + required bool methodIsEpicbox, + String? epicboxConfig, + int? txId, + String? txSlateId, + String? txEpicboxId, + }) async { + final epicWallet = wallet.get(); + + if (epicboxConfig != null) { + epicWallet.updateEpicboxConfig(epicboxConfig); + } + + return epicWallet.cancelEpicboxTransaction( + methodIsEpicbox: methodIsEpicbox, + txId: txId, + txSlateId: txSlateId, + txEpicboxId: txEpicboxId, ); } @@ -124,6 +137,17 @@ final class _LibEpicCashInterfaceImpl extends LibEpicCashInterface { refreshFromNode: refreshFromNode, ); + // Log the flutter_libepiccash Transaction BEFORE converting it. + for (final e in transactions) { + print( + "EPIC INTERFACE TX " + "id=${e.id} " + "txSlateId=${e.txSlateId} " + "txEpicboxId=${e.txEpicboxId} " + "type=${e.txType}", + ); + } + return transactions .map( (e) => EpicTransaction( @@ -138,6 +162,7 @@ final class _LibEpicCashInterfaceImpl extends LibEpicCashInterface { amountCredited: e.amountCredited, amountDebited: e.amountDebited, txSlateId: e.txSlateId, + txEpicboxId: e.txEpicboxId, fee: e.fee, ttlCutoffHeight: e.ttlCutoffHeight, messages: e.messages?.messages