diff --git a/.gitignore b/.gitignore index a7619995e..37db583e5 100644 --- a/.gitignore +++ b/.gitignore @@ -140,6 +140,7 @@ lib/tron/tron.dart lib/wownero/wownero.dart lib/zano/zano.dart lib/decred/decred.dart +lib/tari/tari.dart ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png diff --git a/assets/tari_node_list.yml b/assets/tari_node_list.yml new file mode 100644 index 000000000..e69de29bb diff --git a/cw_tari/lib/tari_balance.dart b/cw_tari/lib/tari_balance.dart index 10879897b..70ec07ce0 100644 --- a/cw_tari/lib/tari_balance.dart +++ b/cw_tari/lib/tari_balance.dart @@ -1,5 +1,6 @@ import 'package:cw_core/balance.dart'; import 'package:cw_core/monero_amount_format.dart'; +import 'package:tari/tari.dart' as tari; class TariBalance extends Balance { TariBalance({required this.fullBalance, required this.unlockedBalance}) @@ -9,18 +10,13 @@ class TariBalance extends Balance { moneroAmountToString(amount: unlockedBalance), super(unlockedBalance, fullBalance); - factory TariBalance.fromFfi((int, int, int, int) result) { - final availableBalance = result.$1; - final pendingIncoming = result.$2; - final pendingOutgoing = result.$3; - final timeLockedBalance = result.$4; - + factory TariBalance.fromTariBalanceInfo(tari.TariBalanceInfo result) { return TariBalance( - fullBalance: availableBalance + - pendingIncoming + - pendingOutgoing + - timeLockedBalance, - unlockedBalance: availableBalance); + fullBalance: result.available + + result.pendingIncoming + + result.pendingOutgoing + + result.timeLocked, + unlockedBalance: result.available); } final int fullBalance; diff --git a/cw_tari/lib/tari_wallet.dart b/cw_tari/lib/tari_wallet.dart index 1fbabb2c3..ca834bcd8 100644 --- a/cw_tari/lib/tari_wallet.dart +++ b/cw_tari/lib/tari_wallet.dart @@ -10,36 +10,36 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_tari/tari_balance.dart'; +import 'package:cw_tari/tari_transaction_history.dart'; +import 'package:cw_tari/tari_transaction_info.dart'; import 'package:cw_tari/tari_wallet_addresses.dart'; import 'package:mobx/mobx.dart'; -import 'package:tari/tari.dart'; +import 'package:tari/tari.dart' as tari; part 'tari_wallet.g.dart'; -abstract class TariWallet = TariWalletBase with _$TariWallet; +class TariWallet = TariWalletBase with _$TariWallet; -abstract class TariWalletBase extends WalletBase< - TariBalance, - EVMChainTransactionHistory, - EVMChainTransactionInfo> with Store, WalletKeysFile { +abstract class TariWalletBase + extends WalletBase + with Store, WalletKeysFile { TariWalletBase({ required WalletInfo walletInfo, required String password, - required TariWalletFfi walletFfi, + required tari.TariWallet walletFfi, }) : syncStatus = const NotConnectedSyncStatus(), walletAddresses = TariWalletAddresses(walletInfo), _password = password, _walletFfi = walletFfi, balance = ObservableMap.of({ - CryptoCurrency.tari: TariBalance.fromFfi(walletFfi.getBalance()), + CryptoCurrency.tari: + TariBalance.fromTariBalanceInfo(walletFfi.getBalance()), }), super(walletInfo) { this.walletInfo = walletInfo; - transactionHistory = - setUpTransactionHistory(walletInfo, password); } - final TariWalletFfi _walletFfi; + final tari.TariWallet _walletFfi; final String _password; @override @@ -108,9 +108,8 @@ abstract class TariWalletBase extends WalletBase< throw UnimplementedError(); } - @override - Future> fetchTransactions() async { + Future> fetchTransactions() async { // ToDo throw UnimplementedError(); } @@ -144,7 +143,8 @@ abstract class TariWalletBase extends WalletBase< @override Future updateBalance() async { - balance[CryptoCurrency.tari] = TariBalance.fromFfi(_walletFfi.getBalance()); + balance[CryptoCurrency.tari] = + TariBalance.fromTariBalanceInfo(_walletFfi.getBalance()); } @override diff --git a/cw_tari/lib/tari_wallet_addresses.dart b/cw_tari/lib/tari_wallet_addresses.dart index 07bedbaa2..c83b4d510 100644 --- a/cw_tari/lib/tari_wallet_addresses.dart +++ b/cw_tari/lib/tari_wallet_addresses.dart @@ -3,7 +3,7 @@ import 'dart:developer'; import 'package:cw_core/wallet_addresses.dart'; import 'package:mobx/mobx.dart'; -part 'tari_addresses.g.dart'; +part 'tari_wallet_addresses.g.dart'; class TariWalletAddresses = TariWalletAddressesBase with _$TariWalletAddresses; diff --git a/cw_tari/lib/tari_wallet_service.dart b/cw_tari/lib/tari_wallet_service.dart new file mode 100644 index 000000000..2940bb09e --- /dev/null +++ b/cw_tari/lib/tari_wallet_service.dart @@ -0,0 +1,276 @@ +import 'dart:io'; + +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_tari/callback.dart'; +import 'package:cw_tari/tari_wallet.dart'; +import 'package:hive/hive.dart'; +import 'package:tari/tari.dart' as tari; + +class TariNewWalletCredentials extends WalletCredentials { + TariNewWalletCredentials({ + required super.name, + super.walletInfo, + super.password, + super.passphrase, + }); +} + +class TariRestoreWalletFromSeedCredentials extends WalletCredentials { + TariRestoreWalletFromSeedCredentials({required super.name, + required this.mnemonic, + super.passphrase, + super.height = 0, + super.password}); + + final String mnemonic; +} + +class MoneroWalletLoadingException implements Exception { + @override + String toString() => 'Failure to load the wallet.'; +} + +class TariRestoreWalletFromKeysCredentials extends WalletCredentials { + TariRestoreWalletFromKeysCredentials({required String name, + required String password, + required this.language, + required this.address, + required this.viewKey, + required this.spendKey, + int height = 0}) + : super(name: name, password: password, height: height); + + final String language; + final String address; + final String viewKey; + final String spendKey; +} + +class TariWalletService extends WalletService< + TariNewWalletCredentials, + TariRestoreWalletFromSeedCredentials, + TariRestoreWalletFromKeysCredentials, + TariNewWalletCredentials> { + TariWalletService(this.walletInfoSource); + + final Box walletInfoSource; + + static bool walletFilesExist(String path) => !File(path).existsSync(); + + @override + WalletType getType() => WalletType.tari; + + @override + Future create(TariNewWalletCredentials credentials, + {bool? isTestnet}) async { + try { + final path = await pathForWallet(name: credentials.name, type: getType()); + + final connection = tari.getTorConnection(); + final config = tari.getWalletConfig( + path: path, + transport: connection, + ); + final tariWallet = tari.createWallet( + commsConfig: config, + passphrase: credentials.password!, + logPath: "$path/logs/wallet.log", + callbackReceivedTransaction: CallbackPlaceholders.callbackReceivedTransaction, + callbackReceivedTransactionReply: CallbackPlaceholders.callbackReceivedTransactionReply, + callbackReceivedFinalizedTransaction: CallbackPlaceholders.callbackReceivedFinalizedTransaction, + callbackReceivedTransactionBroadcast: CallbackPlaceholders.callbackTransactionBroadcast, + callbackReceivedTransactionMined: CallbackPlaceholders.callbackTransactionMined, + callbackReceivedTransactionMinedUnconfirmed: CallbackPlaceholders.callbackTransactionMinedUnconfirmed, + callbackFauxTransactionMinedConfirmed: CallbackPlaceholders.callbackFauxTransactionConfirmed, + callbackFauxTransactionMinedUnconfirmed: CallbackPlaceholders.callbackFauxTransactionUnconfirmed, + callbackTransactionSendResult: CallbackPlaceholders.callbackTransactionSendResult, + callbackTransactionCancellation: CallbackPlaceholders.callbackTransactionCancellation, + callbackTxoValidationComplete: CallbackPlaceholders.callbackTxoValidationComplete, + callbackContactsLivenessDataUpdated: CallbackPlaceholders.callbackContactsLivenessDataUpdated, + callbackBalanceUpdated: CallbackPlaceholders.callbackBalanceUpdated, + callbackTransactionValidationComplete: CallbackPlaceholders.callbackTransactionValidationComplete, + callbackSafMessagesReceived: CallbackPlaceholders.callbackSafMessagesReceived, + callbackConnectivityStatus: CallbackPlaceholders.callbackConnectivityStatus, + callbackWalletScannedHeight: CallbackPlaceholders.callbackWalletScannedHeight, + callbackBaseNodeState: CallbackPlaceholders.callbackBaseNodeState, + ); + + final wallet = TariWallet( + walletInfo: credentials.walletInfo!, + password: credentials.password!, + walletFfi: tariWallet, + ); + await wallet.init(); + + return wallet; + } catch (e) { + // TODO: Implement Exception for wallet list service. + printV('MoneroWalletsManager Error: ${e.toString()}'); + rethrow; + } + } + + @override + Future isWalletExit(String name) async => + File(await pathForWallet(name: name, type: getType())).exists(); + + @override + Future openWallet(String name, String password) async { + try { + final path = await pathForWallet(name: name, type: getType()); + final connection = tari.getTorConnection(); + final config = tari.getWalletConfig( + path: path, + transport: connection, + ); + final tariWallet = tari.createWallet( + commsConfig: config, + passphrase: password, + logPath: "$path/logs/wallet.log", + callbackReceivedTransaction: CallbackPlaceholders.callbackReceivedTransaction, + callbackReceivedTransactionReply: CallbackPlaceholders.callbackReceivedTransactionReply, + callbackReceivedFinalizedTransaction: CallbackPlaceholders.callbackReceivedFinalizedTransaction, + callbackReceivedTransactionBroadcast: CallbackPlaceholders.callbackTransactionBroadcast, + callbackReceivedTransactionMined: CallbackPlaceholders.callbackTransactionMined, + callbackReceivedTransactionMinedUnconfirmed: CallbackPlaceholders.callbackTransactionMinedUnconfirmed, + callbackFauxTransactionMinedConfirmed: CallbackPlaceholders.callbackFauxTransactionConfirmed, + callbackFauxTransactionMinedUnconfirmed: CallbackPlaceholders.callbackFauxTransactionUnconfirmed, + callbackTransactionSendResult: CallbackPlaceholders.callbackTransactionSendResult, + callbackTransactionCancellation: CallbackPlaceholders.callbackTransactionCancellation, + callbackTxoValidationComplete: CallbackPlaceholders.callbackTxoValidationComplete, + callbackContactsLivenessDataUpdated: CallbackPlaceholders.callbackContactsLivenessDataUpdated, + callbackBalanceUpdated: CallbackPlaceholders.callbackBalanceUpdated, + callbackTransactionValidationComplete: CallbackPlaceholders.callbackTransactionValidationComplete, + callbackSafMessagesReceived: CallbackPlaceholders.callbackSafMessagesReceived, + callbackConnectivityStatus: CallbackPlaceholders.callbackConnectivityStatus, + callbackWalletScannedHeight: CallbackPlaceholders.callbackWalletScannedHeight, + callbackBaseNodeState: CallbackPlaceholders.callbackBaseNodeState, + ); + + final walletInfo = walletInfoSource.values + .firstWhere((info) => info.id == WalletBase.idFor(name, getType())); + final wallet = TariWallet( + walletInfo: walletInfo, + password: password, + walletFfi: tariWallet, + ); + + + await wallet.init(); + + return wallet; + } catch (e) { + printV(e); + rethrow; + } + } + + @override + Future remove(String wallet) async { + final path = await pathForWalletDir(name: wallet, type: getType()); + + final file = Directory(path); + final isExist = file.existsSync(); + + if (isExist) { + await file.delete(recursive: true); + } + + final walletInfo = walletInfoSource.values + .firstWhere((info) => info.id == WalletBase.idFor(wallet, getType())); + await walletInfoSource.delete(walletInfo.key); + } + + @override + Future rename(String currentName, String password, + String newName) async { + final currentWalletInfo = walletInfoSource.values.firstWhere( + (info) => info.id == WalletBase.idFor(currentName, getType())); + + throw UnimplementedError(); + + // await currentWallet.renameWalletFiles(newName); + // + // final newWalletInfo = currentWalletInfo; + // newWalletInfo.id = WalletBase.idFor(newName, getType()); + // newWalletInfo.name = newName; + // + // await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + } + + @override + Future restoreFromKeys( + TariRestoreWalletFromKeysCredentials credentials, + {bool? isTestnet}) async { + throw UnimplementedError(); + } + + @override + Future restoreFromHardwareWallet( + TariNewWalletCredentials credentials) async { + throw UnimplementedError(); + } + + @override + Future restoreFromSeed( + TariRestoreWalletFromSeedCredentials credentials, + {bool? isTestnet}) async { + try { + final path = await pathForWallet(name: credentials.name, type: getType()); + + final connection = tari.getTorConnection(); + final config = tari.getWalletConfig( + path: path, + transport: connection, + ); + final tariWallet = tari.createWallet( + commsConfig: config, + passphrase: credentials.password!, + mnemonic: credentials.mnemonic, + seedPassphrase: credentials.passphrase ?? "", + logPath: "$path/logs/wallet.log", + callbackReceivedTransaction: CallbackPlaceholders.callbackReceivedTransaction, + callbackReceivedTransactionReply: CallbackPlaceholders.callbackReceivedTransactionReply, + callbackReceivedFinalizedTransaction: CallbackPlaceholders.callbackReceivedFinalizedTransaction, + callbackReceivedTransactionBroadcast: CallbackPlaceholders.callbackTransactionBroadcast, + callbackReceivedTransactionMined: CallbackPlaceholders.callbackTransactionMined, + callbackReceivedTransactionMinedUnconfirmed: CallbackPlaceholders.callbackTransactionMinedUnconfirmed, + callbackFauxTransactionMinedConfirmed: CallbackPlaceholders.callbackFauxTransactionConfirmed, + callbackFauxTransactionMinedUnconfirmed: CallbackPlaceholders.callbackFauxTransactionUnconfirmed, + callbackTransactionSendResult: CallbackPlaceholders.callbackTransactionSendResult, + callbackTransactionCancellation: CallbackPlaceholders.callbackTransactionCancellation, + callbackTxoValidationComplete: CallbackPlaceholders.callbackTxoValidationComplete, + callbackContactsLivenessDataUpdated: CallbackPlaceholders.callbackContactsLivenessDataUpdated, + callbackBalanceUpdated: CallbackPlaceholders.callbackBalanceUpdated, + callbackTransactionValidationComplete: CallbackPlaceholders.callbackTransactionValidationComplete, + callbackSafMessagesReceived: CallbackPlaceholders.callbackSafMessagesReceived, + callbackConnectivityStatus: CallbackPlaceholders.callbackConnectivityStatus, + callbackWalletScannedHeight: CallbackPlaceholders.callbackWalletScannedHeight, + callbackBaseNodeState: CallbackPlaceholders.callbackBaseNodeState, + ); + final wallet = TariWallet( + walletInfo: credentials.walletInfo!, + walletFfi: tariWallet, + password: credentials.password!); + await wallet.init(); + + return wallet; + } catch (e) { + // TODO: Implement Exception for wallet list service. + printV('MoneroWalletsManager Error: $e'); + rethrow; + } + } + + @override + bool requireHardwareWalletConnection(String name) { + return false; + } +} diff --git a/cw_tari/pubspec.yaml b/cw_tari/pubspec.yaml index 6b87b2250..8eeeb3fdb 100644 --- a/cw_tari/pubspec.yaml +++ b/cw_tari/pubspec.yaml @@ -21,5 +21,7 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^5.0.0 + build_runner: ^2.4.7 + mobx_codegen: ^2.0.7 flutter: diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index 344b5391d..3c6b7b811 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/solana/solana.dart'; +import 'package:cake_wallet/tari/tari.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/zano/zano.dart'; @@ -50,6 +51,8 @@ class SeedValidator extends Validator { return zano!.getWordList(language); case WalletType.decred: return decred!.getDecredWordList(); + case WalletType.tari: + return tari!.getTariWordList(language); case WalletType.none: case WalletType.haven: return []; diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart index b44e56a98..b66fc139b 100644 --- a/lib/core/wallet_creation_service.dart +++ b/lib/core/wallet_creation_service.dart @@ -91,6 +91,7 @@ class WalletCreationService { case WalletType.banano: case WalletType.zano: case WalletType.decred: + case WalletType.tari: return false; } } diff --git a/lib/di.dart b/lib/di.dart index b20b4062f..9a28f94a8 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -36,6 +36,7 @@ import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart'; import 'package:cake_wallet/src/screens/settings/background_sync_page.dart'; +import 'package:cake_wallet/tari/tari.dart'; import 'package:cake_wallet/view_model/dev/monero_background_sync.dart'; import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/tron/tron.dart'; @@ -1115,6 +1116,8 @@ Future setup({ return zano!.createZanoWalletService(_walletInfoSource); case WalletType.decred: return decred!.createDecredWalletService(_walletInfoSource, _unspentCoinsInfoSource); + case WalletType.tari: + return tari!.createTariWalletService(_walletInfoSource); case WalletType.none: case WalletType.haven: throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService'); diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 45234d5ec..80d9e83a1 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -46,6 +46,7 @@ const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568'; const zanoDefaultNodeUri = 'zano.cakewallet.com:11211'; const moneroWorldNodeUri = '.moneroworld.com'; const decredDefaultUri = "default-spv-nodes"; +const tariDefaultUri = ''; // ToDo Future defaultSettingsMigration( {required int version, @@ -617,6 +618,8 @@ String _getDefaultNodeUri(WalletType type) { return zanoDefaultNodeUri; case WalletType.decred: return decredDefaultUri; + case WalletType.tari: + return tariDefaultUri; case WalletType.banano: case WalletType.none: return ''; diff --git a/lib/entities/node_list.dart b/lib/entities/node_list.dart index bb489e715..c5139536d 100644 --- a/lib/entities/node_list.dart +++ b/lib/entities/node_list.dart @@ -46,6 +46,9 @@ Future> loadDefaultNodes(WalletType type) async { case WalletType.decred: path = 'assets/decred_node_list.yml'; break; + case WalletType.tari: + path = 'assets/tari_node_list.yml'; + break; case WalletType.banano: case WalletType.none: path = ''; diff --git a/lib/entities/priority_for_wallet_type.dart b/lib/entities/priority_for_wallet_type.dart index 5307250d5..1baaab7fc 100644 --- a/lib/entities/priority_for_wallet_type.dart +++ b/lib/entities/priority_for_wallet_type.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/polygon/polygon.dart'; +import 'package:cake_wallet/tari/tari.dart'; import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/zano/zano.dart'; import 'package:cake_wallet/decred/decred.dart'; @@ -35,6 +36,8 @@ List priorityForWalletType(WalletType type) { return zano!.getTransactionPriorities(); case WalletType.decred: return decred!.getTransactionPriorities(); + case WalletType.tari: + return tari!.getTransactionPriorities(); case WalletType.none: case WalletType.haven: return []; diff --git a/lib/reactions/bip39_wallet_utils.dart b/lib/reactions/bip39_wallet_utils.dart index 0c58bc76f..77e990012 100644 --- a/lib/reactions/bip39_wallet_utils.dart +++ b/lib/reactions/bip39_wallet_utils.dart @@ -17,6 +17,7 @@ bool isBIP39Wallet(WalletType walletType) { case WalletType.haven: case WalletType.zano: case WalletType.decred: + case WalletType.tari: case WalletType.none: return false; } diff --git a/lib/tari/cw_tari.dart b/lib/tari/cw_tari.dart new file mode 100644 index 000000000..e75ece680 --- /dev/null +++ b/lib/tari/cw_tari.dart @@ -0,0 +1,43 @@ +part of 'tari.dart'; + +class CWTari extends Tari { + List getTariWordList(String language) { + return []; // ToDo + } + + WalletService createTariWalletService(Box walletInfoSource) { + return TariWalletService(walletInfoSource); + } + + WalletCredentials createTariNewWalletCredentials( + {required String name, + WalletInfo? walletInfo, + String? password, + String? passphrase}) => + TariNewWalletCredentials( + name: name, + walletInfo: walletInfo, + password: password, + passphrase: passphrase); + + WalletCredentials createTariRestoreWalletFromSeedCredentials( + {required String name, + required String mnemonic, + required String password, + String? passphrase}) => + TariRestoreWalletFromSeedCredentials( + name: name, + mnemonic: mnemonic, + password: password, + passphrase: passphrase); + + String getAddress(WalletBase wallet) => + (wallet as TariWallet).walletAddresses.address; + + List getTransactionPriorities() { + return []; // ToDo + } + + double formatterTariAmountToDouble({required int amount}) => + cryptoAmountToDouble(amount: amount, divider: 1000000); +} diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart index 803744590..5c99354f8 100644 --- a/lib/view_model/advanced_privacy_settings_view_model.dart +++ b/lib/view_model/advanced_privacy_settings_view_model.dart @@ -56,6 +56,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { case WalletType.haven: case WalletType.zano: case WalletType.decred: + case WalletType.tari: return false; } } diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index a07ba26ef..f0248e520 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -697,6 +697,7 @@ abstract class DashboardViewModelBase with Store { case WalletType.tron: case WalletType.wownero: case WalletType.decred: + case WalletType.tari: return true; case WalletType.zano: case WalletType.haven: diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart index 4a85ec89a..bd56e57a8 100644 --- a/lib/view_model/dashboard/home_settings_view_model.dart +++ b/lib/view_model/dashboard/home_settings_view_model.dart @@ -215,6 +215,7 @@ abstract class HomeSettingsViewModelBase with Store { case WalletType.wownero: case WalletType.bitcoinCash: case WalletType.decred: + case WalletType.tari: return false; } diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index 808ccabea..261d94fab 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/solana/solana.dart'; +import 'package:cake_wallet/tari/tari.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/zano/zano.dart'; @@ -224,6 +225,11 @@ class TransactionListItem extends ActionListItem with Keyable { cryptoAmount: decred!.formatterDecredAmountToDouble(amount: transaction.amount), price: price); break; + case WalletType.tari: + amount = calculateFiatAmountRaw( + cryptoAmount: tari!.formatterTariAmountToDouble(amount: transaction.amount), + price: price); + break; case WalletType.none: case WalletType.banano: case WalletType.haven: diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 8b7349e9a..e8f8afc5d 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -783,6 +783,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with depositCurrency = CryptoCurrency.dcr; receiveCurrency = CryptoCurrency.xmr; break; + case WalletType.tari: + depositCurrency = CryptoCurrency.tari; + receiveCurrency = CryptoCurrency.xmr; + break; case WalletType.none: break; } diff --git a/lib/view_model/node_list/node_create_or_edit_view_model.dart b/lib/view_model/node_list/node_create_or_edit_view_model.dart index 7e4e73915..2e6e33454 100644 --- a/lib/view_model/node_list/node_create_or_edit_view_model.dart +++ b/lib/view_model/node_list/node_create_or_edit_view_model.dart @@ -87,6 +87,7 @@ abstract class NodeCreateOrEditViewModelBase with Store { case WalletType.bitcoin: case WalletType.zano: case WalletType.decred: + case WalletType.tari: return false; } } diff --git a/lib/view_model/send/fees_view_model.dart b/lib/view_model/send/fees_view_model.dart index f6dd0f201..e9ddca137 100644 --- a/lib/view_model/send/fees_view_model.dart +++ b/lib/view_model/send/fees_view_model.dart @@ -96,6 +96,7 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor case WalletType.banano: case WalletType.solana: case WalletType.tron: + case WalletType.tari: return false; } } diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index 23efe8c1a..828a6212d 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -122,6 +122,7 @@ abstract class OutputBase with Store { case WalletType.banano: case WalletType.solana: case WalletType.tron: + case WalletType.tari: break; } @@ -302,6 +303,7 @@ abstract class OutputBase with Store { case WalletType.zano: case WalletType.nano: case WalletType.decred: + case WalletType.tari: maximumFractionDigits = 12; break; case WalletType.bitcoin: diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 067ca73f9..51642a765 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -88,6 +88,9 @@ abstract class TransactionDetailsViewModelBase with Store { case WalletType.decred: _addDecredListItems(tx, dateFormat); break; + case WalletType.tari: + _addTariListItems(tx, dateFormat); + break; case WalletType.none: case WalletType.banano: break; @@ -193,6 +196,7 @@ abstract class TransactionDetailsViewModelBase with Store { return 'https://explorer.zano.org/transaction/${txId}'; case WalletType.decred: return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}'; + case WalletType.tari: // ToDo (Konsti) case WalletType.none: return ''; } @@ -227,6 +231,7 @@ abstract class TransactionDetailsViewModelBase with Store { return S.current.view_transaction_on + 'explorer.zano.org'; case WalletType.decred: return S.current.view_transaction_on + 'dcrdata.decred.org'; + case WalletType.tari: // ToDo (Konsti) case WalletType.none: return ''; } @@ -854,4 +859,22 @@ abstract class TransactionDetailsViewModelBase with Store { StandartListItem(title: S.current.transaction_details_title, value: comment), ]); } + + void _addTariListItems(TransactionInfo tx, DateFormat dateFormat) { + // ToDo (Konsti) + // final comment = tx.additionalInfo['comment'] as String?; + // items.addAll([ + // StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id), + // StandartListItem( + // title: 'Asset ID', value: tx.additionalInfo['assetId'] as String? ?? "Unknown asset id"), + // StandartListItem( + // title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), + // StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'), + // StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), + // if (tx.feeFormatted()?.isNotEmpty ?? false) + // StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!), + // if (comment != null && comment.isNotEmpty) + // StandartListItem(title: S.current.transaction_details_title, value: comment), + // ]); + } } diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 5b9be9109..25df5c022 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -237,6 +237,36 @@ class DecredURI extends PaymentURI { } } +class TariURI extends PaymentURI { + TariURI( + {required this.network, required super.amount, required super.address}); + + final String network; + + // tari://nextnet/profile?alias=Aurora%20User%20%F0%9F%8F%81%F0%9F%9A%B2%F0%9F%8D%8C&tariAddress=349jdYen2RMBGeWm1SyUBzEsfqSJ9NGGiZXAsWUK3TQQbdeQ3wJJnjRwi1EzUqxdAhNQ4YcRmFFDLvtXxx8kBgBboXb + // tari://nextnet/transactions/send?tariAddress=349jdYen2RMBGeWm1SyUBzEsfqSJ9NGGiZXAsWUK3TQQbdeQ3wJJnjRwi1EzUqxdAhNQ4YcRmFFDLvtXxx8kBgBboXb&amount=5000000 + + @override + String toString() { + final queryParams = {}; + final pathParts = [network]; + + if (amount.isNotEmpty) { + queryParams["amount"] = amount; + pathParts.addAll(["transactions","send"]); + } else { + queryParams["alias"] = "CakeWallet User"; + pathParts.add("profile"); + } + + queryParams["tariAddress"] = address; + + final uri = Uri(scheme: "tari", pathSegments: pathParts, queryParameters: queryParams); + + return uri.toString(); + } +} + abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store { WalletAddressListViewModelBase({ required AppStore appStore, @@ -331,6 +361,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo return ZanoURI(amount: amount, address: address.address); case WalletType.decred: return DecredURI(amount: amount, address: address.address); + case WalletType.tari: + return TariURI(amount: amount, address: address.address, network: 'nextnet'); // ToDo: Fix for mainnet case WalletType.none: throw Exception('Unexpected type: ${type.toString()}'); } diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 246227a80..a3e0e41c3 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -163,6 +163,7 @@ abstract class WalletKeysViewModelBase with Store { case WalletType.bitcoinCash: case WalletType.none: case WalletType.haven: + case WalletType.tari: // ToDo (Konsti) // final keys = bitcoin!.getWalletKeys(_appStore.wallet!); // // items.addAll([ diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index f4117f8ab..481592732 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/core/new_wallet_arguments.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/tari/tari.dart'; import 'package:cake_wallet/zano/zano.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/solana/solana.dart'; @@ -70,6 +71,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { return seedSettingsViewModel.nanoSeedType == NanoSeedType.bip39 ? advancedPrivacySettingsViewModel.seedPhraseLength.value : 24; + case WalletType.tari: case WalletType.none: return 24; case WalletType.haven: @@ -164,6 +166,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { ); case WalletType.decred: return decred!.createDecredNewWalletCredentials(name: name); + case WalletType.tari: + return tari!.createTariNewWalletCredentials(name: name); case WalletType.none: case WalletType.haven: throw Exception('Unexpected type: ${type.toString()}'); diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index dec0be6b1..8bae3d3f2 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -9,6 +9,7 @@ import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/store/app_store.dart'; +import 'package:cake_wallet/tari/tari.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/decred/decred.dart'; import 'package:cake_wallet/view_model/restore/restore_mode.dart'; @@ -65,6 +66,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { case WalletType.litecoin: case WalletType.bitcoinCash: case WalletType.zano: + case WalletType.tari: case WalletType.none: availableModes = [WalletRestoreMode.seed]; break; @@ -182,6 +184,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { mnemonic: seed, password: password, ); + case WalletType.tari: + return tari!.createTariRestoreWalletFromSeedCredentials( + name: name, + mnemonic: seed, + password: password, + ); case WalletType.none: case WalletType.haven: break; diff --git a/test/view_model/wallet_address_list/wallet_address_list_view_model_test.dart b/test/view_model/wallet_address_list/wallet_address_list_view_model_test.dart new file mode 100644 index 000000000..a61376702 --- /dev/null +++ b/test/view_model/wallet_address_list/wallet_address_list_view_model_test.dart @@ -0,0 +1,15 @@ +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group("PaymentURI", () { + + group("TariURI", () { + test("uri with amont", () { + final uri = TariURI(network: "nextnet", amount: "5000000", address: "349jdYen2RMBGeWm1SyUBzEsfqSJ9NGGiZXAsWUK3TQQbdeQ3wJJnjRwi1EzUqxdAhNQ4YcRmFFDLvtXxx8kBgBboXb"); + + expect(uri.toString(), "tari://nextnet/transactions/send?tariAddress=349jdYen2RMBGeWm1SyUBzEsfqSJ9NGGiZXAsWUK3TQQbdeQ3wJJnjRwi1EzUqxdAhNQ4YcRmFFDLvtXxx8kBgBboXb&amount=5000000"); + }); + }); + }); +} diff --git a/tool/configure.dart b/tool/configure.dart index 63ed2de84..4d01df4ab 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -30,6 +30,7 @@ Future main(List args) async { final hasWownero = args.contains('${prefix}wownero'); final hasZano = args.contains('${prefix}zano'); final hasDecred = args.contains('${prefix}decred'); + final hasTari = args.contains('${prefix}tari'); final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage'); await generateBitcoin(hasBitcoin); @@ -44,6 +45,7 @@ Future main(List args) async { await generateZano(hasZano); // await generateBanano(hasEthereum); await generateDecred(hasDecred); + await generateTari(hasTari); await generatePubspec( hasMonero: hasMonero, @@ -59,6 +61,7 @@ Future main(List args) async { hasWownero: hasWownero, hasZano: hasZano, hasDecred: hasDecred, + hasTari: hasTari, ); await generateWalletTypes( hasMonero: hasMonero, @@ -73,6 +76,7 @@ Future main(List args) async { hasWownero: hasWownero, hasZano: hasZano, hasDecred: hasDecred, + hasTari: hasTari, ); await injectSecureStorage(!excludeFlutterSecureStorage); } @@ -1377,6 +1381,54 @@ abstract class Decred { await outputFile.writeAsString(output); } +Future generateTari(bool hasImplementation) async { + final outputFile = File(decredOutputPath); + const tariCommonHeaders = """ +import 'package:cw_core/crypto_amount_format.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:hive/hive.dart'; +"""; + const tariCWHeaders = """ +import 'package:cw_tari/tari_wallet.dart'; +import 'package:cw_tari/tari_wallet_service.dart'; +"""; + const tariCwPart = "part 'cw_tari.dart';"; + const tariContent = """ + +abstract class Tari { + List getTariWordList(String language); + WalletService createTariWalletService(Box walletInfoSource); + WalletCredentials createTariNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? passphrase}); + WalletCredentials createTariRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password, String? passphrase}); + + String getAddress(WalletBase wallet); + + List getTransactionPriorities(); + double formatterTariAmountToDouble({required int amount}); +} +"""; + + const tariEmptyDefinition = 'Tari? tari;\n'; + const tariCWDefinition = 'Tari? tari = CWTari();\n'; + + final output = '$tariCommonHeaders\n' + + (hasImplementation ? '$tariCWHeaders\n' : '\n') + + (hasImplementation ? '$tariCwPart\n\n' : '\n') + + (hasImplementation ? tariCWDefinition : tariEmptyDefinition) + + '\n' + + tariContent; + + if (outputFile.existsSync()) { + await outputFile.delete(); + } + + await outputFile.writeAsString(output); +} + Future generatePubspec({ required bool hasMonero, required bool hasBitcoin, @@ -1391,6 +1443,7 @@ Future generatePubspec({ required bool hasWownero, required bool hasZano, required bool hasDecred, + required bool hasTari, }) async { const cwCore = """ cw_core: @@ -1455,6 +1508,10 @@ Future generatePubspec({ cw_decred: path: ./cw_decred """; + const cwTari = """ + cw_tari: + path: ./cw_tari + """; final inputFile = File(pubspecOutputPath); final inputText = await inputFile.readAsString(); final inputLines = inputText.split('\n'); @@ -1520,6 +1577,10 @@ Future generatePubspec({ output += '\n$cwZano'; } + if (hasTari) { + output += '\n$cwTari'; + } + final outputLines = output.split('\n'); inputLines.insertAll(dependenciesIndex + 1, outputLines); final outputContent = inputLines.join('\n'); @@ -1545,6 +1606,7 @@ Future generateWalletTypes({ required bool hasWownero, required bool hasZano, required bool hasDecred, + required bool hasTari, }) async { final walletTypesFile = File(walletTypesPath); @@ -1608,6 +1670,10 @@ Future generateWalletTypes({ outputContent += '\tWalletType.wownero,\n'; } + if (hasWownero) { + outputContent += '\tWalletType.tari,\n'; + } + outputContent += '];\n'; await walletTypesFile.writeAsString(outputContent); }