diff --git a/.gitmodules b/.gitmodules index 2186826df..f1b4eb874 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [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/vekamo/flutter_libmwc diff --git a/README.md b/README.md index 4c18c3181..286bb6f54 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Highlights include: - [Bitcoin Cash](https://bch.info/en/) - [Dogecoin](https://dogecoin.com/) - [Epic Cash](https://linktr.ee/epiccash) + - [MimbleWimbleCoin](https://mwc.mw) - [Ethereum](https://ethereum.org/en/) - [Firo](https://firo.org/) - [Litecoin](https://litecoin.org/) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc new file mode 160000 index 000000000..2008080fd --- /dev/null +++ b/crypto_plugins/flutter_libmwc @@ -0,0 +1 @@ +Subproject commit 2008080fd8e3a68081f4ca1334c2568421539157 diff --git a/lib/db/db_version_migration.dart b/lib/db/db_version_migration.dart index 1415803e4..f350af42e 100644 --- a/lib/db/db_version_migration.dart +++ b/lib/db/db_version_migration.dart @@ -528,6 +528,71 @@ class DbVersionMigrator with WalletDB { await MainDB.instance.addNewTransactionData(transactionsData, walletId); } + // we need to manually migrate mimblewimblecoin transactions as they are not + // stored on the mimblewimblecoin blockchain + final mimblewimblecoin = Mimblewimblecoin(CryptoCurrencyNetwork.main); + if (info.coinIdentifier == mimblewimblecoin.identifier) { + final txnData = walletBox.get("latest_tx_model") as TransactionData?; + + // we ever only used index 0 in the past + const rcvIndex = 0; + + final List> + transactionsData = []; + if (txnData != null) { + final txns = txnData.getAllTransactions(); + + for (final tx in txns.values) { + final bool isIncoming = tx.txType == "Received"; + + final iTx = isar_models.Transaction( + walletId: walletId, + txid: tx.txid, + timestamp: tx.timestamp, + type: isIncoming + ? isar_models.TransactionType.incoming + : isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + amount: tx.amount, + amountString: Amount( + rawValue: BigInt.from(tx.amount), + fractionDigits: mimblewimblecoin.fractionDigits, + ).toJsonString(), + fee: tx.fees, + height: tx.height, + isCancelled: tx.isCancelled, + isLelantus: false, + slateId: tx.slateId, + otherData: tx.otherData, + nonce: null, + inputs: [], + outputs: [], + numberOfMessages: tx.numberOfMessages, + ); + + if (tx.address.isEmpty) { + transactionsData.add(Tuple2(iTx, null)); + } else { + final address = isar_models.Address( + walletId: walletId, + value: tx.address, + publicKey: [], + derivationIndex: isIncoming ? rcvIndex : -1, + derivationPath: null, + type: isIncoming + ? isar_models.AddressType.mimbleWimble + : isar_models.AddressType.unknown, + subType: isIncoming + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.unknown, + ); + transactionsData.add(Tuple2(iTx, address)); + } + } + } + await MainDB.instance.addNewTransactionData(transactionsData, walletId); + } + // delete data from hive await walletBox.delete(receiveAddressesPrefix); await walletBox.delete("${receiveAddressesPrefix}P2PKH"); @@ -549,9 +614,13 @@ class DbVersionMigrator with WalletDB { ); } - // doing this for epic cash will delete transaction history as it is not - // stored on the epic cash blockchain - if (info.coinIdentifier != epic.identifier) { + // doing this for epiccash/mimblewimblecoin will delete transaction history as it is not + // stored on the epiccash/mimblewimblecoin blockchain + final excludedIdentifiers = [ + epic.identifier, + mimblewimblecoin.identifier + ]; + if ((!excludedIdentifiers.contains(info.coinIdentifier))) { // set flag to initiate full rescan on opening wallet await DB.instance.put( boxName: DB.boxNameDBInfo, diff --git a/lib/db/migrate_wallets_to_isar.dart b/lib/db/migrate_wallets_to_isar.dart index cd54a4063..d0f75a2e3 100644 --- a/lib/db/migrate_wallets_to_isar.dart +++ b/lib/db/migrate_wallets_to_isar.dart @@ -11,6 +11,7 @@ import '../wallets/isar/models/token_wallet_info.dart'; import '../wallets/isar/models/wallet_info.dart'; import '../wallets/isar/models/wallet_info_meta.dart'; import '../wallets/wallet/supporting/epiccash_wallet_info_extension.dart'; +import '../wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart'; import 'hive/db.dart'; import 'isar/main_db.dart'; @@ -146,6 +147,21 @@ Future migrateWalletsToIsar({ otherData[WalletInfoKeys.epiccashData] = jsonEncode( epicWalletInfo.toMap(), ); + } else if (old.coinIdentifier == + Mimblewimblecoin(CryptoCurrencyNetwork.main)) { + final mimblewimblecoinWalletInfo = + ExtraMimblewimblecoinWalletInfo.fromMap({ + "receivingIndex": walletBox.get("receivingIndex") as int? ?? 0, + "changeIndex": walletBox.get("changeIndex") as int? ?? 0, + "slatesToAddresses": walletBox.get("slate_to_address") as Map? ?? {}, + "slatesToCommits": walletBox.get("slatesToCommits") as Map? ?? {}, + "lastScannedBlock": walletBox.get("lastScannedBlock") as int? ?? 0, + "restoreHeight": walletBox.get("restoreHeight") as int? ?? 0, + "creationHeight": walletBox.get("creationHeight") as int? ?? 0, + }); + otherData[WalletInfoKeys.mimblewimblecoinData] = jsonEncode( + mimblewimblecoinWalletInfo.toMap(), + ); } else if (old.coinIdentifier == Firo(CryptoCurrencyNetwork.main).identifier || old.coinIdentifier == Firo(CryptoCurrencyNetwork.test).identifier) { 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 4582ef47d..cde1c965f 100644 --- a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart +++ b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart @@ -95,6 +95,8 @@ class TransactionV2 { bool get isEpiccashTransaction => _getFromOtherData(key: TxV2OdKeys.isEpiccashTransaction) == true; + bool get isMimblewimblecoinTransaction => + _getFromOtherData(key: TxV2OdKeys.isMimblewimblecoinTransaction) == true; int? get numberOfMessages => _getFromOtherData(key: TxV2OdKeys.numberOfMessages) as int?; String? get slateId => _getFromOtherData(key: TxV2OdKeys.slateId) as String?; @@ -274,6 +276,40 @@ class TransactionV2 { } } + if (isMimblewimblecoinTransaction) { + if (slateId == null) { + return "Restored Funds"; + } + + if (isCancelled) { + return "Cancelled"; + } else if (type == TransactionType.incoming) { + if (isConfirmed(currentChainHeight, minConfirms)) { + return "Received"; + } else { + if (numberOfMessages == 1) { + return "Receiving (waiting for sender)"; + } else if ((numberOfMessages ?? 0) > 1) { + return "Receiving (waiting for confirmations)"; // TODO test if the sender still has to open again after the receiver has 2 messages present, ie. sender->receiver->sender->node (yes) vs. sender->receiver->node (no) + } else { + return "Receiving ${prettyConfirms()}"; + } + } + } else if (type == TransactionType.outgoing) { + if (isConfirmed(currentChainHeight, minConfirms)) { + return "Sent (confirmed)"; + } else { + if (numberOfMessages == 1) { + return "Sending (waiting for receiver)"; + } else if ((numberOfMessages ?? 0) > 1) { + return "Sending (waiting for confirmations)"; + } else { + return "Sending ${prettyConfirms()}"; + } + } + } + } + if (type == TransactionType.incoming) { // if (_transaction.isMinting) { // return "Minting"; @@ -331,6 +367,7 @@ abstract final class TxV2OdKeys { static const size = "size"; static const vSize = "vSize"; static const isEpiccashTransaction = "isEpiccashTransaction"; + static const isMimblewimblecoinTransaction = "isMimblewimblecoinTransaction"; static const numberOfMessages = "numberOfMessages"; static const slateId = "slateId"; static const onChainNote = "onChainNote"; diff --git a/lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart b/lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart index 68d8a18c5..3bb48c350 100644 --- a/lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart +++ b/lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart @@ -53,66 +53,71 @@ const TransactionV2Schema = CollectionSchema( name: r'isEpiccashTransaction', type: IsarType.bool, ), - r'nonce': PropertySchema( + r'isMimblewimblecoinTransaction': PropertySchema( id: 7, + name: r'isMimblewimblecoinTransaction', + type: IsarType.bool, + ), + r'nonce': PropertySchema( + id: 8, name: r'nonce', type: IsarType.long, ), r'numberOfMessages': PropertySchema( - id: 8, + id: 9, name: r'numberOfMessages', type: IsarType.long, ), r'onChainNote': PropertySchema( - id: 9, + id: 10, name: r'onChainNote', type: IsarType.string, ), r'otherData': PropertySchema( - id: 10, + id: 11, name: r'otherData', type: IsarType.string, ), r'outputs': PropertySchema( - id: 11, + id: 12, name: r'outputs', type: IsarType.objectList, target: r'OutputV2', ), r'slateId': PropertySchema( - id: 12, + id: 13, name: r'slateId', type: IsarType.string, ), r'subType': PropertySchema( - id: 13, + id: 14, name: r'subType', type: IsarType.byte, enumMap: _TransactionV2subTypeEnumValueMap, ), r'timestamp': PropertySchema( - id: 14, + id: 15, name: r'timestamp', type: IsarType.long, ), r'txid': PropertySchema( - id: 15, + id: 16, name: r'txid', type: IsarType.string, ), r'type': PropertySchema( - id: 16, + id: 17, name: r'type', type: IsarType.byte, enumMap: _TransactionV2typeEnumValueMap, ), r'version': PropertySchema( - id: 17, + id: 18, name: r'version', type: IsarType.long, ), r'walletId': PropertySchema( - id: 18, + id: 19, name: r'walletId', type: IsarType.string, ) @@ -256,23 +261,24 @@ void _transactionV2Serialize( ); writer.writeBool(offsets[5], object.isCancelled); writer.writeBool(offsets[6], object.isEpiccashTransaction); - writer.writeLong(offsets[7], object.nonce); - writer.writeLong(offsets[8], object.numberOfMessages); - writer.writeString(offsets[9], object.onChainNote); - writer.writeString(offsets[10], object.otherData); + writer.writeBool(offsets[7], object.isMimblewimblecoinTransaction); + writer.writeLong(offsets[8], object.nonce); + writer.writeLong(offsets[9], object.numberOfMessages); + writer.writeString(offsets[10], object.onChainNote); + writer.writeString(offsets[11], object.otherData); writer.writeObjectList( - offsets[11], + offsets[12], allOffsets, OutputV2Schema.serialize, object.outputs, ); - writer.writeString(offsets[12], object.slateId); - writer.writeByte(offsets[13], object.subType.index); - writer.writeLong(offsets[14], object.timestamp); - writer.writeString(offsets[15], object.txid); - writer.writeByte(offsets[16], object.type.index); - writer.writeLong(offsets[17], object.version); - writer.writeString(offsets[18], object.walletId); + writer.writeString(offsets[13], object.slateId); + writer.writeByte(offsets[14], object.subType.index); + writer.writeLong(offsets[15], object.timestamp); + writer.writeString(offsets[16], object.txid); + writer.writeByte(offsets[17], object.type.index); + writer.writeLong(offsets[18], object.version); + writer.writeString(offsets[19], object.walletId); } TransactionV2 _transactionV2Deserialize( @@ -292,23 +298,23 @@ TransactionV2 _transactionV2Deserialize( InputV2(), ) ?? [], - otherData: reader.readStringOrNull(offsets[10]), + otherData: reader.readStringOrNull(offsets[11]), outputs: reader.readObjectList( - offsets[11], + offsets[12], OutputV2Schema.deserialize, allOffsets, OutputV2(), ) ?? [], subType: - _TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[13])] ?? + _TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[14])] ?? TransactionSubType.none, - timestamp: reader.readLong(offsets[14]), - txid: reader.readString(offsets[15]), - type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[16])] ?? + timestamp: reader.readLong(offsets[15]), + txid: reader.readString(offsets[16]), + type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[17])] ?? TransactionType.outgoing, - version: reader.readLong(offsets[17]), - walletId: reader.readString(offsets[18]), + version: reader.readLong(offsets[18]), + walletId: reader.readString(offsets[19]), ); object.id = id; return object; @@ -342,14 +348,16 @@ P _transactionV2DeserializeProp

( case 6: return (reader.readBool(offset)) as P; case 7: - return (reader.readLongOrNull(offset)) as P; + return (reader.readBool(offset)) as P; case 8: return (reader.readLongOrNull(offset)) as P; case 9: - return (reader.readStringOrNull(offset)) as P; + return (reader.readLongOrNull(offset)) as P; case 10: return (reader.readStringOrNull(offset)) as P; case 11: + return (reader.readStringOrNull(offset)) as P; + case 12: return (reader.readObjectList( offset, OutputV2Schema.deserialize, @@ -357,22 +365,22 @@ P _transactionV2DeserializeProp

( OutputV2(), ) ?? []) as P; - case 12: - return (reader.readStringOrNull(offset)) as P; case 13: + return (reader.readStringOrNull(offset)) as P; + case 14: return (_TransactionV2subTypeValueEnumMap[ reader.readByteOrNull(offset)] ?? TransactionSubType.none) as P; - case 14: - return (reader.readLong(offset)) as P; case 15: - return (reader.readString(offset)) as P; + return (reader.readLong(offset)) as P; case 16: + return (reader.readString(offset)) as P; + case 17: return (_TransactionV2typeValueEnumMap[reader.readByteOrNull(offset)] ?? TransactionType.outgoing) as P; - case 17: - return (reader.readLong(offset)) as P; case 18: + return (reader.readLong(offset)) as P; + case 19: return (reader.readString(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); @@ -1513,6 +1521,16 @@ extension TransactionV2QueryFilter }); } + QueryBuilder + isMimblewimblecoinTransactionEqualTo(bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isMimblewimblecoinTransaction', + value: value, + )); + }); + } + QueryBuilder nonceIsNull() { return QueryBuilder.apply(this, (query) { @@ -2807,6 +2825,20 @@ extension TransactionV2QuerySortBy }); } + QueryBuilder + sortByIsMimblewimblecoinTransaction() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isMimblewimblecoinTransaction', Sort.asc); + }); + } + + QueryBuilder + sortByIsMimblewimblecoinTransactionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isMimblewimblecoinTransaction', Sort.desc); + }); + } + QueryBuilder sortByNonce() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'nonce', Sort.asc); @@ -3038,6 +3070,20 @@ extension TransactionV2QuerySortThenBy }); } + QueryBuilder + thenByIsMimblewimblecoinTransaction() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isMimblewimblecoinTransaction', Sort.asc); + }); + } + + QueryBuilder + thenByIsMimblewimblecoinTransactionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isMimblewimblecoinTransaction', Sort.desc); + }); + } + QueryBuilder thenByNonce() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'nonce', Sort.asc); @@ -3221,6 +3267,13 @@ extension TransactionV2QueryWhereDistinct }); } + QueryBuilder + distinctByIsMimblewimblecoinTransaction() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isMimblewimblecoinTransaction'); + }); + } + QueryBuilder distinctByNonce() { return QueryBuilder.apply(this, (query) { return query.addDistinctBy(r'nonce'); @@ -3347,6 +3400,13 @@ extension TransactionV2QueryProperty }); } + QueryBuilder + isMimblewimblecoinTransactionProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isMimblewimblecoinTransaction'); + }); + } + QueryBuilder nonceProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'nonce'); diff --git a/lib/models/isar/stack_theme.dart b/lib/models/isar/stack_theme.dart index ce5002a1d..4f831732f 100644 --- a/lib/models/isar/stack_theme.dart +++ b/lib/models/isar/stack_theme.dart @@ -1939,6 +1939,7 @@ class ThemeAssets implements IThemeAssets { late final String bitcoincash; late final String dogecoin; late final String epicCash; + late final String mimblewimblecoin; late final String ethereum; late final String firo; late final String monero; @@ -1949,6 +1950,7 @@ class ThemeAssets implements IThemeAssets { late final String bitcoincashImage; late final String dogecoinImage; late final String epicCashImage; + late final String mimblewimblecoinImage; late final String ethereumImage; late final String firoImage; late final String litecoinImage; @@ -1960,6 +1962,7 @@ class ThemeAssets implements IThemeAssets { late final String bitcoincashImageSecondary; late final String dogecoinImageSecondary; late final String epicCashImageSecondary; + late final String mimblewimblecoinImageSecondary; late final String ethereumImageSecondary; late final String firoImageSecondary; late final String litecoinImageSecondary; @@ -2006,6 +2009,8 @@ class ThemeAssets implements IThemeAssets { ..bitcoincash = "$themeId/assets/${json["bitcoincash"] as String}" ..dogecoin = "$themeId/assets/${json["dogecoin"] as String}" ..epicCash = "$themeId/assets/${json["epicCash"] as String}" + ..mimblewimblecoin = + "$themeId/assets/${json["mimblewimblecoin"] as String}" ..ethereum = "$themeId/assets/${json["ethereum"] as String}" ..firo = "$themeId/assets/${json["firo"] as String}" ..monero = "$themeId/assets/${json["monero"] as String}" @@ -2017,6 +2022,8 @@ class ThemeAssets implements IThemeAssets { "$themeId/assets/${json["bitcoincash_image"] as String}" ..dogecoinImage = "$themeId/assets/${json["dogecoin_image"] as String}" ..epicCashImage = "$themeId/assets/${json["epicCash_image"] as String}" + ..mimblewimblecoinImage = + "$themeId/assets/${json["mimblewimblecoin_image"] as String}" ..ethereumImage = "$themeId/assets/${json["ethereum_image"] as String}" ..firoImage = "$themeId/assets/${json["firo_image"] as String}" ..litecoinImage = "$themeId/assets/${json["litecoin_image"] as String}" @@ -2032,6 +2039,8 @@ class ThemeAssets implements IThemeAssets { "$themeId/assets/${json["dogecoin_image_secondary"] as String}" ..epicCashImageSecondary = "$themeId/assets/${json["epicCash_image_secondary"] as String}" + ..mimblewimblecoinImageSecondary = + "$themeId/assets/${json["mimblewimblecoin_image_secondary"] as String}" ..ethereumImageSecondary = "$themeId/assets/${json["ethereum_image_secondary"] as String}" ..firoImageSecondary = diff --git a/lib/models/isar/stack_theme.g.dart b/lib/models/isar/stack_theme.g.dart index c77d979f5..0117ef7b9 100644 --- a/lib/models/isar/stack_theme.g.dart +++ b/lib/models/isar/stack_theme.g.dart @@ -18148,138 +18148,153 @@ const ThemeAssetsSchema = Schema( name: r'loadingGif', type: IsarType.string, ), - r'monero': PropertySchema( + r'mimblewimblecoin': PropertySchema( id: 26, + name: r'mimblewimblecoin', + type: IsarType.string, + ), + r'mimblewimblecoinImage': PropertySchema( + id: 27, + name: r'mimblewimblecoinImage', + type: IsarType.string, + ), + r'mimblewimblecoinImageSecondary': PropertySchema( + id: 28, + name: r'mimblewimblecoinImageSecondary', + type: IsarType.string, + ), + r'monero': PropertySchema( + id: 29, name: r'monero', type: IsarType.string, ), r'moneroImage': PropertySchema( - id: 27, + id: 30, name: r'moneroImage', type: IsarType.string, ), r'moneroImageSecondary': PropertySchema( - id: 28, + id: 31, name: r'moneroImageSecondary', type: IsarType.string, ), r'namecoin': PropertySchema( - id: 29, + id: 32, name: r'namecoin', type: IsarType.string, ), r'namecoinImage': PropertySchema( - id: 30, + id: 33, name: r'namecoinImage', type: IsarType.string, ), r'namecoinImageSecondary': PropertySchema( - id: 31, + id: 34, name: r'namecoinImageSecondary', type: IsarType.string, ), r'particl': PropertySchema( - id: 32, + id: 35, name: r'particl', type: IsarType.string, ), r'particlImage': PropertySchema( - id: 33, + id: 36, name: r'particlImage', type: IsarType.string, ), r'particlImageSecondary': PropertySchema( - id: 34, + id: 37, name: r'particlImageSecondary', type: IsarType.string, ), r'personaEasy': PropertySchema( - id: 35, + id: 38, name: r'personaEasy', type: IsarType.string, ), r'personaIncognito': PropertySchema( - id: 36, + id: 39, name: r'personaIncognito', type: IsarType.string, ), r'receive': PropertySchema( - id: 37, + id: 40, name: r'receive', type: IsarType.string, ), r'receiveCancelled': PropertySchema( - id: 38, + id: 41, name: r'receiveCancelled', type: IsarType.string, ), r'receivePending': PropertySchema( - id: 39, + id: 42, name: r'receivePending', type: IsarType.string, ), r'send': PropertySchema( - id: 40, + id: 43, name: r'send', type: IsarType.string, ), r'sendCancelled': PropertySchema( - id: 41, + id: 44, name: r'sendCancelled', type: IsarType.string, ), r'sendPending': PropertySchema( - id: 42, + id: 45, name: r'sendPending', type: IsarType.string, ), r'stack': PropertySchema( - id: 43, + id: 46, name: r'stack', type: IsarType.string, ), r'stackIcon': PropertySchema( - id: 44, + id: 47, name: r'stackIcon', type: IsarType.string, ), r'themePreview': PropertySchema( - id: 45, + id: 48, name: r'themePreview', type: IsarType.string, ), r'themeSelector': PropertySchema( - id: 46, + id: 49, name: r'themeSelector', type: IsarType.string, ), r'txExchange': PropertySchema( - id: 47, + id: 50, name: r'txExchange', type: IsarType.string, ), r'txExchangeFailed': PropertySchema( - id: 48, + id: 51, name: r'txExchangeFailed', type: IsarType.string, ), r'txExchangePending': PropertySchema( - id: 49, + id: 52, name: r'txExchangePending', type: IsarType.string, ), r'wownero': PropertySchema( - id: 50, + id: 53, name: r'wownero', type: IsarType.string, ), r'wowneroImage': PropertySchema( - id: 51, + id: 54, name: r'wowneroImage', type: IsarType.string, ), r'wowneroImageSecondary': PropertySchema( - id: 52, + id: 55, name: r'wowneroImageSecondary', type: IsarType.string, ) @@ -18332,6 +18347,9 @@ int _themeAssetsEstimateSize( bytesCount += 3 + value.length * 3; } } + bytesCount += 3 + object.mimblewimblecoin.length * 3; + bytesCount += 3 + object.mimblewimblecoinImage.length * 3; + bytesCount += 3 + object.mimblewimblecoinImageSecondary.length * 3; bytesCount += 3 + object.monero.length * 3; bytesCount += 3 + object.moneroImage.length * 3; bytesCount += 3 + object.moneroImageSecondary.length * 3; @@ -18394,33 +18412,36 @@ void _themeAssetsSerialize( writer.writeString(offsets[23], object.litecoinImage); writer.writeString(offsets[24], object.litecoinImageSecondary); writer.writeString(offsets[25], object.loadingGif); - writer.writeString(offsets[26], object.monero); - writer.writeString(offsets[27], object.moneroImage); - writer.writeString(offsets[28], object.moneroImageSecondary); - writer.writeString(offsets[29], object.namecoin); - writer.writeString(offsets[30], object.namecoinImage); - writer.writeString(offsets[31], object.namecoinImageSecondary); - writer.writeString(offsets[32], object.particl); - writer.writeString(offsets[33], object.particlImage); - writer.writeString(offsets[34], object.particlImageSecondary); - writer.writeString(offsets[35], object.personaEasy); - writer.writeString(offsets[36], object.personaIncognito); - writer.writeString(offsets[37], object.receive); - writer.writeString(offsets[38], object.receiveCancelled); - writer.writeString(offsets[39], object.receivePending); - writer.writeString(offsets[40], object.send); - writer.writeString(offsets[41], object.sendCancelled); - writer.writeString(offsets[42], object.sendPending); - writer.writeString(offsets[43], object.stack); - writer.writeString(offsets[44], object.stackIcon); - writer.writeString(offsets[45], object.themePreview); - writer.writeString(offsets[46], object.themeSelector); - writer.writeString(offsets[47], object.txExchange); - writer.writeString(offsets[48], object.txExchangeFailed); - writer.writeString(offsets[49], object.txExchangePending); - writer.writeString(offsets[50], object.wownero); - writer.writeString(offsets[51], object.wowneroImage); - writer.writeString(offsets[52], object.wowneroImageSecondary); + writer.writeString(offsets[26], object.mimblewimblecoin); + writer.writeString(offsets[27], object.mimblewimblecoinImage); + writer.writeString(offsets[28], object.mimblewimblecoinImageSecondary); + writer.writeString(offsets[29], object.monero); + writer.writeString(offsets[30], object.moneroImage); + writer.writeString(offsets[31], object.moneroImageSecondary); + writer.writeString(offsets[32], object.namecoin); + writer.writeString(offsets[33], object.namecoinImage); + writer.writeString(offsets[34], object.namecoinImageSecondary); + writer.writeString(offsets[35], object.particl); + writer.writeString(offsets[36], object.particlImage); + writer.writeString(offsets[37], object.particlImageSecondary); + writer.writeString(offsets[38], object.personaEasy); + writer.writeString(offsets[39], object.personaIncognito); + writer.writeString(offsets[40], object.receive); + writer.writeString(offsets[41], object.receiveCancelled); + writer.writeString(offsets[42], object.receivePending); + writer.writeString(offsets[43], object.send); + writer.writeString(offsets[44], object.sendCancelled); + writer.writeString(offsets[45], object.sendPending); + writer.writeString(offsets[46], object.stack); + writer.writeString(offsets[47], object.stackIcon); + writer.writeString(offsets[48], object.themePreview); + writer.writeString(offsets[49], object.themeSelector); + writer.writeString(offsets[50], object.txExchange); + writer.writeString(offsets[51], object.txExchangeFailed); + writer.writeString(offsets[52], object.txExchangePending); + writer.writeString(offsets[53], object.wownero); + writer.writeString(offsets[54], object.wowneroImage); + writer.writeString(offsets[55], object.wowneroImageSecondary); } ThemeAssets _themeAssetsDeserialize( @@ -18456,33 +18477,36 @@ ThemeAssets _themeAssetsDeserialize( object.litecoinImage = reader.readString(offsets[23]); object.litecoinImageSecondary = reader.readString(offsets[24]); object.loadingGif = reader.readStringOrNull(offsets[25]); - object.monero = reader.readString(offsets[26]); - object.moneroImage = reader.readString(offsets[27]); - object.moneroImageSecondary = reader.readString(offsets[28]); - object.namecoin = reader.readString(offsets[29]); - object.namecoinImage = reader.readString(offsets[30]); - object.namecoinImageSecondary = reader.readString(offsets[31]); - object.particl = reader.readString(offsets[32]); - object.particlImage = reader.readString(offsets[33]); - object.particlImageSecondary = reader.readString(offsets[34]); - object.personaEasy = reader.readString(offsets[35]); - object.personaIncognito = reader.readString(offsets[36]); - object.receive = reader.readString(offsets[37]); - object.receiveCancelled = reader.readString(offsets[38]); - object.receivePending = reader.readString(offsets[39]); - object.send = reader.readString(offsets[40]); - object.sendCancelled = reader.readString(offsets[41]); - object.sendPending = reader.readString(offsets[42]); - object.stack = reader.readString(offsets[43]); - object.stackIcon = reader.readString(offsets[44]); - object.themePreview = reader.readString(offsets[45]); - object.themeSelector = reader.readString(offsets[46]); - object.txExchange = reader.readString(offsets[47]); - object.txExchangeFailed = reader.readString(offsets[48]); - object.txExchangePending = reader.readString(offsets[49]); - object.wownero = reader.readString(offsets[50]); - object.wowneroImage = reader.readString(offsets[51]); - object.wowneroImageSecondary = reader.readString(offsets[52]); + object.mimblewimblecoin = reader.readString(offsets[26]); + object.mimblewimblecoinImage = reader.readString(offsets[27]); + object.mimblewimblecoinImageSecondary = reader.readString(offsets[28]); + object.monero = reader.readString(offsets[29]); + object.moneroImage = reader.readString(offsets[30]); + object.moneroImageSecondary = reader.readString(offsets[31]); + object.namecoin = reader.readString(offsets[32]); + object.namecoinImage = reader.readString(offsets[33]); + object.namecoinImageSecondary = reader.readString(offsets[34]); + object.particl = reader.readString(offsets[35]); + object.particlImage = reader.readString(offsets[36]); + object.particlImageSecondary = reader.readString(offsets[37]); + object.personaEasy = reader.readString(offsets[38]); + object.personaIncognito = reader.readString(offsets[39]); + object.receive = reader.readString(offsets[40]); + object.receiveCancelled = reader.readString(offsets[41]); + object.receivePending = reader.readString(offsets[42]); + object.send = reader.readString(offsets[43]); + object.sendCancelled = reader.readString(offsets[44]); + object.sendPending = reader.readString(offsets[45]); + object.stack = reader.readString(offsets[46]); + object.stackIcon = reader.readString(offsets[47]); + object.themePreview = reader.readString(offsets[48]); + object.themeSelector = reader.readString(offsets[49]); + object.txExchange = reader.readString(offsets[50]); + object.txExchangeFailed = reader.readString(offsets[51]); + object.txExchangePending = reader.readString(offsets[52]); + object.wownero = reader.readString(offsets[53]); + object.wowneroImage = reader.readString(offsets[54]); + object.wowneroImageSecondary = reader.readString(offsets[55]); return object; } @@ -18599,6 +18623,12 @@ P _themeAssetsDeserializeProp

( return (reader.readString(offset)) as P; case 52: return (reader.readString(offset)) as P; + case 53: + return (reader.readString(offset)) as P; + case 54: + return (reader.readString(offset)) as P; + case 55: + return (reader.readString(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); } @@ -22170,6 +22200,417 @@ extension ThemeAssetsQueryFilter }); } + QueryBuilder + mimblewimblecoinEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'mimblewimblecoin', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'mimblewimblecoin', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoin', + value: '', + )); + }); + } + + QueryBuilder + mimblewimblecoinIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'mimblewimblecoin', + value: '', + )); + }); + } + + QueryBuilder + mimblewimblecoinImageEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'mimblewimblecoinImage', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'mimblewimblecoinImage', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoinImage', + value: '', + )); + }); + } + + QueryBuilder + mimblewimblecoinImageIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'mimblewimblecoinImage', + value: '', + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'mimblewimblecoinImageSecondary', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'mimblewimblecoinImageSecondary', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoinImageSecondary', + value: '', + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'mimblewimblecoinImageSecondary', + value: '', + )); + }); + } + QueryBuilder moneroEqualTo( String value, { bool caseSensitive = true, diff --git a/lib/models/mwcmqs_config_model.dart b/lib/models/mwcmqs_config_model.dart new file mode 100644 index 000000000..067d1a8ee --- /dev/null +++ b/lib/models/mwcmqs_config_model.dart @@ -0,0 +1,89 @@ +/* + * 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-05-26 + * + */ + +import 'dart:convert'; + +import 'package:hive/hive.dart'; + +import 'mwcmqs_server_model.dart'; + +part 'type_adaptors/mwcmqs_config_model.g.dart'; + +@HiveType(typeId: 82) +class MwcMqsConfigModel { + @HiveField(1) + final String host; + @HiveField(2) + final int? port; + + MwcMqsConfigModel({ + required this.host, + this.port + }); + + MwcMqsConfigModel copyWith({ + int? port, + bool? protocolInsecure, + }) { + return MwcMqsConfigModel( + host: host, + port: this.port ?? 443, + ); + } + + Map toMap() { + final Map map = {}; + map['mwcmqs_domain'] = host; + map['mwcmqs_port'] = port; + return map; + } + + Map toJson() { + return { + 'mwcmqs_domain': host, + 'mwcmqs_port': port, + }; + } + + @override + String toString() { + return json.encode(toJson()); + } + + static MwcMqsConfigModel fromString(String MwcMqsConfigString) { + final dynamic _mwcmqs = json.decode(MwcMqsConfigString); + + final oldDomain = _mwcmqs["domain"] ?? "empty"; + if (oldDomain != "empty") { + _mwcmqs['mwcmqs_domain'] = _mwcmqs['domain']; + } + final oldPort = _mwcmqs["port"] ?? "empty"; + if (oldPort != "empty") { + _mwcmqs['mwcmqs_port'] = _mwcmqs['port']; + } + + + return MwcMqsConfigModel( + host: _mwcmqs['mwcmqs_domain'] as String, + port: _mwcmqs['mwcmqs_port'] as int + ); + } + + static MwcMqsConfigModel fromServer( + MwcMqsServerModel server, { + bool? protocolInsecure, + int? addressIndex, + }) { + return MwcMqsConfigModel( + host: server.host, + port: server.port ?? 443 + ); + } +} diff --git a/lib/models/mwcmqs_server_model.dart b/lib/models/mwcmqs_server_model.dart new file mode 100644 index 000000000..c6ffd757f --- /dev/null +++ b/lib/models/mwcmqs_server_model.dart @@ -0,0 +1,93 @@ +/* + * 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-05-26 + * + */ + +import 'package:hive/hive.dart'; + +part 'type_adaptors/mwcmqs_server_model.g.dart'; + +@HiveType(typeId: 81) +class MwcMqsServerModel { + @HiveField(0) + final String id; + @HiveField(1) + final String host; + @HiveField(2) + final int? port; + @HiveField(3) + final String name; + @HiveField(4) + final bool? useSSL; + @HiveField(5) + final bool? enabled; + @HiveField(6) + final bool? isFailover; + @HiveField(7) + final bool? isDown; + + MwcMqsServerModel({ + required this.id, + required this.host, + this.port, + required this.name, + this.useSSL, + this.enabled, + this.isFailover, + this.isDown, + }); + + MwcMqsServerModel copyWith({ + String? host, + int? port, + String? name, + bool? useSSL, + bool? enabled, + bool? isFailover, + bool? isDown, + }) { + return MwcMqsServerModel( + id: id, + host: host ?? this.host, + port: port ?? this.port, + name: name ?? this.name, + useSSL: useSSL ?? this.useSSL, + enabled: enabled ?? this.enabled, + isFailover: isFailover ?? this.isFailover, + isDown: isDown ?? this.isDown, + ); + } + + Map toMap() { + final Map map = {}; + map['id'] = id; + map['host'] = host; + map['port'] = port; + map['name'] = name; + map['useSSL'] = useSSL; + map['enabled'] = enabled; + map['isFailover'] = isFailover; + map['isDown'] = isDown; + return map; + } + + bool get isDefault => id.startsWith("default_"); + + Map toJson() { + return { + 'id': id, + 'host': host, + 'port': port, + 'name': name, + 'useSSL': useSSL, + 'enabled': enabled, + 'isFailover': isFailover, + 'isDown': isDown, + }; + } +} diff --git a/lib/models/type_adaptors/mwcmqs_config_model.g.dart b/lib/models/type_adaptors/mwcmqs_config_model.g.dart new file mode 100644 index 000000000..f66fbdb56 --- /dev/null +++ b/lib/models/type_adaptors/mwcmqs_config_model.g.dart @@ -0,0 +1,44 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../mwcmqs_config_model.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class MwcMqsConfigModelAdapter extends TypeAdapter { + @override + final int typeId = 82; + + @override + MwcMqsConfigModel read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return MwcMqsConfigModel( + host: fields[1] as String, + port: fields[2] as int?, + ); + } + + @override + void write(BinaryWriter writer, MwcMqsConfigModel obj) { + writer + ..writeByte(2) + ..writeByte(1) + ..write(obj.host) + ..writeByte(2) + ..write(obj.port); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is MwcMqsConfigModelAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/models/type_adaptors/mwcmqs_server_model.g.dart b/lib/models/type_adaptors/mwcmqs_server_model.g.dart new file mode 100644 index 000000000..dabdd9464 --- /dev/null +++ b/lib/models/type_adaptors/mwcmqs_server_model.g.dart @@ -0,0 +1,62 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../mwcmqs_server_model.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class MwcMqsServerModelAdapter extends TypeAdapter { + @override + final int typeId = 81; + + @override + MwcMqsServerModel read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return MwcMqsServerModel( + id: fields[0] as String, + host: fields[1] as String, + port: fields[2] as int?, + name: fields[3] as String, + useSSL: fields[4] as bool?, + enabled: fields[5] as bool?, + isFailover: fields[6] as bool?, + isDown: fields[7] as bool?, + ); + } + + @override + void write(BinaryWriter writer, MwcMqsServerModel obj) { + writer + ..writeByte(8) + ..writeByte(0) + ..write(obj.id) + ..writeByte(1) + ..write(obj.host) + ..writeByte(2) + ..write(obj.port) + ..writeByte(3) + ..write(obj.name) + ..writeByte(4) + ..write(obj.useSSL) + ..writeByte(5) + ..write(obj.enabled) + ..writeByte(6) + ..write(obj.isFailover) + ..writeByte(7) + ..write(obj.isDown); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is MwcMqsServerModelAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart index 9d2ac8c1d..f95440e8d 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart @@ -361,7 +361,7 @@ class _SeedRestoreOptionState extends ConsumerState { return Column( children: [ - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) Text( "Choose start date", style: Util.isDesktop @@ -372,20 +372,20 @@ class _SeedRestoreOptionState extends ConsumerState { : STextStyles.smallMed12(context), textAlign: TextAlign.left, ), - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) SizedBox( height: Util.isDesktop ? 16 : 8, ), - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) RestoreFromDatePicker( onTap: widget.dateChooserFunction, controller: widget.dateController, ), - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) const SizedBox( height: 8, ), - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) RoundedWhiteContainer( child: Center( child: Text( @@ -402,7 +402,7 @@ class _SeedRestoreOptionState extends ConsumerState { ), ), ), - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) SizedBox( height: Util.isDesktop ? 24 : 16, ), diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart index 5f2bd08b0..8878b305c 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart @@ -21,6 +21,7 @@ import '../../../utilities/util.dart'; import '../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../wallets/isar/models/wallet_info.dart'; import '../../../wallets/wallet/impl/epiccash_wallet.dart'; +import '../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../wallets/wallet/impl/monero_wallet.dart'; import '../../../wallets/wallet/impl/wownero_wallet.dart'; import '../../../wallets/wallet/wallet.dart'; @@ -196,6 +197,10 @@ class _RestoreViewOnlyWalletViewState case const (EpiccashWallet): await (wallet as EpiccashWallet).init(isRestore: true); break; + + case const (MimblewimblecoinWallet): + await (wallet as MimblewimblecoinWallet).init(isRestore: true); + break; case const (MoneroWallet): await (wallet as MoneroWallet).init(isRestore: true); diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart index 2fae2352c..9c9f4ec7e 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart @@ -46,9 +46,11 @@ import '../../../utilities/util.dart'; import '../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../wallets/isar/models/wallet_info.dart'; import '../../../wallets/wallet/impl/epiccash_wallet.dart'; +import '../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../wallets/wallet/impl/monero_wallet.dart'; import '../../../wallets/wallet/impl/wownero_wallet.dart'; import '../../../wallets/wallet/supporting/epiccash_wallet_info_extension.dart'; +import '../../../wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart'; import '../../../wallets/wallet/wallet.dart'; import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../widgets/desktop/desktop_app_bar.dart'; @@ -252,11 +254,36 @@ class _RestoreWalletViewState extends ConsumerState { if (height < 0) { height = 0; } - otherDataJsonString = jsonEncode( { - WalletInfoKeys.epiccashData: jsonEncode( - ExtraEpiccashWalletInfo( + WalletInfoKeys.mimblewimblecoinData: jsonEncode( + ExtraMimblewimblecoinWalletInfo( + receivingIndex: 0, + changeIndex: 0, + slatesToAddresses: {}, + slatesToCommits: {}, + lastScannedBlock: height, + restoreHeight: height, + creationHeight: height, + ).toMap(), + ), + }, + ); + } else if (widget.coin is Mimblewimblecoin) { + final int secondsSinceEpoch = widget.restoreFromDate!.millisecondsSinceEpoch ~/ 1000; + const int mimblewimblecoinFirstBlock = 1573462801; + const double overestimateSecondsPerBlock = 61; + final int chosenSeconds = secondsSinceEpoch - mimblewimblecoinFirstBlock; + final int approximateHeight = + chosenSeconds ~/ overestimateSecondsPerBlock; + height = approximateHeight; + if (height < 0) { + height = 0; + } + otherDataJsonString = jsonEncode( + { + WalletInfoKeys.mimblewimblecoinData: jsonEncode( + ExtraMimblewimblecoinWalletInfo( receivingIndex: 0, changeIndex: 0, slatesToAddresses: {}, @@ -354,6 +381,10 @@ class _RestoreWalletViewState extends ConsumerState { case const (EpiccashWallet): await (wallet as EpiccashWallet).init(isRestore: true); break; + + case const (MimblewimblecoinWallet): + await (wallet as MimblewimblecoinWallet).init(isRestore: true); + break; case const (MoneroWallet): await (wallet as MoneroWallet).init(isRestore: true); diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index 5090645e3..ce08150f3 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -14,6 +14,7 @@ import 'dart:io'; import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_libepiccash/lib.dart'; +import 'package:flutter_libmwc/lib.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; @@ -33,6 +34,7 @@ import '../../utilities/constants.dart'; import '../../utilities/text_styles.dart'; import '../../utilities/util.dart'; import '../../wallets/crypto_currency/coins/epiccash.dart'; +import '../../wallets/crypto_currency/coins/mimblewimblecoin.dart'; import '../../wallets/crypto_currency/intermediate/nano_currency.dart'; import '../../wallets/isar/providers/eth/current_token_wallet_provider.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart'; @@ -159,7 +161,7 @@ class _ConfirmTransactionViewState break; } } else { - if (coin is Epiccash) { + if (coin is Epiccash || coin is Mimblewimblecoin) { txDataFuture = wallet.confirmSend( txData: widget.txData.copyWith( noteOnChain: onChainNoteController.text, @@ -578,11 +580,13 @@ class _ConfirmTransactionViewState ], ), ), - if (coin is Epiccash && widget.txData.noteOnChain!.isNotEmpty) + if ((coin is Epiccash || coin is Mimblewimblecoin) && + widget.txData.noteOnChain!.isNotEmpty) const SizedBox( height: 12, ), - if (coin is Epiccash && widget.txData.noteOnChain!.isNotEmpty) + if ((coin is Epiccash || coin is Mimblewimblecoin) && + widget.txData.noteOnChain!.isNotEmpty) RoundedWhiteContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -611,7 +615,9 @@ class _ConfirmTransactionViewState crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( - (coin is Epiccash) ? "Local Note" : "Note", + (coin is Epiccash || coin is Mimblewimblecoin) + ? "Local Note" + : "Note", style: STextStyles.smallMed12(context), ), const SizedBox( @@ -913,17 +919,17 @@ class _ConfirmTransactionViewState mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) Text( "On chain Note (optional)", style: STextStyles.smallMed12(context), textAlign: TextAlign.left, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) const SizedBox( height: 8, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -963,12 +969,12 @@ class _ConfirmTransactionViewState ), ), ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) const SizedBox( height: 12, ), SelectableText( - (coin is Epiccash) + (coin is Epiccash || coin is Mimblewimblecoin) ? "Local Note (optional)" : "Note (optional)", style: diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 584d868d2..63c6b267c 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -311,7 +311,9 @@ class _SendViewState extends ConsumerState { _cryptoAmountChangedFeeUpdateTimer?.cancel(); _cryptoAmountChangedFeeUpdateTimer = Timer(updateFeesTimerDuration, () { - if (coin is! Epiccash && !_baseFocus.hasFocus) { + if (coin is! Epiccash && + coin is! Mimblewimblecoin && + !_baseFocus.hasFocus) { setState(() { _calculateFeesFuture = calculateFees( amount == null @@ -332,7 +334,9 @@ class _SendViewState extends ConsumerState { void _baseAmountChanged() { _baseAmountChangedFeeUpdateTimer?.cancel(); _baseAmountChangedFeeUpdateTimer = Timer(updateFeesTimerDuration, () { - if (coin is! Epiccash && !_cryptoFocus.hasFocus) { + if (coin is! Epiccash && + coin is! Mimblewimblecoin && + !_cryptoFocus.hasFocus) { setState(() { _calculateFeesFuture = calculateFees( ref.read(pSendAmount) == null @@ -1116,6 +1120,21 @@ class _SendViewState extends ConsumerState { }); } + if (coin is Mimblewimblecoin) { + sendToController.addListener(() { + _address = sendToController.text.trim(); + + if (_address != null && _address!.isNotEmpty) { + _address = _address!.trim(); + if (_address!.contains("\n")) { + _address = _address!.substring(0, _address!.indexOf("\n")); + } + + sendToController.text = AddressUtils().formatAddress(_address!); + } + }); + } + return Background( child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, @@ -1447,6 +1466,14 @@ class _SendViewState extends ConsumerState { content, ); } + if (coin + is Mimblewimblecoin) { + // strip http:// and https:// if content contains @ + content = AddressUtils() + .formatAddress( + content, + ); + } sendToController.text = content.trim(); _address = content.trim(); @@ -2030,17 +2057,17 @@ class _SendViewState extends ConsumerState { const SizedBox( height: 12, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) Text( "On chain Note (optional)", style: STextStyles.smallMed12(context), textAlign: TextAlign.left, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) const SizedBox( height: 8, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -2084,12 +2111,12 @@ class _SendViewState extends ConsumerState { ), ), ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) const SizedBox( height: 12, ), Text( - (coin is Epiccash) + (coin is Epiccash || coin is Mimblewimblecoin) ? "Local Note (optional)" : "Note (optional)", style: STextStyles.smallMed12(context), @@ -2141,6 +2168,7 @@ class _SendViewState extends ConsumerState { height: 12, ), if (coin is! Epiccash && + coin is! Mimblewimblecoin && coin is! NanoCurrency && coin is! Tezos) Text( @@ -2149,12 +2177,14 @@ class _SendViewState extends ConsumerState { textAlign: TextAlign.left, ), if (coin is! Epiccash && + coin is! Mimblewimblecoin && coin is! NanoCurrency && coin is! Tezos) const SizedBox( height: 8, ), if (coin is! Epiccash && + coin is! Mimblewimblecoin && coin is! NanoCurrency && coin is! Tezos) Stack( diff --git a/lib/pages/settings_views/global_settings_view/about_view.dart b/lib/pages/settings_views/global_settings_view/about_view.dart index b72c3222c..d29aae7dc 100644 --- a/lib/pages/settings_views/global_settings_view/about_view.dart +++ b/lib/pages/settings_views/global_settings_view/about_view.dart @@ -268,6 +268,54 @@ class AboutView extends ConsumerWidget { ); }, ), + if (AppConfig.coins + .whereType() + .isNotEmpty) + const SizedBox( + height: 12, + ), + if (AppConfig.coins + .whereType() + .isNotEmpty) + FutureBuilder( + future: GitStatus.getMimblewimblecoinCommitStatus(), + builder: ( + context, + AsyncSnapshot snapshot, + ) { + CommitStatus stateOfCommit = + CommitStatus.notLoaded; + + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + stateOfCommit = snapshot.data!; + } + + return RoundedWhiteContainer( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + Text( + "Mimblewimblecoin Build Commit", + style: STextStyles.titleBold12(context), + ), + const SizedBox( + height: 4, + ), + SelectableText( + GitStatus.mimblewimblecoinCommit, + style: GitStatus.styleForStatus( + stateOfCommit, + context, + ), + ), + ], + ), + ); + }, + ), if (AppConfig.coins.whereType().isNotEmpty) const SizedBox( height: 12, diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart index 92e5cc256..8315302df 100644 --- a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart +++ b/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart @@ -19,6 +19,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; // import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; +import 'package:flutter_libmwc/git_versions.dart' as MIMBLEWIMBLECOIN_VERSIONS; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; @@ -315,6 +316,9 @@ class _DebugViewState extends ConsumerState { EPIC_VERSIONS.getPluginVersion(); // final String moneroCommit = // MONERO_VERSIONS.getPluginVersion(); + final String mimblewimblecoinCommit = + MIMBLEWIMBLECOIN_VERSIONS + .getPluginVersion(); final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); final deviceInfo = @@ -348,6 +352,8 @@ class _DebugViewState extends ConsumerState { "firoCommit": firoCommit, "epicCashCommit": epicCashCommit, // "moneroCommit": moneroCommit, + "mimblewimblecoinCommit": + mimblewimblecoinCommit, "deviceInfoMap": deviceInfoMap, "errorLogs": errorLogs, }; diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 7888635b7..7b5f4f25e 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -78,6 +78,17 @@ class _AddEditNodeViewState extends ConsumerState { ref.read(nodeFormDataProvider).host = data.host; ref.read(nodeFormDataProvider).port = data.port; ref.read(nodeFormDataProvider).useSSL = data.useSSL; + } else if (coin is Mimblewimblecoin) { + ref.read(nodeFormDataProvider).host = data.host; + ref.read(nodeFormDataProvider).port = data.port; + ref.read(nodeFormDataProvider).useSSL = data.useSSL; + } else if (coin is CryptonoteCurrency) { + ref.read(nodeFormDataProvider).host = data.host; + } + if (coin is Mimblewimblecoin) { + ref.read(nodeFormDataProvider).host = data.host; + ref.read(nodeFormDataProvider).port = data.port; + ref.read(nodeFormDataProvider).useSSL = data.useSSL; } else if (coin is CryptonoteCurrency) { ref.read(nodeFormDataProvider).host = data.host; } @@ -706,6 +717,8 @@ class _NodeFormState extends ConsumerState { _trusted = node.trusted ?? false; if (widget.coin is Epiccash) { enableSSLCheckbox = !node.host.startsWith("http"); + } else if (widget.coin is Mimblewimblecoin) { + enableSSLCheckbox = !node.host.startsWith("http"); } else { enableSSLCheckbox = true; } @@ -846,6 +859,17 @@ class _NodeFormState extends ConsumerState { _useSSL = true; } } + if (widget.coin is Mimblewimblecoin) { + if (newValue.startsWith("https://")) { + _useSSL = true; + enableSSLCheckbox = false; + } else if (newValue.startsWith("http://")) { + _useSSL = false; + enableSSLCheckbox = false; + } else { + enableSSLCheckbox = true; + } + } _updateState(); setState(() {}); }, @@ -1105,11 +1129,15 @@ class _NodeFormState extends ConsumerState { ), ], ), - if (widget.coin is! CryptonoteCurrency && widget.coin is! Epiccash) + if (widget.coin is! CryptonoteCurrency && + widget.coin is! Epiccash && + widget.coin is! Mimblewimblecoin) const SizedBox( height: 8, ), - if (widget.coin is! CryptonoteCurrency && widget.coin is! Epiccash) + if (widget.coin is! CryptonoteCurrency && + widget.coin is! Epiccash && + widget.coin is! Mimblewimblecoin) Row( children: [ GestureDetector( diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart index 32430d228..2dcc60456 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart @@ -52,6 +52,7 @@ import '../../../../../wallets/isar/models/frost_wallet_info.dart'; import '../../../../../wallets/isar/models/wallet_info.dart'; import '../../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart'; import '../../../../../wallets/wallet/impl/epiccash_wallet.dart'; +import '../../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../../../wallets/wallet/impl/monero_wallet.dart'; import '../../../../../wallets/wallet/impl/wownero_wallet.dart'; import '../../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; @@ -503,7 +504,7 @@ abstract class SWB { Future? restoringFuture; - if (!(wallet is LibMoneroWallet || wallet is EpiccashWallet)) { + if (!(wallet is LibMoneroWallet || wallet is EpiccashWallet || wallet is MimblewimblecoinWallet)) { if (wallet is BitcoinFrostWallet) { restoringFuture = wallet.recover( isRescan: false, diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart index 1be577f1d..b88e9ce92 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart @@ -33,10 +33,12 @@ import '../../../../utilities/constants.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; import '../../../../wallets/crypto_currency/coins/epiccash.dart'; +import '../../../../wallets/crypto_currency/coins/mimblewimblecoin.dart'; import '../../../../wallets/crypto_currency/coins/monero.dart'; import '../../../../wallets/crypto_currency/coins/wownero.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/wallet/impl/epiccash_wallet.dart'; +import '../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../../wallets/wallet/impl/monero_wallet.dart'; import '../../../../wallets/wallet/impl/wownero_wallet.dart'; import '../../../../widgets/animated_text.dart'; @@ -263,7 +265,10 @@ class _WalletNetworkSettingsViewState final coin = ref.read(pWalletCoin(widget.walletId)); - if (coin is Monero || coin is Wownero || coin is Epiccash) { + if (coin is Monero || + coin is Wownero || + coin is Epiccash || + coin is Mimblewimblecoin) { _blocksRemainingSubscription = eventBus.on().listen( (event) async { if (event.walletId == widget.walletId) { @@ -343,6 +348,14 @@ class _WalletNetworkSettingsViewState if (_percent < highestPercent) { _percent = highestPercent.clamp(0.0, 1.0); } + } else if (coin is Mimblewimblecoin) { + final double highestPercent = (ref + .watch(pWallets) + .getWallet(widget.walletId) as MimblewimblecoinWallet) + .highestPercent; + if (_percent < highestPercent) { + _percent = highestPercent.clamp(0.0, 1.0); + } } return ConditionalParent( @@ -363,7 +376,11 @@ class _WalletNetworkSettingsViewState style: STextStyles.navBarTitle(context), ), actions: [ - if (ref.watch(pWalletCoin(widget.walletId)) is! Epiccash) + if (ref.watch(pWalletCoin(widget.walletId)) is! Epiccash && + ref.watch(pWalletCoin(widget.walletId)) + is! Mimblewimblecoin || + ref.watch(pWalletCoin(widget.walletId)) + is! Mimblewimblecoin) Padding( padding: const EdgeInsets.only( top: 10, @@ -627,6 +644,7 @@ class _WalletNetworkSettingsViewState ), if (coin is Monero || coin is Wownero || + coin is Mimblewimblecoin || coin is Epiccash) Text( " (Blocks to go: ${_blocksRemaining == -1 ? "?" : _blocksRemaining})", @@ -950,11 +968,15 @@ class _WalletNetworkSettingsViewState coin: ref.watch(pWalletCoin(widget.walletId)), popBackToRoute: WalletNetworkSettingsView.routeName, ), - if (isDesktop && ref.watch(pWalletCoin(widget.walletId)) is! Epiccash) + if (isDesktop && + ref.watch(pWalletCoin(widget.walletId)) is! Epiccash && + ref.watch(pWalletCoin(widget.walletId)) is! Mimblewimblecoin) const SizedBox( height: 32, ), - if (isDesktop && ref.watch(pWalletCoin(widget.walletId)) is! Epiccash) + if (isDesktop && + ref.watch(pWalletCoin(widget.walletId)) is! Epiccash && + ref.watch(pWalletCoin(widget.walletId)) is! Mimblewimblecoin) Padding( padding: const EdgeInsets.only( bottom: 12, @@ -970,7 +992,9 @@ class _WalletNetworkSettingsViewState ], ), ), - if (isDesktop && ref.watch(pWalletCoin(widget.walletId)) is! Epiccash) + if (isDesktop && + ref.watch(pWalletCoin(widget.walletId)) is! Epiccash && + ref.watch(pWalletCoin(widget.walletId)) is! Mimblewimblecoin) RoundedWhiteContainer( borderColor: isDesktop ? Theme.of(context).extension()!.background diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart index 494834b02..7fcfa9be9 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart @@ -18,6 +18,7 @@ import 'package:tuple/tuple.dart'; import '../../../db/hive/db.dart'; import '../../../db/sqlite/firo_cache.dart'; import '../../../models/epicbox_config_model.dart'; +import '../../../models/mwcmqs_config_model.dart'; import '../../../models/keys/key_data_interface.dart'; import '../../../notifications/show_flush_bar.dart'; import '../../../providers/global/wallets_provider.dart'; @@ -37,6 +38,7 @@ import '../../../wallets/crypto_currency/intermediate/nano_currency.dart'; import '../../../wallets/wallet/impl/bitcoin_frost_wallet.dart'; import '../../../wallets/wallet/impl/epiccash_wallet.dart'; import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; @@ -694,3 +696,103 @@ class _EpiBoxInfoFormState extends ConsumerState { ); } } + +class MwcMqsInfoForm extends ConsumerStatefulWidget { + const MwcMqsInfoForm({ + super.key, + required this.walletId, + }); + + final String walletId; + + @override + ConsumerState createState() => _MwcmqsInfoFormState(); +} + +class _MwcmqsInfoFormState extends ConsumerState { + final hostController = TextEditingController(); + final portController = TextEditingController(); + + late MimblewimblecoinWallet wallet; + + @override + void initState() { + wallet = + ref.read(pWallets).getWallet(widget.walletId) as MimblewimblecoinWallet; + + wallet.getMwcMqsConfig().then((MwcMqsConfigModel mwcmqsConfig) { + hostController.text = mwcmqsConfig.host; + portController.text = "${mwcmqsConfig.port ?? 443}"; + }); + super.initState(); + } + + @override + void dispose() { + hostController.dispose(); + portController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: hostController, + decoration: const InputDecoration(hintText: "Host"), + ), + const SizedBox( + height: 8, + ), + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: portController, + decoration: const InputDecoration(hintText: "Port"), + keyboardType: + Util.isDesktop ? null : const TextInputType.numberWithOptions(), + ), + const SizedBox( + height: 8, + ), + TextButton( + onPressed: () async { + try { + await wallet.updateMwcmqsConfig( + hostController.text, + int.parse(portController.text), + ); + if (mounted) { + await showFloatingFlushBar( + context: context, + message: "Mwcmqs info saved!", + type: FlushBarType.success, + ); + } + unawaited(wallet.refresh()); + } catch (e) { + await showFloatingFlushBar( + context: context, + message: "Failed to save mwcmqs info: $e", + type: FlushBarType.warning, + ); + } + }, + child: Text( + "Save", + style: STextStyles.button(context).copyWith( + color: + Theme.of(context).extension()!.accentColorDark, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart index 5afd82597..385bb8a2e 100644 --- a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart +++ b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart @@ -873,6 +873,10 @@ class _DesktopTransactionCardRowState return "Restored Funds"; } + if (coin is Mimblewimblecoin && _transaction.slateId == null) { + return "Restored Funds"; + } + if (_transaction.subType == TransactionSubType.mint) { if (_transaction.isConfirmed(height, minConfirms)) { return "Anonymized"; @@ -967,6 +971,19 @@ class _DesktopTransactionCardRowState ); return; } + + if (coin is Mimblewimblecoin && _transaction.slateId == null) { + unawaited( + showFloatingFlushBar( + context: context, + message: + "Restored Mimblewimblecoin funds from your Seed have no Data.", + type: FlushBarType.warning, + duration: const Duration(seconds: 5), + ), + ); + return; + } if (Util.isDesktop) { await showDialog( context: context, diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index 1071ffdae..777f2b75e 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -15,6 +15,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import 'package:tuple/tuple.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -1730,7 +1731,7 @@ class _TransactionDetailsViewState ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, - floatingActionButton: (coin is Epiccash && + floatingActionButton: ((coin is Epiccash || coin is Mimblewimblecoin) && _transaction.getConfirmations(currentHeight) < 1 && _transaction.isCancelled == false) ? ConditionalParent( @@ -1775,6 +1776,59 @@ class _TransactionDetailsViewState ), ); + final result = + await wallet.cancelPendingTransactionAndPost(id); + if (mounted) { + // pop progress dialog + Navigator.of(context).pop(); + + if (result.isEmpty) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Transaction cancelled", + onOkPressed: (_) { + wallet.refresh(); + Navigator.of(context).popUntil( + ModalRoute.withName( + WalletView.routeName, + ), + ); + }, + ), + ); + } else { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Failed to cancel transaction", + message: result, + ), + ); + } + } + } else if (wallet is MimblewimblecoinWallet) { + final String? id = _transaction.slateId; + if (id == null) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Could not find MWC transaction ID", + context: context, + ), + ); + return; + } + + unawaited( + showDialog( + barrierDismissible: false, + context: context, + builder: (_) => + const CancellingTransactionProgressDialog(), + ), + ); + final result = await wallet.cancelPendingTransactionAndPost(id); if (mounted) { @@ -1810,7 +1864,7 @@ class _TransactionDetailsViewState unawaited( showFloatingFlushBar( type: FlushBarType.warning, - message: "ERROR: Wallet type is not Epic Cash", + message: "ERROR: Wallet type is not Epic Cash or MimbleWimbleCoin", context: context, ), ); diff --git a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart index 666d8d041..737b5e660 100644 --- a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart @@ -42,6 +42,7 @@ import '../../../../wallets/crypto_currency/intermediate/nano_currency.dart'; import '../../../../wallets/isar/models/spark_coin.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/wallet/impl/epiccash_wallet.dart'; +import '../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; import '../../../../widgets/background.dart'; @@ -241,6 +242,33 @@ class _TransactionV2DetailsViewState ), ) .toList(); + } else if (_transaction.isMimblewimblecoinTransaction) { + switch (_transaction.type) { + case TransactionType.outgoing: + case TransactionType.unknown: + amount = _transaction.getAmountSentFromThisWallet( + fractionDigits: fractionDigits, + ); + break; + + case TransactionType.incoming: + case TransactionType.sentToSelf: + amount = _transaction.getAmountReceivedInThisWallet( + fractionDigits: fractionDigits, + ); + break; + } + data = _transaction.outputs + .map( + (e) => ( + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, + ) + ), + ) + .toList(); } else if (_transaction.subType == TransactionSubType.cashFusion) { amount = _transaction.getAmountReceivedInThisWallet( fractionDigits: fractionDigits, @@ -1053,13 +1081,13 @@ class _TransactionV2DetailsViewState ], ), ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) isDesktop ? const _Divider() : const SizedBox( height: 12, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) @@ -1131,7 +1159,8 @@ class _TransactionV2DetailsViewState MainAxisAlignment.spaceBetween, children: [ Text( - (coin is Epiccash) + (coin is Epiccash || + coin is Mimblewimblecoin) ? "Local Note" : "Note ", style: isDesktop @@ -1202,7 +1231,9 @@ class _TransactionV2DetailsViewState .watch( pTransactionNote( ( - txid: (coin is Epiccash) + txid: (coin is Epiccash || + coin + is Mimblewimblecoin) ? _transaction.slateId .toString() : _transaction.txid, @@ -1478,8 +1509,9 @@ class _TransactionV2DetailsViewState ? "${_transaction.height!}" : "Pending"; confirmations = confirms.toString(); - } else if (widget.coin is Epiccash && - _transaction.slateId == null) { + } else if (widget.coin is Epiccash || + coin is Mimblewimblecoin && + _transaction.slateId == null) { confirmations = "Unknown"; height = "Unknown"; } else { @@ -1487,7 +1519,9 @@ class _TransactionV2DetailsViewState currentHeight, minConfirms, ); - if (widget.coin is! Epiccash && confirmed) { + if (widget.coin is! Epiccash && + widget.coin is! Mimblewimblecoin && + confirmed) { height = "${_transaction.height == 0 ? "Unknown" : _transaction.height}"; } else { @@ -1748,11 +1782,13 @@ class _TransactionV2DetailsViewState context, ), ), - if (coin is! Epiccash) + if (coin is! Epiccash && + coin is! Mimblewimblecoin) const SizedBox( height: 8, ), - if (coin is! Epiccash) + if (coin is! Epiccash && + coin is! Mimblewimblecoin) CustomTextButton( text: "Open in block explorer", onTap: () async { @@ -1909,13 +1945,13 @@ class _TransactionV2DetailsViewState // ], // ), // ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) isDesktop ? const _Divider() : const SizedBox( height: 12, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) @@ -1999,9 +2035,10 @@ class _TransactionV2DetailsViewState ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, - floatingActionButton: (coin is Epiccash && - _transaction.getConfirmations(currentHeight) < 1 && - _transaction.isCancelled == false) + floatingActionButton: (coin is Epiccash || + coin is Mimblewimblecoin && + _transaction.getConfirmations(currentHeight) < 1 && + _transaction.isCancelled == false) ? ConditionalParent( condition: isDesktop, builder: (child) => Padding( @@ -2044,6 +2081,60 @@ class _TransactionV2DetailsViewState ), ); + final result = + await wallet.cancelPendingTransactionAndPost(id); + if (mounted) { + // pop progress dialog + Navigator.of(context).pop(); + + if (result.isEmpty) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Transaction cancelled", + onOkPressed: (_) { + wallet.refresh(); + Navigator.of(context).popUntil( + ModalRoute.withName( + WalletView.routeName, + ), + ); + }, + ), + ); + } else { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Failed to cancel transaction", + message: result, + ), + ); + } + } + } else if (wallet is MimblewimblecoinWallet) { + final String? id = _transaction.slateId; + if (id == null) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: + "Could not find Mimblewimblecoin transaction ID", + context: context, + ), + ); + return; + } + + unawaited( + showDialog( + barrierDismissible: false, + context: context, + builder: (_) => + const CancellingTransactionProgressDialog(), + ), + ); + final result = await wallet.cancelPendingTransactionAndPost(id); if (mounted) { @@ -2079,7 +2170,7 @@ class _TransactionV2DetailsViewState unawaited( showFloatingFlushBar( type: FlushBarType.warning, - message: "ERROR: Wallet type is not Epic Cash", + message: "ERROR: Wallet type is not Epic Cash or MimbleWimbleCoin", context: context, ), ); diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 80dea6795..a09f60b10 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -482,6 +482,11 @@ class _DesktopSendState extends ConsumerState { noteOnChain: _onChainNote ?? "", ); } + if (coin is Mimblewimblecoin) { + txData = txData.copyWith( + noteOnChain: _onChainNote ?? "", + ); + } } // pop building dialog Navigator.of( @@ -840,6 +845,9 @@ class _DesktopSendState extends ConsumerState { if (coin is Epiccash) { content = AddressUtils().formatAddress(content); } + if (coin is Mimblewimblecoin) { + content = AddressUtils().formatAddressMwc(content); + } sendToController.text = content; _address = content; @@ -855,6 +863,10 @@ class _DesktopSendState extends ConsumerState { // strip http:// and https:// if content contains @ content = AddressUtils().formatAddress(content); } + if (coin is Mimblewimblecoin) { + // strip http:// and https:// if content contains @ + content = AddressUtils().formatAddressMwc(content); + } sendToController.text = content; _address = content; @@ -1092,6 +1104,21 @@ class _DesktopSendState extends ConsumerState { }); } + if (coin is Mimblewimblecoin) { + sendToController.addListener(() { + _address = sendToController.text; + + if (_address != null && _address!.isNotEmpty) { + _address = _address!.trim(); + if (_address!.contains("\n")) { + _address = _address!.substring(0, _address!.indexOf("\n")); + } + + sendToController.text = formatAddressMwc(_address!); + } + }); + } + final firoType = ref.watch(publicPrivateBalanceStateProvider); final isExchangeAddress = ref.watch(pIsExchangeAddress); @@ -1765,7 +1792,10 @@ class _DesktopSendState extends ConsumerState { const SizedBox( height: 20, ), - if (coin is! NanoCurrency && coin is! Epiccash && coin is! Tezos) + if (coin is! NanoCurrency && + coin is! Epiccash && + coin is! Mimblewimblecoin && + coin is! Tezos) ConditionalParent( condition: ref.watch(pWallets).getWallet(walletId) is ElectrumXInterface && @@ -1821,11 +1851,17 @@ class _DesktopSendState extends ConsumerState { textAlign: TextAlign.left, ), ), - if (coin is! NanoCurrency && coin is! Epiccash && coin is! Tezos) + if (coin is! NanoCurrency && + coin is! Epiccash && + coin is! Mimblewimblecoin && + coin is! Tezos) const SizedBox( height: 10, ), - if (coin is! NanoCurrency && coin is! Epiccash && coin is! Tezos) + if (coin is! NanoCurrency && + coin is! Epiccash && + coin is! Mimblewimblecoin && + coin is! Tezos) if (!isCustomFee) Padding( padding: const EdgeInsets.all(10), @@ -2030,3 +2066,25 @@ String formatAddress(String epicAddress) { } return epicAddress; } + +String formatAddressMwc(String mimblewimblecoinAddress) { + // strip http:// or https:// prefixes if the address contains an @ symbol (and is thus an mwcmqs address) + if ((mimblewimblecoinAddress.startsWith("http://") || + mimblewimblecoinAddress.startsWith("https://")) && + mimblewimblecoinAddress.contains("@")) { + mimblewimblecoinAddress = mimblewimblecoinAddress.replaceAll("http://", ""); + mimblewimblecoinAddress = + mimblewimblecoinAddress.replaceAll("https://", ""); + } + // strip mailto: prefix + if (mimblewimblecoinAddress.startsWith("mailto:")) { + mimblewimblecoinAddress = mimblewimblecoinAddress.replaceAll("mailto:", ""); + } + // strip / suffix if the address contains an @ symbol (and is thus an mwcmqs address) + if (mimblewimblecoinAddress.endsWith("/") && + mimblewimblecoinAddress.contains("@")) { + mimblewimblecoinAddress = mimblewimblecoinAddress.substring( + 0, mimblewimblecoinAddress.length - 1); + } + return mimblewimblecoinAddress; +} diff --git a/lib/pages_desktop_specific/password/delete_password_warning_view.dart b/lib/pages_desktop_specific/password/delete_password_warning_view.dart index e8a4c5256..2aae2e1fc 100644 --- a/lib/pages_desktop_specific/password/delete_password_warning_view.dart +++ b/lib/pages_desktop_specific/password/delete_password_warning_view.dart @@ -63,6 +63,12 @@ class _ForgotPasswordDesktopViewState await epicDir.delete(recursive: true); } + final mimblewimblecoinDir = + Directory("${appRoot.path}/mimblewimblecoin"); + if (mimblewimblecoinDir.existsSync()) { + await mimblewimblecoinDir.delete(recursive: true); + } + await Isar.getInstance("desktopStore")?.close(deleteFromDisk: true); await (await StackFileSystem.applicationHiveDirectory()) @@ -79,6 +85,12 @@ class _ForgotPasswordDesktopViewState if (epicDir.existsSync()) { await epicDir.delete(recursive: true); } + final mimblewimblecoinDir = + Directory("${appRoot.path}/mimblewimblecoin"); + if (mimblewimblecoinDir.existsSync()) { + await mimblewimblecoinDir.delete(recursive: true); + } + await (await StackFileSystem.applicationHiveDirectory()) .delete(recursive: true); await (await StackFileSystem.applicationIsarDirectory()) diff --git a/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart b/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart index 98a7dec34..7d48217bc 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart @@ -434,63 +434,122 @@ class DesktopAboutView extends ConsumerWidget { ); }, ), - // if (AppConfig.coins - // .whereType() - // .isNotEmpty) - // FutureBuilder( - // future: GitStatus - // .getMoneroCommitStatus(), - // builder: ( - // context, - // AsyncSnapshot - // snapshot, - // ) { - // CommitStatus stateOfCommit = - // CommitStatus.notLoaded; + if (AppConfig.coins + .whereType() + .isNotEmpty) + FutureBuilder( + future: GitStatus + .getMimblewimblecoinCommitStatus(), + builder: ( + context, + AsyncSnapshot + snapshot, + ) { + CommitStatus stateOfCommit = + CommitStatus.notLoaded; + + if (snapshot.connectionState == + ConnectionState + .done && + snapshot.hasData) { + stateOfCommit = + snapshot.data!; + } + + return Column( + mainAxisSize: + MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Text( + "Mimblewimblecoin Build Commit", + style: STextStyles + .desktopTextExtraExtraSmall( + context, + ).copyWith( + color: Theme.of( + context, + ) + .extension< + StackColors>()! + .textDark, + ), + ), + const SizedBox( + height: 2, + ), + SelectableText( + GitStatus + .mimblewimblecoinCommit, + style: GitStatus + .styleForStatus( + stateOfCommit, + context, + ), + ), + ], + ); + }, + ), + //if (AppConfig.coins + // .whereType() + // .isNotEmpty) + // FutureBuilder( + // future: GitStatus + // .getMoneroCommitStatus(), + // builder: ( + // context, + // AsyncSnapshot + // snapshot, + // ) { + // CommitStatus stateOfCommit = + // CommitStatus.notLoaded; // - // if (snapshot.connectionState == - // ConnectionState - // .done && - // snapshot.hasData) { - // stateOfCommit = - // snapshot.data!; - // } - // return Column( - // mainAxisSize: - // MainAxisSize.min, - // crossAxisAlignment: - // CrossAxisAlignment - // .start, - // children: [ - // Text( - // "Monero Build Commit", - // style: STextStyles - // .desktopTextExtraExtraSmall( - // context, - // ).copyWith( - // color: Theme.of( - // context, - // ) - // .extension< - // StackColors>()! - // .textDark, - // ), - // ), - // const SizedBox( - // height: 2, - // ), - // SelectableText( - // GitStatus.moneroCommit, - // style: GitStatus - // .styleForStatus( - // stateOfCommit, - // context, - // ), - // ), - // ], - // ); - // }, - // ), + // if (snapshot.connectionState == + // ConnectionState + // .done && + // snapshot.hasData) { + // stateOfCommit = + // snapshot.data!; + // } + // return Column( + // mainAxisSize: + // MainAxisSize.min, + // crossAxisAlignment: + // CrossAxisAlignment + // .start, + // children: [ + // Text( + // "Monero Build Commit", + // style: STextStyles + // .desktopTextExtraExtraSmall( + // context, + // ).copyWith( + // color: Theme.of( + // context, + // ) + // .extension< + // StackColors>()! + // .textDark, + // ), + // ), + // const SizedBox( + // height: 2, + // ), + // SelectableText( + // GitStatus.moneroCommit, + // style: GitStatus + // .styleForStatus( + // stateOfCommit, + // context, + // ), + // ), + // ], + // ); + // }, + // ), ], ), const SizedBox(height: 35), diff --git a/lib/services/price.dart b/lib/services/price.dart index 5a97e43cb..071ce5980 100644 --- a/lib/services/price.dart +++ b/lib/services/price.dart @@ -34,6 +34,7 @@ class PriceAPI { Dash: "dash", Dogecoin: "dogecoin", Epiccash: "epic-cash", + Mimblewimblecoin: "mimblewimblecoin", Ecash: "ecash", Ethereum: "ethereum", Firo: "zcoin", diff --git a/lib/services/wallets.dart b/lib/services/wallets.dart index d7b93cf33..1e6c250b7 100644 --- a/lib/services/wallets.dart +++ b/lib/services/wallets.dart @@ -25,6 +25,7 @@ import '../wallets/crypto_currency/crypto_currency.dart'; import '../wallets/isar/models/wallet_info.dart'; import '../wallets/wallet/impl/epiccash_wallet.dart'; import '../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../wallets/wallet/wallet.dart'; import 'event_bus/events/wallet_added_event.dart'; import 'event_bus/global_event_bus.dart'; @@ -110,6 +111,15 @@ class Wallets { "epic wallet: $walletId deleted with result: $deleteResult", level: LogLevel.Info, ); + } else if (info.coin is Mimblewimblecoin) { + final deleteResult = await deleteMimblewimblecoinWallet( + walletId: walletId, + secureStore: secureStorage, + ); + Logging.instance.log( + "Mimblewimblecoin wallet: $walletId deleted with result: $deleteResult", + level: LogLevel.Info, + ); } // delete wallet data in main db diff --git a/lib/themes/coin_icon_provider.dart b/lib/themes/coin_icon_provider.dart index 4a789ad76..1deb22b4e 100644 --- a/lib/themes/coin_icon_provider.dart +++ b/lib/themes/coin_icon_provider.dart @@ -28,6 +28,8 @@ final coinIconProvider = Provider.family((ref, coin) { return assets.dogecoin; case const (Epiccash): return assets.epicCash; + case const (Mimblewimblecoin): + return assets.mimblewimblecoin; case const (Firo): return assets.firo; case const (Monero): diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index 869aa5895..b3c2404f0 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -46,6 +46,8 @@ class AddressUtils { // return Dogecoin(CryptoCurrencyNetwork.main).validateAddress(address); // case Coin.epicCash: // return Epiccash(CryptoCurrencyNetwork.main).validateAddress(address); + // case Coin.mimblewimblecoin: + // return Mimblewimblecoin(CryptoCurrencyNetwork.main).validateAddress(address); // case Coin.ethereum: // return Ethereum(CryptoCurrencyNetwork.main).validateAddress(address); // case Coin.firo: @@ -372,6 +374,31 @@ class AddressUtils { } return epicAddress; } + + /// Formats an address string to remove any unnecessary prefixes or suffixes. + String formatAddressMwc(String mimblewimblecoinAddress) { + // strip http:// or https:// prefixes if the address contains an @ symbol (and is thus an mwcmqs address) + if ((mimblewimblecoinAddress.startsWith("http://") || + mimblewimblecoinAddress.startsWith("https://")) && + mimblewimblecoinAddress.contains("@")) { + mimblewimblecoinAddress = + mimblewimblecoinAddress.replaceAll("http://", ""); + mimblewimblecoinAddress = + mimblewimblecoinAddress.replaceAll("https://", ""); + } + // strip mailto: prefix + if (mimblewimblecoinAddress.startsWith("mailto:")) { + mimblewimblecoinAddress = + mimblewimblecoinAddress.replaceAll("mailto:", ""); + } + // strip / suffix if the address contains an @ symbol (and is thus an mwcmqs address) + if (mimblewimblecoinAddress.endsWith("/") && + mimblewimblecoinAddress.contains("@")) { + mimblewimblecoinAddress = mimblewimblecoinAddress.substring( + 0, mimblewimblecoinAddress.length - 1); + } + return mimblewimblecoinAddress; + } } class PaymentUriData { diff --git a/lib/utilities/amount/amount_unit.dart b/lib/utilities/amount/amount_unit.dart index a8cbfa701..79e45232b 100644 --- a/lib/utilities/amount/amount_unit.dart +++ b/lib/utilities/amount/amount_unit.dart @@ -63,6 +63,7 @@ enum AmountUnit { // case Coin.dogecoin: // case Coin.eCash: // case Coin.epicCash: + // case Coin.mimblewimblecoin: // case Coin.stellar: // TODO: check if this is correct // case Coin.stellarTestnet: // case Coin.tezos: diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index e0245131a..3a1001ad8 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -245,6 +245,7 @@ class _SVG { String get bitcoincash => "assets/svg/coin_icons/Bitcoincash.svg"; String get dogecoin => "assets/svg/coin_icons/Dogecoin.svg"; String get epicCash => "assets/svg/coin_icons/EpicCash.svg"; + String get mimblewimblecoin => "assets/svg/coin_icons/Mimblewimblecoin.svg"; String get ethereum => "assets/svg/coin_icons/Ethereum.svg"; String get firo => "assets/svg/coin_icons/Firo.svg"; String get monero => "assets/svg/coin_icons/Monero.svg"; diff --git a/lib/utilities/default_mwcmqs.dart b/lib/utilities/default_mwcmqs.dart new file mode 100644 index 000000000..84e7356d8 --- /dev/null +++ b/lib/utilities/default_mwcmqs.dart @@ -0,0 +1,53 @@ +/* + * 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-05-26 + * + */ + +import '../models/mwcmqs_server_model.dart'; + +abstract class DefaultMwcMqs { + static const String defaultName = "Default"; + + static List get all => [americas, asia, europe]; + static List get defaultIds => ['americas', 'asia', 'europe']; + + static MwcMqsServerModel get americas => MwcMqsServerModel( + host: 'mqs.mwc.mw', + port: 443, + name: 'Americas', + id: 'americas', + useSSL: true, + enabled: true, + isFailover: true, + isDown: false, + ); + + static MwcMqsServerModel get asia => MwcMqsServerModel( + host: 'mqs.mwc.mw', + port: 443, + name: 'Asia', + id: 'asia', + useSSL: true, + enabled: true, + isFailover: true, + isDown: false, + ); + + static MwcMqsServerModel get europe => MwcMqsServerModel( + host: 'mqs.mwc.mw', + port: 443, + name: 'Europe', + id: 'europe', + useSSL: true, + enabled: true, + isFailover: true, + isDown: false, + ); + + static final defaultMwcMqsServer = americas; +} diff --git a/lib/utilities/git_status.dart b/lib/utilities/git_status.dart index 093bc39af..17617c0fa 100644 --- a/lib/utilities/git_status.dart +++ b/lib/utilities/git_status.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_libepiccash/git_versions.dart' as epic_versions; // import 'package:flutter_libmonero/git_versions.dart' as monero_versions; +import 'package:flutter_libmwc/git_versions.dart' as mimblewimblecoin_versions; import 'package:http/http.dart'; import 'package:lelantus/git_versions.dart' as firo_versions; @@ -21,6 +22,8 @@ abstract class GitStatus { static String get firoCommit => firo_versions.getPluginVersion(); static String get epicCashCommit => epic_versions.getPluginVersion(); // static String get moneroCommit => monero_versions.getPluginVersion(); + static String get mimblewimblecoinCommit => + mimblewimblecoin_versions.getPluginVersion(); static String get appCommitHash => AppConfig.commitHash; @@ -78,31 +81,60 @@ abstract class GitStatus { return _cachedEpicStatus!; } + + static CommitStatus? _cachedMimblewimblecoinStatus; + static Future getMimblewimblecoinCommitStatus() async { + if (_cachedMimblewimblecoinStatus != null) { + return _cachedMimblewimblecoinStatus!; + } + final List results = await Future.wait([ + _doesCommitExist("cypherstack", "flutter_libmwc", mimblewimblecoinCommit), + _isHeadCommit( + "cypherstack", + "flutter_libmwc", + "main", + mimblewimblecoinCommit, + ), + ]); + + final commitExists = results[0]; + final commitIsHead = results[1]; + + if (commitExists && commitIsHead) { + _cachedMimblewimblecoinStatus = CommitStatus.isHead; + } else if (commitExists) { + _cachedMimblewimblecoinStatus = CommitStatus.isOldCommit; + } else { + _cachedMimblewimblecoinStatus = CommitStatus.notACommit; + } + + return _cachedMimblewimblecoinStatus!; + } + + //static CommitStatus? _cachedMoneroStatus; + //static Future getMoneroCommitStatus() async { + // if (_cachedMoneroStatus != null) { + // return _cachedMoneroStatus!; + // } // - // static CommitStatus? _cachedMoneroStatus; - // static Future getMoneroCommitStatus() async { - // if (_cachedMoneroStatus != null) { - // return _cachedMoneroStatus!; - // } - // - // final List results = await Future.wait([ - // _doesCommitExist("cypherstack", "flutter_libmonero", moneroCommit), - // _isHeadCommit("cypherstack", "flutter_libmonero", "main", moneroCommit), - // ]); + // final List results = await Future.wait([ + // _doesCommitExist("cypherstack", "flutter_libmonero", moneroCommit), + // _isHeadCommit("cypherstack", "flutter_libmonero", "main", moneroCommit), + // ]); // - // final commitExists = results[0]; - // final commitIsHead = results[1]; + // final commitExists = results[0]; + // final commitIsHead = results[1]; // - // if (commitExists && commitIsHead) { - // _cachedMoneroStatus = CommitStatus.isHead; - // } else if (commitExists) { - // _cachedMoneroStatus = CommitStatus.isOldCommit; - // } else { - // _cachedMoneroStatus = CommitStatus.notACommit; - // } + // if (commitExists && commitIsHead) { + // _cachedMoneroStatus = CommitStatus.isHead; + // } else if (commitExists) { + // _cachedMoneroStatus = CommitStatus.isOldCommit; + // } else { + // _cachedMoneroStatus = CommitStatus.notACommit; + // } // - // return _cachedMoneroStatus!; - // } + // return _cachedMoneroStatus!; + //} static TextStyle styleForStatus(CommitStatus status, BuildContext context) { final Color color; diff --git a/lib/utilities/test_mwcmqs_connection.dart b/lib/utilities/test_mwcmqs_connection.dart new file mode 100644 index 000000000..cfcd4a098 --- /dev/null +++ b/lib/utilities/test_mwcmqs_connection.dart @@ -0,0 +1,91 @@ +/* + * 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-05-26 + * + */ + +import 'dart:convert'; + +import '../networking/http.dart'; +import '../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart'; +import '../services/tor_service.dart'; +import 'logger.dart'; +import 'prefs.dart'; + +Future _testMwcMqsNodeConnection(Uri uri) async { + final HTTP client = HTTP(); + try { + final headers = { + 'Content-Type': 'application/json', + }; + + if (uri.toString() == 'https://mwc713.mwc.mw/v1/version') { + const username = 'mwcmain'; + const password = '11ne3EAUtOXVKwhxm84U'; + final credentials = base64Encode(utf8.encode('$username:$password')); + headers['Authorization'] = 'Basic $credentials'; + } + final response = await client + .get( + url: uri, + headers: headers, + proxyInfo: Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, + ) + .timeout( + const Duration(milliseconds: 2000), + onTimeout: () async => Response(utf8.encode('Error'), 408), + ); + + final json = jsonDecode(response.body); + + if (response.code == 200 && json["node_version"] != null) { + return true; + } else { + return false; + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Warning); + return false; + } +} + +// returns node data with properly formatted host/url if successful, otherwise null +Future testMwcNodeConnection(NodeFormData data) async { + if (data.host == null || data.port == null || data.useSSL == null) { + return null; + } + const String path_postfix = "/v1/version"; + + if (data.host!.startsWith("https://")) { + data.useSSL = true; + } else if (data.host!.startsWith("http://")) { + data.useSSL = false; + } else { + if (data.useSSL!) { + data.host = "https://${data.host!}"; + } else { + data.host = "http://${data.host!}"; + } + } + + Uri uri = Uri.parse(data.host! + path_postfix); + + uri = uri.replace(port: data.port); + + try { + if (await _testMwcMqsNodeConnection(uri)) { + return data; + } else { + return null; + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Warning); + return null; + } +} diff --git a/lib/utilities/test_node_connection.dart b/lib/utilities/test_node_connection.dart index 458e04e4d..a66352453 100644 --- a/lib/utilities/test_node_connection.dart +++ b/lib/utilities/test_node_connection.dart @@ -22,6 +22,7 @@ import '../wallets/wallet/impl/solana_wallet.dart'; import 'connection_check/electrum_connection_check.dart'; import 'logger.dart'; import 'test_epic_box_connection.dart'; +import 'test_mwcmqs_connection.dart'; import 'test_eth_node_connection.dart'; import 'test_monero_node_connection.dart'; import 'test_stellar_node_connection.dart'; @@ -45,7 +46,6 @@ Future _xmrHelper( final uriString = "${uri.scheme}://${uri.host}:${port ?? 0}$path"; - if (proxyInfo == null && uri.host.endsWith(".onion")) { return false; } @@ -109,6 +109,19 @@ Future testNodeConnection({ } break; + case Mimblewimblecoin(): + try { + final data = await testMwcNodeConnection(formData); + + if (data != null) { + testPassed = true; + onSuccess?.call(data); + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Warning); + } + break; + case CryptonoteCurrency(): try { final proxyInfo = ref diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart new file mode 100644 index 000000000..3127ef035 --- /dev/null +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -0,0 +1,125 @@ +import 'package:flutter_libmwc/lib.dart' as mimblewimblecoin; + +import '../../../models/isar/models/blockchain_data/address.dart'; +import '../../../models/node_model.dart'; +import '../../../utilities/default_nodes.dart'; +import '../../../utilities/enums/derive_path_type_enum.dart'; +import '../crypto_currency.dart'; +import '../intermediate/bip39_currency.dart'; + +class Mimblewimblecoin extends Bip39Currency { + Mimblewimblecoin(super.network) { + _idMain = "mimblewimblecoin"; + _uriScheme = "mimblewimblecoin"; // ? + switch (network) { + case CryptoCurrencyNetwork.main: + _id = _idMain; + _name = "MimbleWimbleCoin"; + _ticker = "MWC"; + default: + throw Exception("Unsupported network: $network"); + } + } + + late final String _id; + @override + String get identifier => _id; + + late final String _idMain; + @override + String get mainNetId => _idMain; + + late final String _name; + @override + String get prettyName => _name; + + late final String _uriScheme; + @override + String get uriScheme => _uriScheme; + + late final String _ticker; + @override + String get ticker => _ticker; + + @override + String get genesisHash { + return "not used in mimblewimblecoin"; + } + + @override + // change this to change the number of confirms a tx needs in order to show as confirmed + int get minConfirms => 3; + + @override +bool validateAddress(String address) { + Uri? uri = Uri.tryParse(address); + if (uri != null && + (uri.scheme == "http" || uri.scheme == "https" || uri.scheme == "mwcmqs") && + uri.host.isNotEmpty && + !uri.host.endsWith(".onion")) { + return true; + } + return mimblewimblecoin.Libmwc.validateSendAddress(address: address); +} + + + @override + NodeModel get defaultNode { + switch (network) { + case CryptoCurrencyNetwork.main: + return NodeModel( + host: "https://mwc713.mwc.mw", + port: 443, + name: DefaultNodes.defaultName, + id: DefaultNodes.buildId(this), + useSSL: true, + enabled: true, + coinName: identifier, + isFailover: true, + isDown: false + ); + + default: + throw UnimplementedError(); + } + } + + @override + int get defaultSeedPhraseLength => 12; + + @override + int get fractionDigits => 9; + + @override + bool get hasBuySupport => false; + + @override + bool get hasMnemonicPassphraseSupport => false; + + @override + List get possibleMnemonicLengths => [defaultSeedPhraseLength, 24]; + + @override + AddressType get defaultAddressType => AddressType.mimbleWimble; + + @override + BigInt get satsPerCoin => BigInt.from(1000000000); + + @override + int get targetBlockTimeSeconds => 60; + + @override + DerivePathType get defaultDerivePathType => throw UnsupportedError( + "$runtimeType does not use bitcoin style derivation paths", + ); + + @override + Uri defaultBlockExplorer(String txid) { + switch (network) { + default: + throw Exception( + "Unsupported network for defaultBlockExplorer(): $network", + ); + } + } +} diff --git a/lib/wallets/crypto_currency/crypto_currency.dart b/lib/wallets/crypto_currency/crypto_currency.dart index 074536be6..4d2acc401 100644 --- a/lib/wallets/crypto_currency/crypto_currency.dart +++ b/lib/wallets/crypto_currency/crypto_currency.dart @@ -11,6 +11,7 @@ export 'coins/dash.dart'; export 'coins/dogecoin.dart'; export 'coins/ecash.dart'; export 'coins/epiccash.dart'; +export 'coins/mimblewimblecoin.dart'; export 'coins/ethereum.dart'; export 'coins/firo.dart'; export 'coins/litecoin.dart'; @@ -61,7 +62,7 @@ abstract class CryptoCurrency { int get minConfirms; - // TODO: [prio=low] could be handled differently as (at least) epiccash does not use this + // TODO: [prio=low] could be handled differently as (at least) epiccash/mimblewimblecoin does not use this String get genesisHash; bool validateAddress(String address); diff --git a/lib/wallets/isar/models/wallet_info.dart b/lib/wallets/isar/models/wallet_info.dart index 3e296a3a0..ec805e6ac 100644 --- a/lib/wallets/isar/models/wallet_info.dart +++ b/lib/wallets/isar/models/wallet_info.dart @@ -507,6 +507,7 @@ class WalletInfo implements IsarId { abstract class WalletInfoKeys { static const String tokenContractAddresses = "tokenContractAddressesKey"; static const String epiccashData = "epiccashDataKey"; + static const String mimblewimblecoinData = "mimblewimblecoinDataKey"; static const String bananoMonkeyImageBytes = "monkeyImageBytesKey"; static const String tezosDerivationPath = "tezosDerivationPathKey"; static const String lelantusCoinIsarRescanRequired = diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart new file mode 100644 index 000000000..31339e2fc --- /dev/null +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -0,0 +1,1183 @@ +import 'dart:async'; +import 'dart:convert'; +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/isar.dart'; +import 'package:mutex/mutex.dart'; +import 'package:stack_wallet_backup/generate_password.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +import '../../../models/balance.dart'; +import '../../../models/mwcmqs_config_model.dart'; +import '../../../models/isar/models/blockchain_data/address.dart'; +import '../../../models/isar/models/blockchain_data/transaction.dart'; +import '../../../models/isar/models/blockchain_data/v2/input_v2.dart'; +import '../../../models/isar/models/blockchain_data/v2/output_v2.dart'; +import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart'; +import '../../../models/node_model.dart'; +import '../../../models/paymint/fee_object_model.dart'; +import '../../../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart'; +import '../../../services/event_bus/events/global/blocks_remaining_event.dart'; +import '../../../services/event_bus/events/global/node_connection_status_changed_event.dart'; +import '../../../services/event_bus/events/global/refresh_percent_changed_event.dart'; +import '../../../services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import '../../../services/event_bus/global_event_bus.dart'; +import '../../../utilities/amount/amount.dart'; +import '../../../utilities/flutter_secure_storage_interface.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/stack_file_system.dart'; +import '../../../utilities/default_mwcmqs.dart'; +import '../../../utilities/test_mwcmqs_connection.dart'; +import '../../crypto_currency/crypto_currency.dart'; +import '../../models/tx_data.dart'; +import '../intermediate/bip39_wallet.dart'; +import '../supporting/mimblewimblecoin_wallet_info_extension.dart'; + + +class MimblewimblecoinWallet extends Bip39Wallet { + MimblewimblecoinWallet(CryptoCurrencyNetwork network) + : super(Mimblewimblecoin(network)); + + final syncMutex = Mutex(); + NodeModel? _mimblewimblecoinNode; + Timer? timer; + bool _logsInitialized = false; + + double highestPercent = 0; + Future get getSyncPercent async { + final int lastScannedBlock = + info.mimblewimblecoinData?.lastScannedBlock ?? 0; + final _chainHeight = await chainHeight; + final double restorePercent = lastScannedBlock / _chainHeight; + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(highestPercent, walletId)); + if (restorePercent > highestPercent) { + highestPercent = restorePercent; + } + + final int blocksRemaining = _chainHeight - lastScannedBlock; + GlobalEventBus.instance + .fire(BlocksRemainingEvent(blocksRemaining, walletId)); + + return restorePercent < 0 ? 0.0 : restorePercent; + } + + Future updateMwcmqsConfig(String host, int port) async { + final String stringConfig = jsonEncode({ + "mwcmqs_domain": host, + "mwcmqs_port": port + }); + await secureStorageInterface.write( + key: '${walletId}_mwcmqsConfig', + value: stringConfig, + ); + // TODO: refresh anything that needs to be refreshed/updated due to mwcmqs info changed + } + + /// returns an empty String on success, error message on failure + Future cancelPendingTransactionAndPost(String txSlateId) async { + try { + final String wallet = (await secureStorageInterface.read( + key: '${walletId}_wallet', + ))!; + + final result = await mimblewimblecoin.Libmwc.cancelTransaction( + wallet: wallet, + transactionId: txSlateId, + ); + Logging.instance.log( + "cancel $txSlateId result: $result", + level: LogLevel.Info, + ); + return result; + } catch (e, s) { + Logging.instance.log("$e, $s", level: LogLevel.Error); + return e.toString(); + } + } + + Future getMwcMqsConfig() async { + final MwcMqsConfigModel _mwcMqsConfig = MwcMqsConfigModel.fromServer( + DefaultMwcMqs.defaultMwcMqsServer, + ); + + //Get the default mwcmqs server and check if it's conected + // bool ismwcmqsConnected = await _testmwcmqsServer( + // Defaultmwcmqses.defaultmwcmqsServer.host, Defaultmwcmqses.defaultmwcmqsServer.port ?? 443); + + // if (ismwcmqsConnected) { + //Use default server for as mwcmqs config + + // } + // else { + // //Use Europe config + // _mwcmqsConfig = mwcmqsConfigModel.fromServer(Defaultmwcmqses.europe); + // } + // // example of selecting another random server from the default list + // // alternative servers: copy list of all default EB servers but remove the default default + // // List alternativeServers = Defaultmwcmqses.all; + // // alternativeServers.removeWhere((opt) => opt.name == Defaultmwcmqses.defaultmwcmqsServer.name); + // // alternativeServers.shuffle(); // randomize which server is used + // // _mwcmqsConfig = mwcmqsConfigModel.fromServer(alternativeServers.first); + // + // // TODO test this connection before returning it + // } + + return _mwcMqsConfig; + } + + // ================= Private ================================================= + + Future _getConfig() async { + if (_mimblewimblecoinNode == null) { + await updateNode(); + } + final NodeModel node = _mimblewimblecoinNode!; + final String nodeAddress = node.host; + final int port = node.port; + + final uri = Uri.parse(nodeAddress).replace(port: port); + + final String nodeApiAddress = uri.toString(); + final walletDir = await _currentWalletDirPath(); + + final Map config = {}; + config["wallet_dir"] = walletDir; + config["check_node_api_http_addr"] = nodeApiAddress; + config["chain"] = "mainnet"; + config["account"] = "default"; + final String stringConfig = jsonEncode(config); + return stringConfig; + } + + Future _currentWalletDirPath() async { + final Directory appDir = await StackFileSystem.applicationRootDirectory(); + + final path = "${appDir.path}/mimblewimblecoin"; + final String name = walletId.trim(); + return '$path/$name'; + } + + Future _nativeFee( + int satoshiAmount, { + bool ifErrorEstimateFee = false, + }) async { + final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + try { + final available = info.cachedBalance.spendable.raw.toInt(); + final transactionFees = await mimblewimblecoin.Libmwc.getTransactionFees( + wallet: wallet!, + amount: satoshiAmount, + minimumConfirmations: cryptoCurrency.minConfirms, + available: available, + ); + + int realFee = 0; + try { + realFee = + (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); + rethrow; + } + } + + Future _startSync() async { + Logging.instance.log("request start sync", level: LogLevel.Info); + final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + const int refreshFromNode = 1; + if (!syncMutex.isLocked) { + await syncMutex.protect(() async { + // How does getWalletBalances start syncing???? + await mimblewimblecoin.Libmwc.getWalletBalances( + wallet: wallet!, + refreshFromNode: refreshFromNode, + minimumConfirmations: 10, + ); + }); + } else { + Logging.instance.log("request start sync denied", level: LogLevel.Info); + } + } + + Future< + ({ + double awaitingFinalization, + double pending, + double spendable, + double total + })> _allWalletBalances() async { + final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + const refreshFromNode = 0; + return await mimblewimblecoin.Libmwc.getWalletBalances( + wallet: wallet!, + refreshFromNode: refreshFromNode, + minimumConfirmations: cryptoCurrency.minConfirms, + ); + } + + Future _testMwcmqsServer(MwcMqsConfigModel mwcmqsConfig) async { + final host = mwcmqsConfig.host; + final port = mwcmqsConfig.port ?? 443; + WebSocketChannel? channel; + try { + final uri = Uri.parse('wss://$host:$port'); + + channel = WebSocketChannel.connect( + uri, + ); + + await channel.ready; + + final response = await channel.stream.first.timeout( + const Duration(seconds: 2), + ); + + return response is String && response.contains("Challenge"); + } catch (_) { + Logging.instance.log( + "_testMwcmqsConnection failed on \"$host:$port\"", + level: LogLevel.Info, + ); + return false; + } finally { + await channel?.sink.close(); + } + } + + Future _putSendToAddresses( + ({String slateId, String commitId}) slateData, + Map txAddressInfo, + ) async { + try { + final slatesToCommits = info.mimblewimblecoinData?.slatesToCommits ?? {}; + final from = txAddressInfo['from']; + final to = txAddressInfo['to']; + slatesToCommits[slateData.slateId] = { + "commitId": slateData.commitId, + "from": from, + "to": to, + }; + await info.updateExtraMimblewimblecoinWalletInfo( + mimblewimblecoinData: info.mimblewimblecoinData!.copyWith( + slatesToCommits: slatesToCommits, + ), + isar: mainDB.isar, + ); + return true; + } catch (e, s) { + Logging.instance + .log("ERROR STORING ADDRESS $e $s", level: LogLevel.Error); + return false; + } + } + + Future _getCurrentIndex() async { + try { + final int receivingIndex = info.mimblewimblecoinData!.receivingIndex; + // TODO: go through pendingarray and processed array and choose the index + // of the last one that has not been processed, or the index after the one most recently processed; + return receivingIndex; + } catch (e, s) { + Logging.instance.log("$e $s", level: LogLevel.Error); + return 0; + } + } + + Future

_generateAndStoreReceivingAddressForIndex( + int index, + ) async { + Address? address = await getCurrentReceivingAddress(); + + if (address == null) { + final mwcmqsConfig = await getMwcMqsConfig(); + address = await thisWalletAddress(index, mwcmqsConfig); + } + + if (info.cachedReceivingAddress != address.value) { + await info.updateReceivingAddress( + newAddress: address.value, + isar: mainDB.isar, + ); + } + return address; + } + + Future
thisWalletAddress( + int index, + MwcMqsConfigModel mwcmqsConfig, + ) async { + final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + + final walletAddress = await mimblewimblecoin.Libmwc.getAddressInfo( + wallet: wallet!, + index: index + ); + + Logging.instance.log( + "WALLET_ADDRESS_IS $walletAddress", + level: LogLevel.Info, + ); + + final address = Address( + walletId: walletId, + value: walletAddress, + derivationIndex: index, + derivationPath: null, + type: AddressType.mimbleWimble, + subType: AddressSubType.receiving, + publicKey: [], // ?? + ); + await mainDB.updateOrPutAddresses([address]); + return address; + } + + Future _startScans() async { + try { + //First stop the current listener + mimblewimblecoin.Libmwc.stopMwcMqsListener(); + final wallet = + await secureStorageInterface.read(key: '${walletId}_wallet'); + + // max number of blocks to scan per loop iteration + const scanChunkSize = 10000; + + // force firing of scan progress event + await getSyncPercent; + + // fetch current chain height and last scanned block (should be the + // restore height if full rescan or a wallet restore) + int chainHeight = await this.chainHeight; + int lastScannedBlock = info.mimblewimblecoinData!.lastScannedBlock; + + // loop while scanning in chain in chunks (of blocks?) + while (lastScannedBlock < chainHeight) { + Logging.instance.log( + "chainHeight: $chainHeight, lastScannedBlock: $lastScannedBlock", + level: LogLevel.Info, + ); + + final int nextScannedBlock = await mimblewimblecoin.Libmwc.scanOutputs( + wallet: wallet!, + startHeight: lastScannedBlock, + numberOfBlocks: scanChunkSize, + ); + + // update local cache + await info.updateExtraMimblewimblecoinWalletInfo( + mimblewimblecoinData: info.mimblewimblecoinData!.copyWith( + lastScannedBlock: nextScannedBlock, + ), + isar: mainDB.isar, + ); + + // force firing of scan progress event + await getSyncPercent; + + // update while loop condition variables + chainHeight = await this.chainHeight; + lastScannedBlock = nextScannedBlock; + } + + Logging.instance.log( + "_startScans successfully at the tip", + level: LogLevel.Info, + ); + //Once scanner completes restart listener + await _listenToMwcmqs(); + } catch (e, s) { + Logging.instance.log( + "_startScans failed: $e\n$s", + level: LogLevel.Error, + ); + rethrow; + } + } + + Future _listenToMwcmqs() async { + Logging.instance.log("STARTING WALLET LISTENER ....", level: LogLevel.Info); + final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); + mimblewimblecoin.Libmwc.startMwcMqsListener( + wallet: wallet!, + mwcmqsConfig: mwcmqsConfig.toString(), + ); + } + + // As opposed to fake config? + Future _getRealConfig() async { + String? config = await secureStorageInterface.read( + key: '${walletId}_config', + ); + if (Platform.isIOS) { + final walletDir = await _currentWalletDirPath(); + final editConfig = jsonDecode(config as String); + + editConfig["wallet_dir"] = walletDir; + config = jsonEncode(editConfig); + } + return config!; + } + + int _calculateRestoreHeightFrom({required DateTime date}) { + final int secondsSinceEpoch = date.millisecondsSinceEpoch ~/ 1000; + const int mimblewimblecoinFirstBlock = 1565370278; + const double overestimateSecondsPerBlock = 61; + final int chosenSeconds = secondsSinceEpoch - mimblewimblecoinFirstBlock; + final int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock; + int height = approximateHeight; + if (height < 0) { + height = 0; + } + return height; + } + + // ============== Overrides ================================================== + + @override + int get isarTransactionVersion => 2; + + @override + FilterOperation? get changeAddressFilterOperation => + FilterGroup.and(standardChangeAddressFilters); + + @override + FilterOperation? get receivingAddressFilterOperation => + FilterGroup.and(standardReceivingAddressFilters); + + @override + Future checkSaveInitialReceivingAddress() async { + // epiccash seems ok with nothing here? + } + + @override + Future init({bool? isRestore}) async { + + if (isRestore != true) { + String? encodedWallet = + await secureStorageInterface.read(key: "${walletId}_wallet"); + + // check if should create a new wallet + if (encodedWallet == null) { + await updateNode(); + final mnemonicString = await getMnemonic(); + + final String password = generatePassword(); + final String stringConfig = await _getConfig(); + final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); + //if (!_logsInitialized) { + // await mimblewimblecoin.Libmwc.initLogs(config: stringConfig); + // _logsInitialized = true; // Set flag to true after initializing + // } + await secureStorageInterface.write( + key: '${walletId}_config', + value: stringConfig, + ); + await secureStorageInterface.write( + key: '${walletId}_password', + value: password, + ); + await secureStorageInterface.write( + key: '${walletId}_mwcmqsConfig', + value: mwcmqsConfig.toString(), + ); + + final String name = walletId; + + await mimblewimblecoin.Libmwc.initializeNewWallet( + config: stringConfig, + mnemonic: mnemonicString, + password: password, + name: name, + ); + + //Open wallet + encodedWallet = await mimblewimblecoin.Libmwc.openWallet( + config: stringConfig, + password: password, + ); + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: encodedWallet, + ); + //Store MwcMqs address info + await _generateAndStoreReceivingAddressForIndex(0); + + // subtract a couple days to ensure we have a buffer for SWB + final bufferedCreateHeight = _calculateRestoreHeightFrom( + date: DateTime.now().subtract(const Duration(days: 2)), + ); + + final mimblewimblecoinData = ExtraMimblewimblecoinWalletInfo( + receivingIndex: 0, + changeIndex: 0, + slatesToAddresses: {}, + slatesToCommits: {}, + lastScannedBlock: bufferedCreateHeight, + restoreHeight: bufferedCreateHeight, + creationHeight: bufferedCreateHeight, + ); + + await info.updateExtraMimblewimblecoinWalletInfo( + mimblewimblecoinData: mimblewimblecoinData, + isar: mainDB.isar, + ); + } else { + try { + final config = await _getRealConfig(); + //if (!_logsInitialized) { + // await mimblewimblecoin.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( + config: config, + password: password!, + ); + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: walletOpen, + ); + + await updateNode(); + } catch (e, s) { + // do nothing, still allow user into wallet + Logging.instance.log( + "$runtimeType init() failed: $e\n$s", + level: LogLevel.Error, + ); + } + } + } + + return await super.init(); + } + + @override + Future confirmSend({required TxData txData}) async { + try { + final wallet = + await secureStorageInterface.read(key: '${walletId}_wallet'); + final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); + + // TODO determine whether it is worth sending change to a change address. + + final String receiverAddress = txData.recipients!.first.address; + + //if (!receiverAddress.startsWith("http://") || + // !receiverAddress.startsWith("https://")) { + // final bool isMwcmqsConnected = await _testMwcmqsServer( + // mwcmqsConfig, + // ); + // if (!isMwcmqsConnected) { + // throw Exception( + // "Failed to send TX : Unable to reach mimblewimblecoin server"); + // } + //} + + ({String commitId, String slateId}) transaction; + + if (receiverAddress.startsWith("http://") || + receiverAddress.startsWith("https://")) { + transaction = await mimblewimblecoin.Libmwc.txHttpSend( + wallet: wallet!, + selectionStrategyIsAll: 0, + minimumConfirmations: cryptoCurrency.minConfirms, + message: txData.noteOnChain ?? "", + amount: txData.recipients!.first.amount.raw.toInt(), + address: txData.recipients!.first.address, + ); + } else if (receiverAddress.startsWith("mwcmqs://")) { + transaction = await mimblewimblecoin.Libmwc.createTransaction( + wallet: wallet!, + amount: txData.recipients!.first.amount.raw.toInt(), + address: txData.recipients!.first.address, + secretKeyIndex: 0, + mwcmqsConfig: mwcmqsConfig.toString(), + minimumConfirmations: cryptoCurrency.minConfirms, + note: txData.noteOnChain!, + ); + + } else { + throw Exception( + "Unsupported address format: $receiverAddress. Please use a valid address.", + ); + + } + + final Map txAddressInfo = {}; + txAddressInfo['from'] = (await getCurrentReceivingAddress())!.value; + txAddressInfo['to'] = txData.recipients!.first.address; + await _putSendToAddresses(transaction, txAddressInfo); + + return txData.copyWith( + txid: transaction.slateId, + ); + } catch (e, s) { + Logging.instance.log( + "Mimblewimblecoin confirmSend: $e\n$s", + level: LogLevel.Error, + ); + rethrow; + } + } + + @override + Future prepareSend({required TxData txData}) async { + try { + if (txData.recipients?.length != 1) { + throw Exception( + "Mimblewimblecoin prepare send requires a single recipient!"); + } + + ({String address, Amount amount, bool isChange}) recipient = + txData.recipients!.first; + + final int realFee = await _nativeFee(recipient.amount.raw.toInt()); + final feeAmount = Amount( + rawValue: BigInt.from(realFee), + fractionDigits: cryptoCurrency.fractionDigits, + ); + + if (feeAmount > info.cachedBalance.spendable) { + throw Exception( + "Mimblewimblecoin prepare send fee is greater than available balance!", + ); + } + + if (info.cachedBalance.spendable == recipient.amount) { + recipient = ( + address: recipient.address, + amount: recipient.amount - feeAmount, + isChange: recipient.isChange, + ); + } + + return txData.copyWith( + recipients: [recipient], + fee: feeAmount, + ); + } catch (e, s) { + Logging.instance + .log("Mimblewimblecoin prepareSend: $e\n$s", level: LogLevel.Error); + rethrow; + } + } + + @override + Future recover({required bool isRescan}) async { + try { + await refreshMutex.protect(() async { + if (isRescan) { + // clear blockchain info + await mainDB.deleteWalletBlockchainData(walletId); + + await info.updateExtraMimblewimblecoinWalletInfo( + mimblewimblecoinData: info.mimblewimblecoinData!.copyWith( + lastScannedBlock: info.mimblewimblecoinData!.restoreHeight, + ), + isar: mainDB.isar, + ); + + unawaited(_startScans()); + } else { + await updateNode(); + final String password = generatePassword(); + + final String stringConfig = await _getConfig(); + final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); + + await secureStorageInterface.write( + key: '${walletId}_config', + value: stringConfig, + ); + await secureStorageInterface.write( + key: '${walletId}_password', + value: password, + ); + + await secureStorageInterface.write( + key: '${walletId}_mwcmqsConfig', + value: mwcmqsConfig.toString(), + ); + + await mimblewimblecoin.Libmwc.recoverWallet( + config: stringConfig, + password: password, + mnemonic: await getMnemonic(), + name: info.walletId, + ); + + final mimblewimblecoinData = ExtraMimblewimblecoinWalletInfo( + receivingIndex: 0, + changeIndex: 0, + slatesToAddresses: {}, + slatesToCommits: {}, + lastScannedBlock: info.restoreHeight, + restoreHeight: info.restoreHeight, + creationHeight: + info.mimblewimblecoinData?.creationHeight ?? info.restoreHeight, + ); + + await info.updateExtraMimblewimblecoinWalletInfo( + mimblewimblecoinData: mimblewimblecoinData, + isar: mainDB.isar, + ); + + //Open Wallet + final walletOpen = await mimblewimblecoin.Libmwc.openWallet( + config: stringConfig, + password: password, + ); + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: walletOpen, + ); + + await _generateAndStoreReceivingAddressForIndex( + mimblewimblecoinData.receivingIndex, + ); + } + }); + + unawaited(refresh()); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from electrumx_mixin recover(): $e\n$s", + level: LogLevel.Info, + ); + + rethrow; + } + } + + @override + Future refresh() async { + // Awaiting this lock could be dangerous. + // Since refresh is periodic (generally) + if (refreshMutex.isLocked) { + return; + } + + try { + // this acquire should be almost instant due to above check. + // Slight possibility of race but should be irrelevant + await refreshMutex.acquire(); + + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.syncing, + walletId, + cryptoCurrency, + ), + ); + + // if (info.epicData?.creationHeight == null) { + // await info.updateExtraEpiccashWalletInfo(epicData: inf, isar: isar) + // await epicUpdateCreationHeight(await chainHeight); + // } + + // this will always be zero???? + final int curAdd = await _getCurrentIndex(); + await _generateAndStoreReceivingAddressForIndex(curAdd); + + await _startScans(); + + unawaited(_startSync()); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); + await updateChainHeight(); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId)); + + // if (this is MultiAddressInterface) { + // await (this as MultiAddressInterface) + // .checkReceivingAddressForTransactions(); + // } + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); + + // // TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided. + // if (this is MultiAddressInterface) { + // await (this as MultiAddressInterface) + // .checkChangeAddressForTransactions(); + // } + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId)); + final fetchFuture = updateTransactions(); + // if (currentHeight != storedHeight) { + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.60, walletId)); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.70, walletId)); + + await fetchFuture; + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.80, walletId)); + + // await getAllTxsToWatch(); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.90, walletId)); + + await updateBalance(); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + cryptoCurrency, + ), + ); + + if (shouldAutoSync) { + timer ??= Timer.periodic(const Duration(seconds: 150), (timer) async { + // chain height check currently broken + // if ((await chainHeight) != (await storedChainHeight)) { + + // TODO: [prio=med] some kind of quick check if wallet needs to refresh to replace the old refreshIfThereIsNewData call + // if (await refreshIfThereIsNewData()) { + unawaited(refresh()); + + // } + // } + }); + } + } catch (error, strace) { + GlobalEventBus.instance.fire( + NodeConnectionStatusChangedEvent( + NodeConnectionStatus.disconnected, + walletId, + cryptoCurrency, + ), + ); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.unableToSync, + walletId, + cryptoCurrency, + ), + ); + Logging.instance.log( + "Caught exception in refreshWalletData(): $error\n$strace", + level: LogLevel.Error, + ); + } finally { + refreshMutex.release(); + } + } + + @override + Future updateBalance() async { + try { + final balances = await _allWalletBalances(); + final balance = Balance( + total: Amount.fromDecimal( + Decimal.parse(balances.total.toString()) + + Decimal.parse(balances.awaitingFinalization.toString()), + fractionDigits: cryptoCurrency.fractionDigits, + ), + spendable: Amount.fromDecimal( + Decimal.parse(balances.spendable.toString()), + fractionDigits: cryptoCurrency.fractionDigits, + ), + blockedTotal: Amount.zeroWith( + fractionDigits: cryptoCurrency.fractionDigits, + ), + pendingSpendable: Amount.fromDecimal( + Decimal.parse(balances.pending.toString()), + fractionDigits: cryptoCurrency.fractionDigits, + ), + ); + + await info.updateBalance( + newBalance: balance, + isar: mainDB.isar, + ); + } catch (e, s) { + Logging.instance.log( + "Mimblewimblecoin wallet failed to update balance: $e\n$s", + level: LogLevel.Warning, + ); + } + } + + @override + Future updateTransactions() async { + try { + final wallet = + await secureStorageInterface.read(key: '${walletId}_wallet'); + const refreshFromNode = 1; + + 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( + wallet: wallet!, + refreshFromNode: refreshFromNode, + ); + + final List txns = []; + + final slatesToCommits = info.mimblewimblecoinData?.slatesToCommits ?? {}; + + for (final tx in transactions) { + Logging.instance.log("tx: $tx", level: LogLevel.Info); + + final isIncoming = + tx.txType == mimblewimblecoin_models.TransactionType.TxReceived || + tx.txType == + mimblewimblecoin_models.TransactionType.TxReceivedCancelled; + 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 addressFrom = slatesToCommits[slateId]?["from"] as String?; + final addressTo = slatesToCommits[slateId]?["to"] as String?; + + final credit = int.parse(tx.amountCredited); + final debit = int.parse(tx.amountDebited); + final fee = int.tryParse(tx.fee ?? "0") ?? 0; + + // hack Mimblewimblecoin tx data into inputs and outputs + final List outputs = []; + final List inputs = []; + final addressFromIsMine = myAddressesSet.contains(addressFrom); + final addressToIsMine = myAddressesSet.contains(addressTo); + + OutputV2 output = OutputV2.isarCantDoRequiredInDefaultConstructor( + scriptPubKeyHex: "00", + valueStringSats: credit.toString(), + addresses: [ + if (addressFrom != null) addressFrom, + ], + walletOwns: true, + ); + final InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor( + scriptSigHex: null, + scriptSigAsm: null, + sequence: null, + outpoint: null, + addresses: [if (addressTo != null) addressTo], + valueStringSats: debit.toString(), + witness: null, + innerRedeemScriptAsm: null, + coinbase: null, + walletOwns: true, + ); + + final TransactionType txType; + if (isIncoming) { + if (addressToIsMine && addressFromIsMine) { + txType = TransactionType.sentToSelf; + } else { + txType = TransactionType.incoming; + } + output = output.copyWith( + addresses: [ + myAddressesSet + .first, // Must be changed if we ever do more than a single wallet address!!! + ], + walletOwns: true, + ); + } else { + txType = TransactionType.outgoing; + } + + outputs.add(output); + inputs.add(input); + + final otherData = { + "isMimblewimblecoinTransaction": true, + "numberOfMessages": numberOfMessages, + "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(), + }; + + final txn = TransactionV2( + walletId: walletId, + blockHash: null, + hash: commitId ?? tx.id.toString(), + txid: commitId ?? tx.id.toString(), + timestamp: + DateTime.parse(tx.creationTs).millisecondsSinceEpoch ~/ 1000, + height: tx.confirmed ? tx.kernelLookupMinHeight ?? 1 : null, + inputs: List.unmodifiable(inputs), + outputs: List.unmodifiable(outputs), + version: 0, + type: txType, + subType: TransactionSubType.none, + otherData: jsonEncode(otherData), + ); + + txns.add(txn); + } + + await mainDB.isar.writeTxn(() async { + await mainDB.isar.transactionV2s + .where() + .walletIdEqualTo(walletId) + .deleteAll(); + await mainDB.isar.transactionV2s.putAll(txns); + }); + } catch (e, s) { + Logging.instance.log( + "${cryptoCurrency.runtimeType} ${cryptoCurrency.network} net wallet" + " \"${info.name}\"_${info.walletId} updateTransactions() failed: $e\n$s", + level: LogLevel.Warning, + ); + } + } + + @override + Future updateUTXOs() async { + // not used for mimblewimblecoin + return false; + } + + @override + Future updateNode() async { + _mimblewimblecoinNode = getCurrentNode(); + + // TODO: [prio=low] move this out of secure storage if secure storage not needed + final String stringConfig = await _getConfig(); + await secureStorageInterface.write( + key: '${walletId}_config', + value: stringConfig, + ); + + // unawaited(refresh()); + } + + @override + Future pingCheck() async { + try { + final node = nodeService.getPrimaryNodeFor(currency: cryptoCurrency); + return await testMwcNodeConnection( + NodeFormData() + ..host = node!.host + ..useSSL = node.useSSL + ..port = node.port, + ) != + null; + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Info); + return false; + } + } + + @override + Future updateChainHeight() async { + final config = await _getRealConfig(); + final latestHeight = + await mimblewimblecoin.Libmwc.getChainHeight(config: config); + await info.updateCachedChainHeight( + newHeight: latestHeight, + isar: mainDB.isar, + ); + } + + @override + Future estimateFeeFor(Amount amount, int feeRate) async { + // setting ifErrorEstimateFee doesn't do anything as its not used in the nativeFee function????? + final int currentFee = await _nativeFee( + amount.raw.toInt(), + ifErrorEstimateFee: true, + ); + return Amount( + rawValue: BigInt.from(currentFee), + fractionDigits: cryptoCurrency.fractionDigits, + ); + } + + @override + Future get fees async { + // this wasn't done before the refactor either so... + // TODO: implement _getFees + return FeeObject( + numberOfBlocksFast: 10, + numberOfBlocksAverage: 10, + numberOfBlocksSlow: 10, + fast: 1, + medium: 1, + slow: 1, + ); + } + + @override + Future updateSentCachedTxData({required TxData txData}) async { + // TODO: [prio=low] Was not used before refactor so maybe not required(?) + return txData; + } + + @override + Future exit() async { + timer?.cancel(); + timer = null; + await super.exit(); + Logging.instance + .log("Mimblewimblecoin_wallet exit finished", level: LogLevel.Info); + } +} + +Future deleteMimblewimblecoinWallet({ + required String walletId, + required SecureStorageInterface secureStore, +}) async { + final wallet = await secureStore.read(key: '${walletId}_wallet'); + String? config = await secureStore.read(key: '${walletId}_config'); + if (Platform.isIOS) { + final Directory appDir = await StackFileSystem.applicationRootDirectory(); + + final path = "${appDir.path}/mimblewimblecoin"; + final String name = walletId.trim(); + final walletDir = '$path/$name'; + + final editConfig = jsonDecode(config as String); + + editConfig["wallet_dir"] = walletDir; + config = jsonEncode(editConfig); + } + + if (wallet == null) { + return "Tried to delete non existent mimblewimblecoin wallet file with walletId=$walletId"; + } else { + try { + return mimblewimblecoin.Libmwc.deleteWallet( + wallet: wallet, + config: config!, + ); + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Error); + return "deleteMimblewimblecoinWallet($walletId) failed..."; + } + } +} diff --git a/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart b/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart new file mode 100644 index 000000000..0c876d5c5 --- /dev/null +++ b/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart @@ -0,0 +1,111 @@ +import 'dart:convert'; + +import 'package:isar/isar.dart'; +import '../../../utilities/logger.dart'; +import '../../isar/models/wallet_info.dart'; + +extension MimblewimblecoinWalletInfoExtension on WalletInfo { + ExtraMimblewimblecoinWalletInfo? get mimblewimblecoinData { + final String? data = + otherData[WalletInfoKeys.mimblewimblecoinData] as String?; + if (data == null) { + return null; + } + try { + return ExtraMimblewimblecoinWalletInfo.fromMap( + Map.from( + jsonDecode(data) as Map, + ), + ); + } catch (e, s) { + Logging.instance.log( + "ExtraMimblewimblecoinWalletInfo.fromMap failed: $e\n$s", + level: LogLevel.Error, + ); + return null; + } + } + + Future updateExtraMimblewimblecoinWalletInfo({ + required ExtraMimblewimblecoinWalletInfo mimblewimblecoinData, + required Isar isar, + }) async { + await updateOtherData( + newEntries: { + WalletInfoKeys.mimblewimblecoinData: jsonEncode(mimblewimblecoinData.toMap()), + }, + isar: isar, + ); + } +} + +/// Holds data previously stored in hive +class ExtraMimblewimblecoinWalletInfo { + final int receivingIndex; + final int changeIndex; + + // TODO [prio=low] strongly type these maps at some point + final Map slatesToAddresses; + final Map slatesToCommits; + + final int lastScannedBlock; + final int restoreHeight; + final int creationHeight; + + ExtraMimblewimblecoinWalletInfo({ + required this.receivingIndex, + required this.changeIndex, + required this.slatesToAddresses, + required this.slatesToCommits, + required this.lastScannedBlock, + required this.restoreHeight, + required this.creationHeight, + }); + + // Convert the object to JSON + Map toMap() { + return { + 'receivingIndex': receivingIndex, + 'changeIndex': changeIndex, + 'slatesToAddresses': slatesToAddresses, + 'slatesToCommits': slatesToCommits, + 'lastScannedBlock': lastScannedBlock, + 'restoreHeight': restoreHeight, + 'creationHeight': creationHeight, + }; + } + + ExtraMimblewimblecoinWalletInfo.fromMap(Map json) + : receivingIndex = json['receivingIndex'] as int, + changeIndex = json['changeIndex'] as int, + slatesToAddresses = json['slatesToAddresses'] as Map, + slatesToCommits = json['slatesToCommits'] as Map, + lastScannedBlock = json['lastScannedBlock'] as int, + restoreHeight = json['restoreHeight'] as int, + creationHeight = json['creationHeight'] as int; + + ExtraMimblewimblecoinWalletInfo copyWith({ + int? receivingIndex, + int? changeIndex, + Map? slatesToAddresses, + Map? slatesToCommits, + int? lastScannedBlock, + int? restoreHeight, + int? creationHeight, + }) { + return ExtraMimblewimblecoinWalletInfo( + receivingIndex: receivingIndex ?? this.receivingIndex, + changeIndex: changeIndex ?? this.changeIndex, + slatesToAddresses: slatesToAddresses ?? this.slatesToAddresses, + slatesToCommits: slatesToCommits ?? this.slatesToCommits, + lastScannedBlock: lastScannedBlock ?? this.lastScannedBlock, + restoreHeight: restoreHeight ?? this.restoreHeight, + creationHeight: creationHeight ?? this.creationHeight, + ); + } + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/wallets/wallet/wallet.dart b/lib/wallets/wallet/wallet.dart index 4f69b6056..3a4ccd3d3 100644 --- a/lib/wallets/wallet/wallet.dart +++ b/lib/wallets/wallet/wallet.dart @@ -33,6 +33,7 @@ import 'impl/dash_wallet.dart'; import 'impl/dogecoin_wallet.dart'; import 'impl/ecash_wallet.dart'; import 'impl/epiccash_wallet.dart'; +import 'impl/mimblewimblecoin_wallet.dart'; import 'impl/ethereum_wallet.dart'; import 'impl/firo_wallet.dart'; import 'impl/litecoin_wallet.dart'; @@ -358,6 +359,9 @@ abstract class Wallet { case const (Epiccash): return EpiccashWallet(net); + case const (Mimblewimblecoin): + return MimblewimblecoinWallet(net); + case const (Ethereum): return EthereumWallet(net); diff --git a/lib/widgets/onetime_popups/tor_has_been_add_dialog.dart b/lib/widgets/onetime_popups/tor_has_been_add_dialog.dart index 9709c46b3..05dd4311d 100644 --- a/lib/widgets/onetime_popups/tor_has_been_add_dialog.dart +++ b/lib/widgets/onetime_popups/tor_has_been_add_dialog.dart @@ -153,7 +153,7 @@ class _TorHasBeenAddedDialogState extends State<_TorHasBeenAddedDialog> { height: Util.isDesktop ? 24 : 16, ), Text( - "Note: Tor does NOT yet work for Monero or Epic Cash wallets. " + "Note: Tor does NOT yet work for Monero, Mimblewimblecoin or Epic Cash wallets. " "Opening one of these will leak your IP address.", style: Util.isDesktop ? STextStyles.desktopTextMedium(context) diff --git a/lib/widgets/transaction_card.dart b/lib/widgets/transaction_card.dart index cab5043d6..f842858e9 100644 --- a/lib/widgets/transaction_card.dart +++ b/lib/widgets/transaction_card.dart @@ -27,6 +27,7 @@ import '../utilities/constants.dart'; import '../utilities/format.dart'; import '../utilities/text_styles.dart'; import '../utilities/util.dart'; +import '../wallets/crypto_currency/coins/mimblewimblecoin.dart'; import '../wallets/crypto_currency/crypto_currency.dart'; import 'desktop/desktop_dialog.dart'; @@ -63,6 +64,10 @@ class _TransactionCardState extends ConsumerState { return "Restored Funds"; } + if (coin is Mimblewimblecoin && _transaction.slateId == null) { + return "Restored Funds"; + } + final confirmedStatus = _transaction.isConfirmed( currentHeight, minConfirms, @@ -188,6 +193,20 @@ class _TransactionCardState extends ConsumerState { ); return; } + + if (coin is Mimblewimblecoin && _transaction.slateId == null) { + unawaited( + showFloatingFlushBar( + context: context, + message: + "Restored Mimblewimblecoin funds from your Seed have no Data.", + type: FlushBarType.warning, + duration: const Duration(seconds: 5), + ), + ); + return; + } + if (Util.isDesktop) { await showDialog( context: context, diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 88c196c5e..8faaf8a51 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include #include @@ -30,6 +30,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_libepiccash_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLibepiccashPlugin"); flutter_libepiccash_plugin_register_with_registrar(flutter_libepiccash_registrar); + g_autoptr(FlPluginRegistrar) flutter_libmwc_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLibmwcPlugin"); + flutter_libmwc_plugin_register_with_registrar(flutter_libmwc_registrar); g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 72a81025c..52a5ce0a5 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_drop devicelocale flutter_libepiccash + flutter_libmwc flutter_secure_storage_linux isar_flutter_libs sqlite3_flutter_libs diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 66db749d9..6c71495b0 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,6 +12,7 @@ import desktop_drop import device_info_plus import devicelocale import flutter_libepiccash +import flutter_libmwc import flutter_local_notifications import flutter_secure_storage_macos import isar_flutter_libs @@ -34,6 +35,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin")) FlutterLibepiccashPlugin.register(with: registry.registrar(forPlugin: "FlutterLibepiccashPlugin")) + FlutterLibmwcPlugin.register(with: registry.registrar(forPlugin: "FlutterLibmwcPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 96a16a3ae..48728fb53 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -810,6 +810,13 @@ packages: relative: true source: path version: "0.0.1" + flutter_libmwc: + dependency: "direct main" + description: + path: "crypto_plugins/flutter_libmwc" + relative: true + source: path + version: "0.0.1" flutter_libsparkmobile: dependency: "direct main" description: diff --git a/scripts/app_config/configure_stack_wallet.sh b/scripts/app_config/configure_stack_wallet.sh index 0fd8e5e8a..1139cbfc8 100755 --- a/scripts/app_config/configure_stack_wallet.sh +++ b/scripts/app_config/configure_stack_wallet.sh @@ -62,6 +62,7 @@ final List _supportedCoins = List.unmodifiable([ Dogecoin(CryptoCurrencyNetwork.main), Ecash(CryptoCurrencyNetwork.main), Epiccash(CryptoCurrencyNetwork.main), + Mimblewimblecoin(CryptoCurrencyNetwork.main), Ethereum(CryptoCurrencyNetwork.main), Firo(CryptoCurrencyNetwork.main), Litecoin(CryptoCurrencyNetwork.main), diff --git a/scripts/app_config/templates/linux/CMakeLists.txt b/scripts/app_config/templates/linux/CMakeLists.txt index 25750ef4f..3707ec1b4 100644 --- a/scripts/app_config/templates/linux/CMakeLists.txt +++ b/scripts/app_config/templates/linux/CMakeLists.txt @@ -137,7 +137,10 @@ install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libepiccash/scripts/linux/build/rust/target/x86_64-unknown-linux-gnu/release/libepic_cash_wallet.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) -install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_liblelantus/scripts/linux/build/libmobileliblelantus.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libmwc/scripts/linux/build/rust/target/x86_64-unknown-linux-gnu/release/libmwc_wallet.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_liblelantus/scripts/linux/build/libmobileliblelantus.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/jsoncpp/build/src/lib_json/libjsoncpp.so.1.7.4" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" diff --git a/scripts/app_config/templates/pubspec.template b/scripts/app_config/templates/pubspec.template index e54f270b7..59279398d 100644 --- a/scripts/app_config/templates/pubspec.template +++ b/scripts/app_config/templates/pubspec.template @@ -45,6 +45,9 @@ dependencies: flutter_libepiccash: path: ./crypto_plugins/flutter_libepiccash + flutter_libmwc: + path: ./crypto_plugins/flutter_libmwc + bitcoindart: git: url: https://github.com/cypherstack/bitcoindart.git diff --git a/scripts/app_config/templates/windows/CMakeLists.txt b/scripts/app_config/templates/windows/CMakeLists.txt index b9add856d..dd039195e 100644 --- a/scripts/app_config/templates/windows/CMakeLists.txt +++ b/scripts/app_config/templates/windows/CMakeLists.txt @@ -83,6 +83,9 @@ install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libepiccash/scripts/windows/build/libepic_cash_wallet.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libmwc/scripts/windows/build/libmwc_cash_wallet.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_liblelantus/scripts/windows/build/libmobileliblelantus.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) diff --git a/scripts/ios/build_all.sh b/scripts/ios/build_all.sh index bcb03e991..aa9d52aee 100755 --- a/scripts/ios/build_all.sh +++ b/scripts/ios/build_all.sh @@ -16,6 +16,8 @@ rustup target add x86_64-apple-ios (cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/ios/ && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh ) diff --git a/scripts/ios/build_all_campfire.sh b/scripts/ios/build_all_campfire.sh index bcb03e991..aa9d52aee 100755 --- a/scripts/ios/build_all_campfire.sh +++ b/scripts/ios/build_all_campfire.sh @@ -16,6 +16,8 @@ rustup target add x86_64-apple-ios (cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/ios/ && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh ) diff --git a/scripts/ios/build_all_duo.sh b/scripts/ios/build_all_duo.sh index 89e6f4641..c6c11add7 100755 --- a/scripts/ios/build_all_duo.sh +++ b/scripts/ios/build_all_duo.sh @@ -18,6 +18,8 @@ rustup target add x86_64-apple-ios (cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/ios/ && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh ) diff --git a/scripts/linux/build_all.sh b/scripts/linux/build_all.sh index 423646185..4cc1ad8a0 100755 --- a/scripts/linux/build_all.sh +++ b/scripts/linux/build_all.sh @@ -14,6 +14,8 @@ mkdir -p build ./build_secure_storage_deps.sh (cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/linux && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/linux && ./build_all.sh ) diff --git a/scripts/linux/build_all_campfire.sh b/scripts/linux/build_all_campfire.sh index 423646185..4cc1ad8a0 100755 --- a/scripts/linux/build_all_campfire.sh +++ b/scripts/linux/build_all_campfire.sh @@ -14,6 +14,8 @@ mkdir -p build ./build_secure_storage_deps.sh (cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/linux && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/linux && ./build_all.sh ) diff --git a/scripts/linux/build_all_duo.sh b/scripts/linux/build_all_duo.sh index 78067b478..ace233fd8 100755 --- a/scripts/linux/build_all_duo.sh +++ b/scripts/linux/build_all_duo.sh @@ -16,6 +16,8 @@ mkdir -p build ./build_secure_storage_deps.sh & (cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/linux && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/linux && ./build_all.sh ) diff --git a/scripts/linux/build_secp256k1.sh b/scripts/linux/build_secp256k1.sh index f00b6b82b..cf7650ffe 100755 --- a/scripts/linux/build_secp256k1.sh +++ b/scripts/linux/build_secp256k1.sh @@ -10,5 +10,5 @@ mkdir -p build && cd build cmake .. cmake --build . mkdir -p ../../../../../build -cp lib/libsecp256k1.so.2.2.2 "../../../../../build/libsecp256k1.so" +cp lib/libsecp256k1.so.2.*.* "../../../../../build/libsecp256k1.so" cd ../../../ diff --git a/scripts/macos/build_all.sh b/scripts/macos/build_all.sh index af608846f..2e28e2605 100755 --- a/scripts/macos/build_all.sh +++ b/scripts/macos/build_all.sh @@ -8,6 +8,8 @@ set_rust_to_1671 (cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/macos && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh ) diff --git a/scripts/macos/build_all_campfire.sh b/scripts/macos/build_all_campfire.sh index af608846f..2e28e2605 100755 --- a/scripts/macos/build_all_campfire.sh +++ b/scripts/macos/build_all_campfire.sh @@ -8,6 +8,8 @@ set_rust_to_1671 (cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/macos && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh ) diff --git a/scripts/macos/build_all_duo.sh b/scripts/macos/build_all_duo.sh index 8a53e5801..ac3788cc8 100755 --- a/scripts/macos/build_all_duo.sh +++ b/scripts/macos/build_all_duo.sh @@ -10,6 +10,8 @@ set_rust_to_1671 (cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/macos && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh ) diff --git a/scripts/rust_version.sh b/scripts/rust_version.sh index 8cda1229b..a95729573 100755 --- a/scripts/rust_version.sh +++ b/scripts/rust_version.sh @@ -16,4 +16,13 @@ set_rust_to_1720() { echo "Rust version 1.72.0 is not installed. Please install it using 'rustup install 1.72.0'." >&2 exit 1 fi +} + +set_rust_to_1810() { + if rustup toolchain list | grep -q "1.72.0"; then + rustup default 1.81.0 + else + echo "Rust version 1.81.0 is not installed. Please install it using 'rustup install 1.81.0'." >&2 + exit 1 + fi } \ No newline at end of file diff --git a/scripts/windows/build_all.sh b/scripts/windows/build_all.sh index 191a46cc0..53c035a3d 100755 --- a/scripts/windows/build_all.sh +++ b/scripts/windows/build_all.sh @@ -9,6 +9,8 @@ set_rust_to_1671 mkdir -p build (cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) (cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/windows && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/windows && ./build_all.sh ) diff --git a/scripts/windows/build_all_campfire.sh b/scripts/windows/build_all_campfire.sh index 191a46cc0..53c035a3d 100755 --- a/scripts/windows/build_all_campfire.sh +++ b/scripts/windows/build_all_campfire.sh @@ -9,6 +9,8 @@ set_rust_to_1671 mkdir -p build (cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) (cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/windows && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/windows && ./build_all.sh ) diff --git a/scripts/windows/build_all_duo.sh b/scripts/windows/build_all_duo.sh index 3e27eff02..e5667b188 100755 --- a/scripts/windows/build_all_duo.sh +++ b/scripts/windows/build_all_duo.sh @@ -11,6 +11,8 @@ set_rust_to_1671 mkdir -p build (cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) (cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/windows && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/windows && ./build_all.sh ) diff --git a/scripts/windows/deps.sh b/scripts/windows/deps.sh index 4914d074f..513a8ce7f 100644 --- a/scripts/windows/deps.sh +++ b/scripts/windows/deps.sh @@ -1,8 +1,9 @@ #!/bin/bash -cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./deps.sh -cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./mxedeps.sh -# cd ../../crypto_plugins/flutter_libmonero/scripts/windows && ./monerodeps.sh && ./mxedeps.sh +(cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./deps.sh ) +(cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./mxedeps.sh ) +(cd ../../crypto_plugins/flutter_libmwc/scripts/windows && ./deps.sh) +# (cd ../../crypto_plugins/flutter_libmonero/scripts/windows && ./monerodeps.sh && ./mxedeps.sh) & wait echo "Done building" diff --git a/test/cached_electrumx_test.mocks.dart b/test/cached_electrumx_test.mocks.dart index 993f73f69..e37380e0b 100644 --- a/test/cached_electrumx_test.mocks.dart +++ b/test/cached_electrumx_test.mocks.dart @@ -1113,6 +1113,19 @@ class MockPrefs extends _i1.Mock implements _i8.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, diff --git a/test/pages/send_view/send_view_test.mocks.dart b/test/pages/send_view/send_view_test.mocks.dart index fee6a0d7f..c20fd4e29 100644 --- a/test/pages/send_view/send_view_test.mocks.dart +++ b/test/pages/send_view/send_view_test.mocks.dart @@ -1129,6 +1129,19 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, diff --git a/test/price_test.dart b/test/price_test.dart index dc7aaeb9e..e15a7b95d 100644 --- a/test/price_test.dart +++ b/test/price_test.dart @@ -1,3 +1,5 @@ +// TODO MWC + import 'dart:convert'; import 'dart:io'; diff --git a/test/sample_data/theme_json.dart b/test/sample_data/theme_json.dart index 6f7601b42..660e680e6 100644 --- a/test/sample_data/theme_json.dart +++ b/test/sample_data/theme_json.dart @@ -10,6 +10,7 @@ const Map lightThemeJsonMap = { "firo": "0xFFFF897A", "dogecoin": "0xFFFFE079", "epicCash": "0xFFC5C7CB", + "mimblewimblecoin": "0xFFC5C7CB", "ethereum": "0xFFA7ADE9", "monero": "0xFFFF9E6B", "namecoin": "0xFF91B1E1", @@ -186,6 +187,7 @@ const Map lightThemeJsonMap = { "bitcoincash": "dummy.svg", "dogecoin": "dummy.svg", "epicCash": "dummy.svg", + "mimblewimblecoin": "dummy.svg", "ethereum": "dummy.svg", "firo": "dummy.svg", "monero": "dummy.svg", @@ -197,6 +199,7 @@ const Map lightThemeJsonMap = { "bitcoincash_image": "dummy.svg", "dogecoin_image": "dummy.svg", "epicCash_image": "dummy.svg.svg", + "mimblewimblecoin_image": "dummy.svg", "ethereum_image": "dummy.svg", "firo_image": "dummy.svg", "monero_image": "dummy.svg", @@ -208,6 +211,7 @@ const Map lightThemeJsonMap = { "bitcoincash_image_secondary": "dummy.svg", "dogecoin_image_secondary": "dummy.svg", "epicCash_image_secondary": "dummy.svg.svg", + "mimblewimblecoin_image_secondary": "dummy.svg", "ethereum_image_secondary": "dummy.svg", "firo_image_secondary": "dummy.svg", "monero_image_secondary": "dummy.svg", diff --git a/test/sample_data/theme_json_v2.dart b/test/sample_data/theme_json_v2.dart index 3f9fbfb7f..1327789c0 100644 --- a/test/sample_data/theme_json_v2.dart +++ b/test/sample_data/theme_json_v2.dart @@ -13,6 +13,7 @@ const Map lightThemeJsonMap = { "dogecoin": "0xFFFFE079", "eCash": "0xFFC5C7CB", "epicCash": "0xFFC5C7CB", + "mimblewimblecoin": "0xFFC5C7CB", "ethereum": "0xFFA7ADE9", "monero": "0xFFFF9E6B", "namecoin": "0xFF91B1E1", @@ -194,6 +195,7 @@ const Map lightThemeJsonMap = { "dogecoin": "dummy.svg", "eCash": "dummy.svg", "epicCash": "dummy.svg", + "mimblewimblecoin": "dummy.svg", "ethereum": "dummy.svg", "firo": "dummy.svg", "monero": "dummy.svg", @@ -208,6 +210,7 @@ const Map lightThemeJsonMap = { "dogecoin": "dummy.svg", "eCash": "dummy.svg", "epicCash": "dummy.svg", + "mimblewimblecoin": "dummy.svg", "ethereum": "dummy.svg", "firo": "dummy.svg", "monero": "dummy.svg", @@ -222,6 +225,7 @@ const Map lightThemeJsonMap = { "dogecoin": "dummy.svg", "eCash": "dummy.svg", "epicCash": "dummy.svg", + "mimblewimblecoin": "dummy.svg", "ethereum": "dummy.svg", "firo": "dummy.svg", "monero": "dummy.svg", diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart index b7a8d49d5..3f04c3fc2 100644 --- a/test/screen_tests/exchange/exchange_view_test.mocks.dart +++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart @@ -558,6 +558,19 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, diff --git a/test/services/node_service_test.dart b/test/services/node_service_test.dart index 5b4f1e635..b44ebf323 100644 --- a/test/services/node_service_test.dart +++ b/test/services/node_service_test.dart @@ -1,3 +1,5 @@ +// TODO MWC + import 'package:flutter_test/flutter_test.dart'; import 'package:hive/hive.dart'; import 'package:hive_test/hive_test.dart'; @@ -152,6 +154,17 @@ void main() { isFailover: true, isDown: false, ); + final nodeD = NodeModel( + host: "host3", + port: 423, + name: "btcnode", + id: "pnodeID3", + useSSL: true, + enabled: true, + coinName: "mimblewimblecoin", + isFailover: true, + isDown: false, + ); setUp(() async { await NodeService(secureStorageInterface: FakeSecureStorage()) diff --git a/test/widget_tests/managed_favorite_test.mocks.dart b/test/widget_tests/managed_favorite_test.mocks.dart index c2bc00d2c..ee2de45f9 100644 --- a/test/widget_tests/managed_favorite_test.mocks.dart +++ b/test/widget_tests/managed_favorite_test.mocks.dart @@ -834,6 +834,19 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, diff --git a/test/widget_tests/node_options_sheet_test.mocks.dart b/test/widget_tests/node_options_sheet_test.mocks.dart index 5e878015f..26f7b6cf3 100644 --- a/test/widget_tests/node_options_sheet_test.mocks.dart +++ b/test/widget_tests/node_options_sheet_test.mocks.dart @@ -709,6 +709,19 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, diff --git a/test/widget_tests/transaction_card_test.mocks.dart b/test/widget_tests/transaction_card_test.mocks.dart index a5816bdcb..d9234ff9b 100644 --- a/test/widget_tests/transaction_card_test.mocks.dart +++ b/test/widget_tests/transaction_card_test.mocks.dart @@ -818,6 +818,19 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 74ef33c4e..bb0e28697 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("DesktopDropPlugin")); FlutterLibepiccashPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterLibepiccashPluginCApi")); + FlutterLibmwcPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterLibmwcPluginCApi")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); IsarFlutterLibsPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 2f370e685..0cd9af794 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST cs_monero_flutter_libs_windows desktop_drop flutter_libepiccash + flutter_libmwc flutter_secure_storage_windows isar_flutter_libs local_auth_windows