diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 43e1486e6..fa0e9728d 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -61,6 +61,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B999088F2ABE1E170012A442 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; E6F536731AC506735EB76340 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -140,6 +141,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + B999088F2ABE1E170012A442 /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -321,8 +323,8 @@ "${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework", "${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework", "${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework", - "${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework", "${BUILT_PRODUCTS_DIR}/stack_wallet_backup/stack_wallet_backup.framework", + "${BUILT_PRODUCTS_DIR}/tor_ffi_plugin/tor_ffi_plugin.framework", "${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework", "${BUILT_PRODUCTS_DIR}/wakelock/wakelock.framework", ); @@ -354,8 +356,8 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/stack_wallet_backup.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/tor_ffi_plugin.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/wakelock.framework", ); @@ -455,6 +457,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -645,6 +648,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -727,6 +731,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements new file mode 100644 index 000000000..c326c8341 --- /dev/null +++ b/ios/Runner/Runner.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/lib/models/isar/models/blockchain_data/epic_transaction.dart b/lib/models/isar/models/blockchain_data/epic_transaction.dart new file mode 100644 index 000000000..251c89705 --- /dev/null +++ b/lib/models/isar/models/blockchain_data/epic_transaction.dart @@ -0,0 +1,119 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-10-03 + * + */ + +class EpicTransaction { + final String parentKeyId; + final int id; + final String? txSlateId; + final EpicTransactionType 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 Messages? messages; + final String? storedTx; + final String? kernelExcess; + final int? kernelLookupMinHeight; + final String? paymentProof; + + EpicTransaction({ + 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, + }); + + factory EpicTransaction.fromJson(dynamic json) { + // print("THIS JSON IS $json") + return EpicTransaction( + parentKeyId: json['parent_key_id'] as String, + id: int.parse(json!['id'].toString()), + txSlateId: json['tx_slate_id'].toString(), + txType: EpicTransactionType.values.byName(json['tx_type'] as String), + creationTs: json['creation_ts'].toString(), + confirmationTs: json['confirmation_ts'].toString(), + confirmed: bool.parse(json['confirmed'].toString()), + numInputs: int.parse(json['num_inputs'].toString()), + numOutputs: int.parse(json['num_outputs'].toString()), + amountCredited: json['amount_credited'].toString(), + amountDebited: json['amount_debited'].toString(), + fee: json['fee'].toString(), + ttlCutoffHeight: json['ttl_cutoff_height'].toString(), + messages: json['messages'] != null ? Messages.fromJson(json['messages'] as Map) : null, + storedTx: json['stored_tx'].toString(), + kernelExcess: json['kernel_excess'].toString(), + kernelLookupMinHeight: json['kernel_lookup_min_height'] == null? null : int.parse(json['kernel_lookup_min_height'].toString()), + paymentProof: json['payment_proof'].toString(), + ); + } +} + +class Messages { + final List messages; + + Messages({required this.messages}); + + factory Messages.fromJson(Map json) { + final messageList = json['messages'] as List; + final messages = messageList.map((message) => Message.fromJson(message as Map)).toList(); + return Messages(messages: messages); + } +} + +class Message { + final String id; + final String publicKey; + final String? message; + final String? messageSig; + + Message({ + required this.id, + required this.publicKey, + this.message, + this.messageSig, + }); + + factory Message.fromJson(Map json) { + return Message( + id: json['id'].toString(), + publicKey: json['public_key'].toString(), + message: json['message'].toString(), + messageSig: json['message_sig'].toString(), + ); + } +} + + +enum EpicTransactionType { + //Use Epic transaction type here + TxReceived, + TxReceivedCancelled, + TxSent, + TxSentCancelled, // should we keep this? +} diff --git a/lib/services/coins/epiccash/epiccash_wallet.dart b/lib/services/coins/epiccash/epiccash_wallet.dart index bdf803e4b..accd6752e 100644 --- a/lib/services/coins/epiccash/epiccash_wallet.dart +++ b/lib/services/coins/epiccash/epiccash_wallet.dart @@ -23,6 +23,7 @@ import 'package:stack_wallet_backup/generate_password.dart'; import 'package:stackwallet/db/isar/main_db.dart'; import 'package:stackwallet/models/balance.dart'; import 'package:stackwallet/models/epicbox_config_model.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/epic_transaction.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; @@ -48,6 +49,7 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/stack_file_system.dart'; import 'package:stackwallet/utilities/test_epic_box_connection.dart'; +import 'package:stackwallet/wallets/example/libepiccash.dart' as epiccash; import 'package:tuple/tuple.dart'; import 'package:websocket_universal/websocket_universal.dart'; @@ -71,166 +73,6 @@ abstract class ListenerManager { static Pointer? pointer; } -// isolate - -Map isolates = {}; - -Future getIsolate(Map arguments, - {String name = ""}) async { - ReceivePort receivePort = - ReceivePort(); //port for isolate to receive messages. - arguments['sendPort'] = receivePort.sendPort; - Logging.instance.log("starting isolate ${arguments['function']} name: $name", - level: LogLevel.Info); - Isolate isolate = await Isolate.spawn(executeNative, arguments); - isolates[receivePort] = isolate; - return receivePort; -} - -Future executeNative(Map arguments) async { - await Logging.instance.initInIsolate(); - final SendPort sendPort = arguments['sendPort'] as SendPort; - final function = arguments['function'] as String; - try { - if (function == "scanOutPuts") { - final wallet = arguments['wallet'] as String?; - final startHeight = arguments['startHeight'] as int?; - final numberOfBlocks = arguments['numberOfBlocks'] as int?; - Map result = {}; - if (!(wallet == null || startHeight == null || numberOfBlocks == null)) { - var outputs = await scanOutPuts(wallet, startHeight, numberOfBlocks); - result['outputs'] = outputs; - sendPort.send(result); - return; - } - } else if (function == "getWalletInfo") { - final wallet = arguments['wallet'] as String?; - final refreshFromNode = arguments['refreshFromNode'] as int?; - final minimumConfirmations = arguments['minimumConfirmations'] as int?; - Map result = {}; - if (!(wallet == null || - refreshFromNode == null || - minimumConfirmations == null)) { - var res = - await getWalletInfo(wallet, refreshFromNode, minimumConfirmations); - result['result'] = res; - sendPort.send(result); - return; - } - } else if (function == "getTransactions") { - final wallet = arguments['wallet'] as String?; - final refreshFromNode = arguments['refreshFromNode'] as int?; - Map result = {}; - if (!(wallet == null || refreshFromNode == null)) { - var res = await getTransactions(wallet, refreshFromNode); - result['result'] = res; - sendPort.send(result); - return; - } - } else if (function == "startSync") { - final wallet = arguments['wallet'] as String?; - const int refreshFromNode = 1; - Map result = {}; - if (!(wallet == null)) { - var res = await getWalletInfo(wallet, refreshFromNode, 10); - result['result'] = res; - sendPort.send(result); - return; - } - } else if (function == "getTransactionFees") { - final wallet = arguments['wallet'] as String?; - final amount = arguments['amount'] as int?; - final minimumConfirmations = arguments['minimumConfirmations'] as int?; - Map result = {}; - if (!(wallet == null || amount == null || minimumConfirmations == null)) { - var res = - await getTransactionFees(wallet, amount, minimumConfirmations); - result['result'] = res; - sendPort.send(result); - return; - } - } else if (function == "createTransaction") { - final wallet = arguments['wallet'] as String?; - final amount = arguments['amount'] as int?; - final address = arguments['address'] as String?; - final secretKeyIndex = arguments['secretKeyIndex'] as int?; - final epicboxConfig = arguments['epicboxConfig'] as String?; - final minimumConfirmations = arguments['minimumConfirmations'] as int?; - final onChainNote = arguments['onChainNote'] as String?; - - Map result = {}; - if (!(wallet == null || - amount == null || - address == null || - secretKeyIndex == null || - epicboxConfig == null || - minimumConfirmations == null)) { - var res = await createTransaction(wallet, amount, address, - secretKeyIndex, epicboxConfig, minimumConfirmations, onChainNote!); - result['result'] = res; - sendPort.send(result); - return; - } - } else if (function == "txHttpSend") { - final wallet = arguments['wallet'] as String?; - final selectionStrategyIsAll = - arguments['selectionStrategyIsAll'] as int?; - final minimumConfirmations = arguments['minimumConfirmations'] as int?; - final message = arguments['message'] as String?; - final amount = arguments['amount'] as int?; - final address = arguments['address'] as String?; - - Map result = {}; - - if (!(wallet == null || - selectionStrategyIsAll == null || - minimumConfirmations == null || - message == null || - amount == null || - address == null)) { - var res = await txHttpSend(wallet, selectionStrategyIsAll, - minimumConfirmations, message, amount, address); - result['result'] = res; - sendPort.send(result); - return; - } - } - - Logging.instance.log( - "Error Arguments for $function not formatted correctly", - level: LogLevel.Fatal); - sendPort.send("Error Arguments for $function not formatted correctly"); - } catch (e, s) { - Logging.instance.log( - "An error was thrown in this isolate $function: $e\n$s", - level: LogLevel.Error); - sendPort - .send("Error An error was thrown in this isolate $function: $e\n$s"); - } finally { - await Logging.instance.isar?.close(); - } -} - -void stop(ReceivePort port) { - Isolate? isolate = isolates.remove(port); - if (isolate != null) { - isolate.kill(priority: Isolate.immediate); - isolate = null; - } -} - -// Keep Wrapper functions outside of the class to avoid memory leaks and errors about receive ports and illegal arguments. -// TODO: Can get rid of this wrapper and call it in a full isolate instead of compute() if we want more control over this -Future _cancelTransactionWrapper(Tuple2 data) async { - // assuming this returns an empty string on success - // or an error message string on failure - return cancelTransaction(data.item1, data.item2); -} - -Future _deleteWalletWrapper(Tuple2 data) async { - return deleteWallet(data.item1, data.item2); -} - Future deleteEpicWallet({ required String walletId, required SecureStorageInterface secureStore, @@ -254,7 +96,10 @@ Future deleteEpicWallet({ return "Tried to delete non existent epic wallet file with walletId=$walletId"; } else { try { - return _deleteWalletWrapper(Tuple2(wallet, config!)); + return epiccash.LibEpiccash.deleteWallet( + wallet: wallet, + config: config!, + ); } catch (e, s) { Logging.instance.log("$e\n$s", level: LogLevel.Error); return "deleteEpicWallet($walletId) failed..."; @@ -262,34 +107,6 @@ Future deleteEpicWallet({ } } -Future _initWalletWrapper( - Tuple4 data) async { - final String initWalletStr = - initWallet(data.item1, data.item2, data.item3, data.item4); - return initWalletStr; -} - -Future _initGetAddressInfoWrapper( - Tuple3 data) async { - String walletAddress = getAddressInfo(data.item1, data.item2, data.item3); - return walletAddress; -} - -Future _walletMnemonicWrapper(int throwaway) async { - final String mnemonic = walletMnemonic(); - return mnemonic; -} - -Future _recoverWrapper( - Tuple4 data) async { - return recoverWallet(data.item1, data.item2, data.item3, data.item4); -} - -Future _getChainHeightWrapper(String config) async { - final int chainHeight = getChainHeight(config); - return chainHeight; -} - class EpicCashWallet extends CoinServiceAPI with WalletCache, WalletDB, EpicCashHive { EpicCashWallet({ @@ -306,13 +123,6 @@ class EpicCashWallet extends CoinServiceAPI initCache(walletId, coin); initEpicCashHive(walletId); initWalletDB(mockableOverride: mockableOverride); - - Logging.instance.log("$walletName isolate length: ${isolates.length}", - level: LogLevel.Info); - for (final isolate in isolates.values) { - isolate.kill(priority: Isolate.immediate); - } - isolates.clear(); } static const integrationTestFlag = @@ -355,62 +165,33 @@ class EpicCashWallet extends CoinServiceAPI Future startSync() async { Logging.instance.log("request start sync", level: LogLevel.Info); final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - + const int refreshFromNode = 1; if (!syncMutex.isLocked) { - await syncMutex.protect(() async { - Logging.instance.log("sync started", level: LogLevel.Info); - ReceivePort receivePort = await getIsolate({ - "function": "startSync", - "wallet": wallet!, - }, name: walletName); - this.receivePort = receivePort; - - var message = await receivePort.first; - if (message is String) { - Logging.instance - .log("this is a string $message", level: LogLevel.Error); - stop(receivePort); - throw Exception("startSync isolate failed"); - } - stop(receivePort); - Logging.instance - .log('Closing startSync!\n $message', level: LogLevel.Info); - Logging.instance.log("sync ended", level: LogLevel.Info); - }); + await epiccash.LibEpiccash.getWalletBalances( + wallet: wallet!, + refreshFromNode: refreshFromNode, + minimumConfirmations: 10, + ); } else { Logging.instance.log("request start sync denied", level: LogLevel.Info); } return ""; } - Future allWalletBalances() async { + Future< + ({ + double awaitingFinalization, + double pending, + double spendable, + double total + })> allWalletBalances() async { final wallet = await _secureStore.read(key: '${_walletId}_wallet'); const refreshFromNode = 0; - - dynamic message; - await m.protect(() async { - ReceivePort receivePort = await getIsolate({ - "function": "getWalletInfo", - "wallet": wallet!, - "refreshFromNode": refreshFromNode, - "minimumConfirmations": MINIMUM_CONFIRMATIONS, - }, name: walletName); - - message = await receivePort.first; - if (message is String) { - Logging.instance - .log("this is a string $message", level: LogLevel.Error); - stop(receivePort); - throw Exception("getWalletInfo isolate failed"); - } - stop(receivePort); - Logging.instance - .log('Closing getWalletInfo!\n $message', level: LogLevel.Info); - }); - - // return message; - final String walletBalances = message['result'] as String; - return walletBalances; + return await epiccash.LibEpiccash.getWalletBalances( + wallet: wallet!, + refreshFromNode: refreshFromNode, + minimumConfirmations: MINIMUM_CONFIRMATIONS, + ); } Timer? timer; @@ -428,15 +209,10 @@ class EpicCashWallet extends CoinServiceAPI key: '${_walletId}_wallet', ))!; - final result = await m.protect(() async { - return await compute( - _cancelTransactionWrapper, - Tuple2( - wallet, - txSlateId, - ), - ); - }); + final result = await epiccash.LibEpiccash.cancelTransaction( + wallet: wallet, + transactionId: txSlateId, + ); Logging.instance.log( "cancel $txSlateId result: $result", level: LogLevel.Info, @@ -452,12 +228,10 @@ class EpicCashWallet extends CoinServiceAPI Future confirmSend({required Map txData}) async { try { final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); // TODO determine whether it is worth sending change to a change address. - dynamic message; - + String slateId; String receiverAddress = txData['addresss'] as String; if (!receiverAddress.startsWith("http://") || @@ -469,88 +243,40 @@ class EpicCashWallet extends CoinServiceAPI } } - await m.protect(() async { - if (receiverAddress.startsWith("http://") || - receiverAddress.startsWith("https://")) { - const int selectionStrategyIsAll = 0; - ReceivePort receivePort = await getIsolate({ - "function": "txHttpSend", - "wallet": wallet!, - "selectionStrategyIsAll": selectionStrategyIsAll, - "minimumConfirmations": MINIMUM_CONFIRMATIONS, - "message": txData['onChainNote'], - "amount": (txData['recipientAmt'] as Amount).raw.toInt(), - "address": txData['addresss'] as String, - }, name: walletName); + ({String commitId, String slateId}) transaction; - message = await receivePort.first; - if (message is String) { - Logging.instance - .log("this is a string $message", level: LogLevel.Error); - stop(receivePort); - throw Exception(message); - } - stop(receivePort); - Logging.instance - .log('Closing txHttpSend!\n $message', level: LogLevel.Info); - } else { - ReceivePort receivePort = await getIsolate({ - "function": "createTransaction", - "wallet": wallet!, - "amount": (txData['recipientAmt'] as Amount).raw.toInt(), - "address": txData['addresss'] as String, - "secretKeyIndex": 0, - "epicboxConfig": epicboxConfig.toString(), - "minimumConfirmations": MINIMUM_CONFIRMATIONS, - "onChainNote": txData['onChainNote'], - }, name: walletName); - - message = await receivePort.first; - if (message is String) { - Logging.instance - .log("this is a string $message", level: LogLevel.Error); - stop(receivePort); - throw Exception("createTransaction isolate failed"); - } - stop(receivePort); - Logging.instance.log('Closing createTransaction!\n $message', - level: LogLevel.Info); - } - }); - - // return message; - final String sendTx = message['result'] as String; - if (sendTx.contains("Error")) { - throw BadEpicHttpAddressException(message: sendTx); + if (receiverAddress.startsWith("http://") || + receiverAddress.startsWith("https://")) { + transaction = await epiccash.LibEpiccash.txHttpSend( + wallet: wallet!, + selectionStrategyIsAll: 0, + minimumConfirmations: MINIMUM_CONFIRMATIONS, + message: txData['onChainNote'] as String, + amount: (txData['recipientAmt'] as Amount).raw.toInt(), + address: txData['addresss'] as String); + } else { + transaction = await epiccash.LibEpiccash.createTransaction( + wallet: wallet!, + amount: (txData['recipientAmt'] as Amount).raw.toInt(), + address: txData['addresss'] as String, + secretKeyIndex: 0, + epicboxConfig: epicboxConfig.toString(), + minimumConfirmations: MINIMUM_CONFIRMATIONS, + note: txData['onChainNote'] as String); } Map txAddressInfo = {}; txAddressInfo['from'] = await currentReceivingAddress; txAddressInfo['to'] = txData['addresss'] as String; - await putSendToAddresses(sendTx, txAddressInfo); + await putSendToAddresses(transaction, txAddressInfo); - Logging.instance.log("CONFIRM_RESULT_IS $sendTx", level: LogLevel.Info); - - final decodeData = json.decode(sendTx); - - if (decodeData[0] == "transaction_failed") { - String errorMessage = decodeData[1] as String; - throw Exception("Transaction failed with error code $errorMessage"); - } else { - final txCreateResult = decodeData[0]; - // //TODO: second problem - final transaction = json.decode(txCreateResult as String); - - final tx = transaction[0]; - final txLogEntry = json.decode(tx as String); - final txLogEntryFirst = txLogEntry[0]; - final slateId = txLogEntryFirst['tx_slate_id'] as String; - return slateId!; - } + slateId = transaction.slateId; + return slateId; } catch (e, s) { Logging.instance.log("Error sending $e - $s", level: LogLevel.Error); rethrow; } + // return ""; } Future _getReceivingAddressForIndex( @@ -570,13 +296,12 @@ class EpicCashWallet extends CoinServiceAPI final wallet = await _secureStore.read(key: '${_walletId}_wallet'); EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); - String? walletAddress; - await m.protect(() async { - walletAddress = await compute( - _initGetAddressInfoWrapper, - Tuple3(wallet!, index, epicboxConfig.toString()), - ); - }); + String? walletAddress = await epiccash.LibEpiccash.getAddressInfo( + wallet: wallet!, + index: index, + epicboxConfig: epicboxConfig.toString(), + ); + Logging.instance .log("WALLET_ADDRESS_IS $walletAddress", level: LogLevel.Info); @@ -616,10 +341,6 @@ class EpicCashWallet extends CoinServiceAPI timer?.cancel(); timer = null; stopNetworkAlivePinging(); - for (final isolate in isolates.values) { - isolate.kill(priority: Isolate.immediate); - } - isolates.clear(); Logging.instance.log("EpicCash_wallet exit finished", level: LogLevel.Info); } @@ -700,7 +421,8 @@ class EpicCashWallet extends CoinServiceAPI final config = await getRealConfig(); final password = await _secureStore.read(key: '${_walletId}_password'); - final walletOpen = openWallet(config, password!); + final walletOpen = await epiccash.LibEpiccash.openWallet( + config: config, password: password!); await _secureStore.write(key: '${_walletId}_wallet', value: walletOpen); if (getCachedId() == null) { @@ -722,20 +444,16 @@ class EpicCashWallet extends CoinServiceAPI Logging.instance.log("This index is $index", level: LogLevel.Info); EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); - String? walletAddress; - await m.protect(() async { - walletAddress = await compute( - _initGetAddressInfoWrapper, - Tuple3(wallet!, index, epicboxConfig.toString()), - ); - }); + String? walletAddress = await epiccash.LibEpiccash.getAddressInfo( + wallet: wallet!, + index: index, + epicboxConfig: epicboxConfig.toString(), + ); + Logging.instance .log("WALLET_ADDRESS_IS $walletAddress", level: LogLevel.Info); - Logging.instance - .log("Wallet address is $walletAddress", level: LogLevel.Info); - String addressInfo = walletAddress!; await _secureStore.write( - key: '${_walletId}_address_info', value: addressInfo); + key: '${_walletId}_address_info', value: walletAddress); } // TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index @@ -777,20 +495,16 @@ class EpicCashWallet extends CoinServiceAPI String name = _walletId; - await m.protect(() async { - await compute( - _initWalletWrapper, - Tuple4( - stringConfig, - mnemonicString, - password, - name, - ), - ); - }); + await epiccash.LibEpiccash.initializeNewWallet( + config: stringConfig, + mnemonic: mnemonicString, + password: password, + name: name, + ); //Open wallet - final walletOpen = openWallet(stringConfig, password); + final walletOpen = await epiccash.LibEpiccash.openWallet( + config: stringConfig, password: password); await _secureStore.write(key: '${_walletId}_wallet', value: walletOpen); //Store Epic box address info @@ -828,15 +542,10 @@ class EpicCashWallet extends CoinServiceAPI final List data = _mnemonicString.split(' '); return data; } else { - await m.protect(() async { - _mnemonicString = await compute( - _walletMnemonicWrapper, - 0, - ); - }); + _mnemonicString = epiccash.LibEpiccash.getMnemonic(); await _secureStore.write( key: '${_walletId}_mnemonic', value: _mnemonicString); - final List data = _mnemonicString!.split(' '); + final List data = _mnemonicString.split(' '); return data; } } @@ -887,120 +596,24 @@ class EpicCashWallet extends CoinServiceAPI Future nativeFee(int satoshiAmount, {bool ifErrorEstimateFee = false}) async { final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - try { - String? transactionFees; - await m.protect(() async { - ReceivePort receivePort = await getIsolate({ - "function": "getTransactionFees", - "wallet": wallet!, - "amount": satoshiAmount, - "minimumConfirmations": MINIMUM_CONFIRMATIONS, - }, name: walletName); - - var message = await receivePort.first; - if (message is String) { - Logging.instance - .log("this is a string $message", level: LogLevel.Error); - stop(receivePort); - throw Exception("getTransactionFees isolate failed"); - } - stop(receivePort); - Logging.instance.log('Closing getTransactionFees!\n $message', - level: LogLevel.Info); - // return message; - transactionFees = message['result'] as String; - }); - debugPrint(transactionFees); - dynamic decodeData; - final available = balance.spendable.raw.toInt(); - if (available == satoshiAmount) { - if (transactionFees!.contains("Required")) { - var splits = transactionFees!.split(" "); - Decimal required = Decimal.zero; - Decimal available = Decimal.zero; - for (int i = 0; i < splits.length; i++) { - var word = splits[i]; - if (word == "Required:") { - required = Decimal.parse(splits[i + 1].replaceAll(",", "")); - } else if (word == "Available:") { - available = Decimal.parse(splits[i + 1].replaceAll(",", "")); - } - } - int largestSatoshiFee = - ((required - available) * Decimal.fromInt(100000000)) - .toBigInt() - .toInt(); - var amountSending = satoshiAmount - largestSatoshiFee; + var transactionFees = await epiccash.LibEpiccash.getTransactionFees( + wallet: wallet!, + amount: satoshiAmount, + minimumConfirmations: MINIMUM_CONFIRMATIONS, + available: available, + ); - //Get fees for this new amount - await m.protect(() async { - ReceivePort receivePort = await getIsolate({ - "function": "getTransactionFees", - "wallet": wallet!, - "amount": amountSending, - "minimumConfirmations": MINIMUM_CONFIRMATIONS, - }, name: walletName); - - var message = await receivePort.first; - if (message is String) { - Logging.instance - .log("this is a string $message", level: LogLevel.Error); - stop(receivePort); - throw Exception("getTransactionFees isolate failed"); - } - stop(receivePort); - Logging.instance.log('Closing getTransactionFees!\n $message', - level: LogLevel.Info); - // return message; - transactionFees = message['result'] as String; - }); - } - decodeData = json.decode(transactionFees!); - } else { - try { - decodeData = json.decode(transactionFees!); - } catch (e) { - if (ifErrorEstimateFee) { - //Error Not enough funds. Required: 0.56500000, Available: 0.56200000 - if (transactionFees!.contains("Required")) { - var splits = transactionFees!.split(" "); - Decimal required = Decimal.zero; - Decimal available = Decimal.zero; - for (int i = 0; i < splits.length; i++) { - var word = splits[i]; - if (word == "Required:") { - required = Decimal.parse(splits[i + 1].replaceAll(",", "")); - } else if (word == "Available:") { - available = Decimal.parse(splits[i + 1].replaceAll(",", "")); - } - } - int largestSatoshiFee = - ((required - available) * Decimal.fromInt(100000000)) - .toBigInt() - .toInt(); - Logging.instance.log("largestSatoshiFee $largestSatoshiFee", - level: LogLevel.Info); - return largestSatoshiFee; - } - } - rethrow; - } - } - - //TODO: first problem int realfee = 0; try { - var txObject = decodeData[0]; realfee = - (Decimal.parse(txObject["fee"].toString())).toBigInt().toInt(); + (Decimal.parse(transactionFees.fee.toString())).toBigInt().toInt(); } catch (e, s) { //todo: come back to this debugPrint("$e $s"); } - return realfee; } catch (e, s) { Logging.instance.log("Error getting fees $e - $s", level: LogLevel.Error); @@ -1186,42 +799,11 @@ class EpicCashWallet extends CoinServiceAPI level: LogLevel.Info, ); - final int nextScannedBlock = await m.protect(() async { - ReceivePort? receivePort; - try { - receivePort = await getIsolate({ - "function": "scanOutPuts", - "wallet": wallet!, - "startHeight": lastScannedBlock, - "numberOfBlocks": scanChunkSize, - }, name: walletName); - - // get response - final message = await receivePort.first; - - // check for error message - if (message is String) { - throw Exception("scanOutPuts isolate failed: $message"); - } - - // attempt to grab next scanned block number - final nextScanned = int.tryParse(message['outputs'] as String); - if (nextScanned == null) { - throw Exception( - "scanOutPuts failed to parse next scanned block number from: $message", - ); - } - - return nextScanned; - } catch (_) { - rethrow; - } finally { - if (receivePort != null) { - // kill isolate - stop(receivePort); - } - } - }); + int nextScannedBlock = int.parse(await epiccash.LibEpiccash.scanOutputs( + wallet: wallet!, + startHeight: lastScannedBlock, + numberOfBlocks: scanChunkSize, + )); // update local cache await epicUpdateLastScannedBlock(nextScannedBlock); @@ -1292,14 +874,11 @@ class EpicCashWallet extends CoinServiceAPI await _secureStore.write( key: '${_walletId}_epicboxConfig', value: epicboxConfig.toString()); - await compute( - _recoverWrapper, - Tuple4( - stringConfig, - password, - mnemonic, - name, - ), + await epiccash.LibEpiccash.recoverWallet( + config: stringConfig, + password: password, + mnemonic: mnemonic, + name: name, ); await Future.wait([ @@ -1311,7 +890,8 @@ class EpicCashWallet extends CoinServiceAPI ]); //Open Wallet - final walletOpen = openWallet(stringConfig, password); + final walletOpen = await epiccash.LibEpiccash.openWallet( + config: stringConfig, password: password); await _secureStore.write(key: '${_walletId}_wallet', value: walletOpen); //Store Epic box address info @@ -1339,16 +919,11 @@ class EpicCashWallet extends CoinServiceAPI Future get chainHeight async { try { final config = await getRealConfig(); - int? latestHeight; - await m.protect(() async { - latestHeight = await compute( - _getChainHeightWrapper, - config, - ); - }); + int? latestHeight = + await epiccash.LibEpiccash.getChainHeight(config: config); - await updateCachedChainHeight(latestHeight!); - if (latestHeight! > storedChainHeight) { + await updateCachedChainHeight(latestHeight); + if (latestHeight > storedChainHeight) { GlobalEventBus.instance.fire( UpdatedInBackgroundEvent( "Updated current chain height in $walletId $walletName!", @@ -1356,7 +931,7 @@ class EpicCashWallet extends CoinServiceAPI ), ); } - return latestHeight!; + return latestHeight; } catch (e, s) { Logging.instance.log("Exception caught in chainHeight: $e\n$s", level: LogLevel.Error); @@ -1437,21 +1012,14 @@ class EpicCashWallet extends CoinServiceAPI } } - Future putSendToAddresses( - String slateMessage, Map txAddressInfo) async { + Future putSendToAddresses(({String slateId, String commitId}) slateData, + Map txAddressInfo) async { try { var slatesToCommits = await getSlatesToCommits(); - final slate0 = jsonDecode(slateMessage); - final slate = jsonDecode(slate0[0] as String); - final part1 = jsonDecode(slate[0] as String); - final part2 = jsonDecode(slate[1] as String); - final slateId = part1[0]['tx_slate_id']; - final commitId = part2['tx']['body']['outputs'][0]['commit']; - final from = txAddressInfo['from']; final to = txAddressInfo['to']; - slatesToCommits[slateId] = { - "commitId": commitId, + slatesToCommits[slateData.slateId] = { + "commitId": slateData.commitId, "from": from, "to": to, }; @@ -1669,78 +1237,57 @@ class EpicCashWallet extends CoinServiceAPI bool get isConnected => _isConnected; Future _refreshTransactions() async { - // final currentChainHeight = await chainHeight; final wallet = await _secureStore.read(key: '${_walletId}_wallet'); const refreshFromNode = 1; - dynamic message; - await m.protect(() async { - ReceivePort receivePort = await getIsolate({ - "function": "getTransactions", - "wallet": wallet!, - "refreshFromNode": refreshFromNode, - }, name: walletName); - - message = await receivePort.first; - if (message is String) { - Logging.instance - .log("this is a string $message", level: LogLevel.Error); - stop(receivePort); - throw Exception("getTransactions isolate failed"); - } - stop(receivePort); - Logging.instance - .log('Closing getTransactions!\n $message', level: LogLevel.Info); - }); - // return message; - final String transactions = message['result'] as String; - final jsonTransactions = json.decode(transactions) as List; + var transactions = await epiccash.LibEpiccash.getTransactions( + wallet: wallet!, refreshFromNode: refreshFromNode); final List> txnsData = []; final slatesToCommits = await getSlatesToCommits(); - for (var tx in jsonTransactions) { + for (var tx in transactions) { Logging.instance.log("tx: $tx", level: LogLevel.Info); // // TODO: does "confirmed" mean finalized? If so please remove this todo - final isConfirmed = tx["confirmed"] as bool; + final isConfirmed = tx.confirmed; int amt = 0; - if (tx["tx_type"] == "TxReceived" || - tx["tx_type"] == "TxReceivedCancelled") { - amt = int.parse(tx['amount_credited'] as String); + if (tx.txType == EpicTransactionType.TxReceived || + tx.txType == EpicTransactionType.TxReceivedCancelled) { + amt = int.parse(tx.amountCredited); } else { - int debit = int.parse(tx['amount_debited'] as String); - int credit = int.parse(tx['amount_credited'] as String); - int fee = int.parse((tx['fee'] ?? "0") as String); + int debit = int.parse(tx.amountDebited); + int credit = int.parse(tx.amountCredited); + int fee = int.parse((tx.fee ?? "0")); //TODO -double check this amt = debit - credit - fee; } - DateTime dt = DateTime.parse(tx["creation_ts"] as String); + DateTime dt = DateTime.parse(tx.creationTs); - String? slateId = tx['tx_slate_id'] as String?; + String? slateId = tx.txSlateId == "null" ? null : tx.txSlateId; String address = slatesToCommits[slateId] - ?[tx["tx_type"] == "TxReceived" ? "from" : "to"] as String? ?? + ?[tx.txType == EpicTransactionType.TxReceived ? "from" : "to"] + as String? ?? ""; String? commitId = slatesToCommits[slateId]?['commitId'] as String?; - tx['numberOfMessages'] = tx['messages']?['messages']?.length; - tx['onChainNote'] = tx['messages']?['messages']?[0]?['message']; + int? numberOfMessages = tx.messages?.messages.length; + String? onChainNote = tx.messages?.messages[0].message; int? height; if (isConfirmed) { - height = tx["kernel_lookup_min_height"] as int? ?? 1; + height = tx.kernelLookupMinHeight ?? 1; } else { height = null; } - final isIncoming = (tx["tx_type"] == "TxReceived" || - tx["tx_type"] == "TxReceivedCancelled"); - + final isIncoming = (tx.txType == EpicTransactionType.TxReceived || + tx.txType == EpicTransactionType.TxReceivedCancelled); final txn = isar_models.Transaction( walletId: walletId, - txid: commitId ?? tx["id"].toString(), + txid: commitId ?? tx.id.toString(), timestamp: (dt.millisecondsSinceEpoch ~/ 1000), type: isIncoming ? isar_models.TransactionType.incoming @@ -1751,20 +1298,17 @@ class EpicCashWallet extends CoinServiceAPI rawValue: BigInt.from(amt), fractionDigits: coin.decimals, ).toJsonString(), - fee: (tx["fee"] == null) ? 0 : int.parse(tx["fee"] as String), + fee: (tx.fee == "null") ? 0 : int.parse(tx.fee!), height: height, - isCancelled: tx["tx_type"] == "TxSentCancelled" || - tx["tx_type"] == "TxReceivedCancelled", + isCancelled: tx.txType == EpicTransactionType.TxSentCancelled || + tx.txType == EpicTransactionType.TxReceivedCancelled, isLelantus: false, slateId: slateId, nonce: null, - // otherData: tx["id"].toString(), - otherData: tx['onChainNote'].toString(), + otherData: onChainNote, inputs: [], outputs: [], - numberOfMessages: ((tx["numberOfMessages"] == null) - ? 0 - : tx["numberOfMessages"]) as int, + numberOfMessages: numberOfMessages, ); // txn.address = @@ -1775,11 +1319,13 @@ class EpicCashWallet extends CoinServiceAPI .valueEqualTo(address) .findFirst(); - if (transactionAddress == null) { + if (transactionAddress!.value.isEmpty) { if (isIncoming) { + //Use current receiving address as address + String receivingAddress = await currentReceivingAddress; transactionAddress = isar_models.Address( walletId: walletId, - value: address, + value: receivingAddress, publicKey: [], derivationIndex: 0, derivationPath: null, @@ -1830,7 +1376,7 @@ class EpicCashWallet extends CoinServiceAPI @override bool validateAddress(String address) { - //Invalid address that contains HTTP and epicbox domain + // Invalid address that contains HTTP and epicbox domain if ((address.startsWith("http://") || address.startsWith("https://")) && address.contains("@")) { return false; @@ -1840,17 +1386,7 @@ class EpicCashWallet extends CoinServiceAPI return true; } } - - String validate = validateSendAddress(address); - if (int.parse(validate) == 1) { - //Check if address contrains a domain - if (address.contains("@")) { - return true; - } - return false; - } else { - return false; - } + return epiccash.LibEpiccash.validateSendAddress(address: address); } @override @@ -1870,11 +1406,6 @@ class EpicCashWallet extends CoinServiceAPI timer = null; if (isActive) { unawaited(startSync()); - } else { - for (final isolate in isolates.values) { - isolate.kill(priority: Isolate.immediate); - } - isolates.clear(); } this.isActive = isActive; }; @@ -1905,26 +1436,15 @@ class EpicCashWallet extends CoinServiceAPI } Future _refreshBalance() async { - String walletBalances = await allWalletBalances(); - var jsonBalances = json.decode(walletBalances); - - final spendable = - (jsonBalances['amount_currently_spendable'] as double).toString(); - - final pending = - (jsonBalances['amount_awaiting_confirmation'] as double).toString(); - - final total = (jsonBalances['total'] as double).toString(); - final awaiting = - (jsonBalances['amount_awaiting_finalization'] as double).toString(); - + var balances = await allWalletBalances(); _balance = Balance( total: Amount.fromDecimal( - Decimal.parse(total) + Decimal.parse(awaiting), + Decimal.parse(balances.total.toString()) + + Decimal.parse(balances.awaitingFinalization.toString()), fractionDigits: coin.decimals, ), spendable: Amount.fromDecimal( - Decimal.parse(spendable), + Decimal.parse(balances.spendable.toString()), fractionDigits: coin.decimals, ), blockedTotal: Amount( @@ -1932,11 +1452,10 @@ class EpicCashWallet extends CoinServiceAPI fractionDigits: coin.decimals, ), pendingSpendable: Amount.fromDecimal( - Decimal.parse(pending), + Decimal.parse(balances.pending.toString()), fractionDigits: coin.decimals, ), ); - await updateCachedBalance(_balance!); } diff --git a/lib/wallets/crypto_currency/coins/epiccash.dart b/lib/wallets/crypto_currency/coins/epiccash.dart index eb8d50682..b0e53797e 100644 --- a/lib/wallets/crypto_currency/coins/epiccash.dart +++ b/lib/wallets/crypto_currency/coins/epiccash.dart @@ -32,4 +32,201 @@ class Epiccash extends Bip39Currency { return LibEpiccash.validateSendAddress(address: address); } + + String getMnemonic() { + return LibEpiccash.getMnemonic(); + } + + Future createNewWallet( + ({ + String config, + String mnemonic, + String password, + String name, + })? data) async { + String result = await LibEpiccash.initializeNewWallet( + config: data!.config, + mnemonic: data.mnemonic, + password: data.password, + name: data.name); + + if (result.isNotEmpty) { + return result; + } + return null; + } + + Future<({double awaitingFinalization, double pending, double spendable, double total})> + getWalletInfo( + ({ + String wallet, + int refreshFromNode, + })? data) async { + var result = await LibEpiccash.getWalletBalances( + wallet: data!.wallet, + refreshFromNode: data.refreshFromNode, + minimumConfirmations: minConfirms); + return result; + } + + Future scanOutputs( + ({String wallet, int startHeight, int numberOfBlocks})? data) async { + var result = await LibEpiccash.scanOutputs( + wallet: data!.wallet, + startHeight: data.startHeight, + numberOfBlocks: data.numberOfBlocks, + ); + + if (result.isNotEmpty) { + return result; + } + return null; + } + + Future createTransaction( + ({ + String wallet, + int amount, + String address, + int secretKey, + String epicboxConfig, + String note, + })? data) async { + var result = await LibEpiccash.createTransaction( + wallet: data!.wallet, + amount: data.amount, + address: data.address, + secretKey: data.secretKey, + epicboxConfig: data.epicboxConfig, + minimumConfirmations: minConfirms, + note: data.note, + ); + + if (result.isNotEmpty) { + return result; + } + return null; + } + + Future getTransaction( + ({ + String wallet, + int refreshFromNode, + })? data) async { + var result = await LibEpiccash.getTransaction( + wallet: data!.wallet, + refreshFromNode: data.refreshFromNode, + ); + + if (result.isNotEmpty) { + return result; + } + return null; + } + + Future cancelTransaction( + ({ + String wallet, + String transactionId, + })? data) async { + var result = await LibEpiccash.cancelTransaction( + wallet: data!.wallet, + transactionId: data.transactionId, + ); + + if (result.isNotEmpty) { + return result; + } + return null; + } + + Future getAddressInfo( + ({ + String wallet, + int index, + String epicboxConfig, + })? data) async { + var result = await LibEpiccash.getAddressInfo( + wallet: data!.wallet, + index: data.index, + epicboxConfig: data.epicboxConfig, + ); + + if (result.isNotEmpty) { + return result; + } + return null; + } + + Future<({int fee, bool strategyUseAll, int total})> transactionFees( + ({ + String wallet, + int amount, + int availableAmount, + })? data, + ) async { + var result = await LibEpiccash.getTransactionFees( + wallet: data!.wallet, + amount: data.amount, + minimumConfirmations: minConfirms, + available: data.availableAmount, + ); + return result; + } + + Future deleteWallet( + ({ + String wallet, + String config, + })? data, + ) async { + var result = await LibEpiccash.deleteWallet( + wallet: data!.wallet, + config: data.config, + ); + if (result.isNotEmpty) { + return result; + } + return null; + } + + Future openWallet( + ({ + String config, + String password, + })? data, + ) async { + var result = await LibEpiccash.openWallet( + config: data!.config, + password: data.password, + ); + if (result.isNotEmpty) { + return result; + } + return null; + } + + Future txHttpSend( + ({ + String wallet, + int selectionStrategyIsAll, + int minimumConfirmations, + String message, + int amount, + String address, + })? data, + ) async { + var result = await LibEpiccash.txHttpSend( + wallet: data!.wallet, + selectionStrategyIsAll: data.selectionStrategyIsAll, + minimumConfirmations: data.minimumConfirmations, + message: data.message, + amount: data.amount, + address: data.address, + ); + if (result.isNotEmpty) { + return result; + } + return null; + } } diff --git a/lib/wallets/example/libepiccash.dart b/lib/wallets/example/libepiccash.dart index 1f7d45e59..28619e94a 100644 --- a/lib/wallets/example/libepiccash.dart +++ b/lib/wallets/example/libepiccash.dart @@ -1,4 +1,10 @@ +import 'dart:convert'; + +import 'package:decimal/decimal.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_libepiccash/epic_cash.dart' as lib_epiccash; +import 'package:mutex/mutex.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/epic_transaction.dart'; /// /// Wrapped up calls to flutter_libepiccash. @@ -6,6 +12,11 @@ import 'package:flutter_libepiccash/epic_cash.dart' as lib_epiccash; /// Should all be static calls (no state stored in this class) /// abstract class LibEpiccash { + static final Mutex _mutex = Mutex(); + + /// + /// Check if [address] is a valid epiccash address according to libepiccash + /// static bool validateSendAddress({required String address}) { final String validate = lib_epiccash.validateSendAddress(address); if (int.parse(validate) == 1) { @@ -18,4 +29,606 @@ abstract class LibEpiccash { return false; } } + + /// + /// Fetch the mnemonic For a new wallet (Only used in the example app) + /// + // TODO: ensure the above documentation comment is correct + // TODO: ensure this will always return the mnemonic. If not, this function should throw an exception + //Function is used in _getMnemonicList() + static String getMnemonic() { + try { + String mnemonic = lib_epiccash.walletMnemonic(); + if (mnemonic.isEmpty) { + throw Exception("Error getting mnemonic, returned empty string"); + } + return mnemonic; + } catch (e) { + throw Exception(e.toString()); + } + } + + // Private function wrapper for compute + static Future _initializeWalletWrapper( + ({ + String config, + String mnemonic, + String password, + String name, + }) data, + ) async { + final String initWalletStr = lib_epiccash.initWallet( + data.config, + data.mnemonic, + data.password, + data.name, + ); + return initWalletStr; + } + + /// + /// Create a new epiccash wallet. + /// + // TODO: Complete/modify the documentation comment above + // TODO: Should return a void future. On error this function should throw and exception + static Future initializeNewWallet({ + required String config, + required String mnemonic, + required String password, + required String name, + }) async { + try { + return await compute( + _initializeWalletWrapper, + ( + config: config, + mnemonic: mnemonic, + password: password, + name: name, + ), + ); + } catch (e) { + throw ("Error creating new wallet : ${e.toString()}"); + } + } + + /// + /// Private function wrapper for wallet balances + /// + static Future _walletBalancesWrapper( + ({String wallet, int refreshFromNode, int minimumConfirmations}) data, + ) async { + return lib_epiccash.getWalletInfo( + data.wallet, data.refreshFromNode, data.minimumConfirmations); + } + + /// + /// Get balance information for the currently open wallet + /// + static Future< + ({ + double awaitingFinalization, + double pending, + double spendable, + double total + })> + getWalletBalances( + {required String wallet, + required int refreshFromNode, + required int minimumConfirmations}) async { + try { + String balances = await compute(_walletBalancesWrapper, ( + wallet: wallet, + refreshFromNode: refreshFromNode, + minimumConfirmations: minimumConfirmations, + )); + + //If balances is valid json return, else return error + if (balances.toUpperCase().contains("ERROR")) { + throw Exception(balances); + } + var jsonBalances = json.decode(balances); + //Return balances as record + ({ + double spendable, + double pending, + double total, + double awaitingFinalization + }) balancesRecord = ( + spendable: jsonBalances['amount_currently_spendable'], + pending: jsonBalances['amount_awaiting_finalization'], + total: jsonBalances['total'], + awaitingFinalization: jsonBalances['amount_awaiting_finalization'], + ); + return balancesRecord; + } catch (e) { + throw ("Error getting wallet info : ${e.toString()}"); + } + } + + /// + /// Private function wrapper for scanning output function + /// + static Future _scanOutputsWrapper( + ({String wallet, int startHeight, int numberOfBlocks}) data, + ) async { + return lib_epiccash.scanOutPuts( + data.wallet, + data.startHeight, + data.numberOfBlocks, + ); + } + + /// + /// Scan Epic outputs + /// + static Future scanOutputs({ + required String wallet, + required int startHeight, + required int numberOfBlocks, + }) async { + try { + return await compute(_scanOutputsWrapper, ( + wallet: wallet, + startHeight: startHeight, + numberOfBlocks: numberOfBlocks, + )); + } catch (e) { + throw ("Error getting scanning outputs : ${e.toString()}"); + } + } + + /// + /// Private function wrapper for create transactions + /// + static Future _createTransactionWrapper( + ({ + String wallet, + int amount, + String address, + int secretKeyIndex, + String epicboxConfig, + int minimumConfirmations, + String note, + }) data, + ) async { + return lib_epiccash.createTransaction( + data.wallet, + data.amount, + data.address, + data.secretKeyIndex, + data.epicboxConfig, + data.minimumConfirmations, + data.note); + } + + /// + /// Create an Epic transaction + /// + static Future<({String slateId, String commitId})> createTransaction({ + required String wallet, + required int amount, + required String address, + required int secretKeyIndex, + required String epicboxConfig, + required int minimumConfirmations, + required String note, + }) async { + try { + String result = await compute(_createTransactionWrapper, ( + wallet: wallet, + amount: amount, + address: address, + secretKeyIndex: secretKeyIndex, + epicboxConfig: epicboxConfig, + minimumConfirmations: minimumConfirmations, + note: note, + )); + + if (result.toUpperCase().contains("ERROR")) { + throw Exception("Error creating transaction ${result.toString()}"); + } + + //Decode sent tx and return Slate Id + final slate0 = jsonDecode(result); + final slate = jsonDecode(slate0[0] as String); + final part1 = jsonDecode(slate[0] as String); + final part2 = jsonDecode(slate[1] as String); + + ({String slateId, String commitId}) data = ( + slateId: part1[0]['tx_slate_id'], + commitId: part2['tx']['body']['outputs'][0]['commit'], + ); + + return data; + } catch (e) { + throw ("Error creating epic transaction : ${e.toString()}"); + } + } + + /// + /// Private function wrapper for get transactions + /// + static Future _getTransactionsWrapper( + ({ + String wallet, + int refreshFromNode, + }) data, + ) async { + return lib_epiccash.getTransactions( + data.wallet, + data.refreshFromNode, + ); + } + + /// + /// + /// + static Future> getTransactions({ + required String wallet, + required int refreshFromNode, + }) async { + try { + var result = await compute(_getTransactionsWrapper, ( + wallet: wallet, + refreshFromNode: refreshFromNode, + )); + + if (result.toUpperCase().contains("ERROR")) { + throw Exception("Error getting epic transactions ${result.toString()}"); + } + + //Parse the returned data as an EpicTransaction + List finalResult = []; + var jsonResult = json.decode(result) as List; + + for (var tx in jsonResult) { + EpicTransaction itemTx = EpicTransaction.fromJson(tx); + finalResult.add(itemTx); + } + return finalResult; + } catch (e) { + throw ("Error getting epic transactions : ${e.toString()}"); + } + } + + /// + /// Private function for cancel transaction function + /// + static Future _cancelTransactionWrapper( + ({ + String wallet, + String transactionId, + }) data, + ) async { + return lib_epiccash.cancelTransaction( + data.wallet, + data.transactionId, + ); + } + + /// + /// Cancel current Epic transaction + /// + static Future cancelTransaction({ + required String wallet, + required String transactionId, + }) async { + try { + return await compute(_cancelTransactionWrapper, ( + wallet: wallet, + transactionId: transactionId, + )); + } catch (e) { + throw ("Error canceling epic transaction : ${e.toString()}"); + } + } + + static Future _chainHeightWrapper( + ({ + String config, + }) data, + ) async { + return lib_epiccash.getChainHeight(data.config); + } + + static Future getChainHeight({ + required String config, + }) async { + try { + return await compute(_chainHeightWrapper, (config: config,)); + } catch (e) { + throw ("Error getting chain height : ${e.toString()}"); + } + } + + /// + /// Private function for address info function + /// + static Future _addressInfoWrapper( + ({ + String wallet, + int index, + String epicboxConfig, + }) data, + ) async { + return lib_epiccash.getAddressInfo( + data.wallet, + data.index, + data.epicboxConfig, + ); + } + + /// + /// get Epic address info + /// + static Future getAddressInfo({ + required String wallet, + required int index, + required String epicboxConfig, + }) async { + try { + return await compute(_addressInfoWrapper, ( + wallet: wallet, + index: index, + epicboxConfig: epicboxConfig, + )); + } catch (e) { + throw ("Error getting address info : ${e.toString()}"); + } + } + + /// + /// Private function for getting transaction fees + /// + static Future _transactionFeesWrapper( + ({ + String wallet, + int amount, + int minimumConfirmations, + }) data, + ) async { + return lib_epiccash.getTransactionFees( + data.wallet, + data.amount, + data.minimumConfirmations, + ); + } + + /// + /// get transaction fees for Epic + /// + static Future<({int fee, bool strategyUseAll, int total})> + getTransactionFees({ + required String wallet, + required int amount, + required int minimumConfirmations, + required int available, + }) async { + try { + String fees = await compute(_transactionFeesWrapper, ( + wallet: wallet, + amount: amount, + minimumConfirmations: minimumConfirmations, + )); + + if (available == amount) { + if (fees.contains("Required")) { + var splits = fees.split(" "); + Decimal required = Decimal.zero; + Decimal available = Decimal.zero; + for (int i = 0; i < splits.length; i++) { + var word = splits[i]; + if (word == "Required:") { + required = Decimal.parse(splits[i + 1].replaceAll(",", "")); + } else if (word == "Available:") { + available = Decimal.parse(splits[i + 1].replaceAll(",", "")); + } + } + int largestSatoshiFee = + ((required - available) * Decimal.fromInt(100000000)) + .toBigInt() + .toInt(); + var amountSending = amount - largestSatoshiFee; + //Get fees for this new amount + ({ + String wallet, + int amount, + }) data = (wallet: wallet, amount: amountSending); + fees = await compute(_transactionFeesWrapper, ( + wallet: wallet, + amount: amountSending, + minimumConfirmations: minimumConfirmations, + )); + } + } + + if (fees.toUpperCase().contains("ERROR")) { + //Check if the error is an + //Throw the returned error + throw Exception(fees); + } + var decodedFees = json.decode(fees); + var feeItem = decodedFees[0]; + ({ + bool strategyUseAll, + int total, + int fee, + }) feeRecord = ( + strategyUseAll: feeItem['selection_strategy_is_use_all'], + total: feeItem['total'], + fee: feeItem['fee'], + ); + return feeRecord; + } catch (e) { + throw (e.toString()); + } + } + + /// + /// Private function wrapper for recover wallet function + /// + static Future _recoverWalletWrapper( + ({ + String config, + String password, + String mnemonic, + String name, + }) data, + ) async { + return lib_epiccash.recoverWallet( + data.config, + data.password, + data.mnemonic, + data.name, + ); + } + + /// + /// Recover an Epic wallet using a mnemonic + /// + static Future recoverWallet( + {required String config, + required String password, + required String mnemonic, + required String name}) async { + try { + await compute(_recoverWalletWrapper, ( + config: config, + password: password, + mnemonic: mnemonic, + name: name, + )); + } catch (e) { + throw (e.toString()); + } + } + + /// + /// Private function wrapper for delete wallet function + /// + static Future _deleteWalletWrapper( + ({ + String wallet, + String config, + }) data, + ) async { + return lib_epiccash.deleteWallet( + data.wallet, + data.config, + ); + } + + /// + /// Delete an Epic wallet + /// + static Future deleteWallet({ + required String wallet, + required String config, + }) async { + try { + return await compute(_deleteWalletWrapper, ( + wallet: wallet, + config: config, + )); + } catch (e) { + throw ("Error deleting wallet : ${e.toString()}"); + } + } + + /// + /// Private function wrapper for open wallet function + /// + static Future _openWalletWrapper( + ({ + String config, + String password, + }) data, + ) async { + return lib_epiccash.openWallet( + data.config, + data.password, + ); + } + + /// + /// Open an Epic wallet + /// + static Future openWallet({ + required String config, + required String password, + }) async { + try { + return await compute(_openWalletWrapper, ( + config: config, + password: password, + )); + } catch (e) { + throw ("Error opening wallet : ${e.toString()}"); + } + } + + /// + /// Private function for txHttpSend function + /// + static Future _txHttpSendWrapper( + ({ + String wallet, + int selectionStrategyIsAll, + int minimumConfirmations, + String message, + int amount, + String address, + }) data, + ) async { + return lib_epiccash.txHttpSend( + data.wallet, + data.selectionStrategyIsAll, + data.minimumConfirmations, + data.message, + data.amount, + data.address, + ); + } + + /// + /// + /// + static Future<({String commitId, String slateId})> txHttpSend({ + required String wallet, + required int selectionStrategyIsAll, + required int minimumConfirmations, + required String message, + required int amount, + required String address, + }) async { + try { + var result = await compute(_txHttpSendWrapper, ( + wallet: wallet, + selectionStrategyIsAll: selectionStrategyIsAll, + minimumConfirmations: minimumConfirmations, + message: message, + amount: amount, + address: address, + )); + if (result.toUpperCase().contains("ERROR")) { + throw Exception("Error creating transaction ${result.toString()}"); + } + + //Decode sent tx and return Slate Id + final slate0 = jsonDecode(result); + final slate = jsonDecode(slate0[0] as String); + final part1 = jsonDecode(slate[0] as String); + final part2 = jsonDecode(slate[1] as String); + + ({String slateId, String commitId}) data = ( + slateId: part1[0]['tx_slate_id'], + commitId: part2['tx']['body']['outputs'][0]['commit'], + ); + + return data; + } catch (e) { + throw ("Error sending tx HTTP : ${e.toString()}"); + } + } } diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 90ae1d6e5..95f3da71e 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -4,8 +4,8 @@ com.apple.security.app-sandbox - com.apple.security.network.client - + com.apple.security.network.client + com.apple.security.network.server