feat: Add initial Tari wallet support

This commit introduces support for the Tari wallet, including its transaction priorities, node list paths, wallet services, and related configurations. It also integrates Tari functionality across wallet creation, address handling, fees, seed validation, and exchange views.
This commit is contained in:
Konstantin Ullrich 2025-04-17 16:54:27 +02:00
parent baea28abaf
commit 641bf6a6fa
No known key found for this signature in database
GPG key ID: 6B3199AD9B3D23B8
30 changed files with 527 additions and 26 deletions

1
.gitignore vendored
View file

@ -140,6 +140,7 @@ lib/tron/tron.dart
lib/wownero/wownero.dart lib/wownero/wownero.dart
lib/zano/zano.dart lib/zano/zano.dart
lib/decred/decred.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_180.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png

View file

View file

@ -1,5 +1,6 @@
import 'package:cw_core/balance.dart'; import 'package:cw_core/balance.dart';
import 'package:cw_core/monero_amount_format.dart'; import 'package:cw_core/monero_amount_format.dart';
import 'package:tari/tari.dart' as tari;
class TariBalance extends Balance { class TariBalance extends Balance {
TariBalance({required this.fullBalance, required this.unlockedBalance}) TariBalance({required this.fullBalance, required this.unlockedBalance})
@ -9,18 +10,13 @@ class TariBalance extends Balance {
moneroAmountToString(amount: unlockedBalance), moneroAmountToString(amount: unlockedBalance),
super(unlockedBalance, fullBalance); super(unlockedBalance, fullBalance);
factory TariBalance.fromFfi((int, int, int, int) result) { factory TariBalance.fromTariBalanceInfo(tari.TariBalanceInfo result) {
final availableBalance = result.$1;
final pendingIncoming = result.$2;
final pendingOutgoing = result.$3;
final timeLockedBalance = result.$4;
return TariBalance( return TariBalance(
fullBalance: availableBalance + fullBalance: result.available +
pendingIncoming + result.pendingIncoming +
pendingOutgoing + result.pendingOutgoing +
timeLockedBalance, result.timeLocked,
unlockedBalance: availableBalance); unlockedBalance: result.available);
} }
final int fullBalance; final int fullBalance;

View file

@ -10,36 +10,36 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_core/wallet_keys_file.dart';
import 'package:cw_tari/tari_balance.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:cw_tari/tari_wallet_addresses.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:tari/tari.dart'; import 'package:tari/tari.dart' as tari;
part 'tari_wallet.g.dart'; part 'tari_wallet.g.dart';
abstract class TariWallet = TariWalletBase with _$TariWallet; class TariWallet = TariWalletBase with _$TariWallet;
abstract class TariWalletBase extends WalletBase< abstract class TariWalletBase
TariBalance, extends WalletBase<TariBalance, TariTransactionHistory, TariTransactionInfo>
EVMChainTransactionHistory, with Store, WalletKeysFile {
EVMChainTransactionInfo> with Store, WalletKeysFile {
TariWalletBase({ TariWalletBase({
required WalletInfo walletInfo, required WalletInfo walletInfo,
required String password, required String password,
required TariWalletFfi walletFfi, required tari.TariWallet walletFfi,
}) : syncStatus = const NotConnectedSyncStatus(), }) : syncStatus = const NotConnectedSyncStatus(),
walletAddresses = TariWalletAddresses(walletInfo), walletAddresses = TariWalletAddresses(walletInfo),
_password = password, _password = password,
_walletFfi = walletFfi, _walletFfi = walletFfi,
balance = ObservableMap<CryptoCurrency, TariBalance>.of({ balance = ObservableMap<CryptoCurrency, TariBalance>.of({
CryptoCurrency.tari: TariBalance.fromFfi(walletFfi.getBalance()), CryptoCurrency.tari:
TariBalance.fromTariBalanceInfo(walletFfi.getBalance()),
}), }),
super(walletInfo) { super(walletInfo) {
this.walletInfo = walletInfo; this.walletInfo = walletInfo;
transactionHistory =
setUpTransactionHistory(walletInfo, password);
} }
final TariWalletFfi _walletFfi; final tari.TariWallet _walletFfi;
final String _password; final String _password;
@override @override
@ -108,9 +108,8 @@ abstract class TariWalletBase extends WalletBase<
throw UnimplementedError(); throw UnimplementedError();
} }
@override @override
Future<Map<String, EVMChainTransactionInfo>> fetchTransactions() async { Future<Map<String, TariTransactionInfo>> fetchTransactions() async {
// ToDo // ToDo
throw UnimplementedError(); throw UnimplementedError();
} }
@ -144,7 +143,8 @@ abstract class TariWalletBase extends WalletBase<
@override @override
Future<void> updateBalance() async { Future<void> updateBalance() async {
balance[CryptoCurrency.tari] = TariBalance.fromFfi(_walletFfi.getBalance()); balance[CryptoCurrency.tari] =
TariBalance.fromTariBalanceInfo(_walletFfi.getBalance());
} }
@override @override

View file

@ -3,7 +3,7 @@ import 'dart:developer';
import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_addresses.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
part 'tari_addresses.g.dart'; part 'tari_wallet_addresses.g.dart';
class TariWalletAddresses = TariWalletAddressesBase with _$TariWalletAddresses; class TariWalletAddresses = TariWalletAddressesBase with _$TariWalletAddresses;

View file

@ -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<WalletInfo> walletInfoSource;
static bool walletFilesExist(String path) => !File(path).existsSync();
@override
WalletType getType() => WalletType.tari;
@override
Future<TariWallet> 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<bool> isWalletExit(String name) async =>
File(await pathForWallet(name: name, type: getType())).exists();
@override
Future<TariWallet> 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<void> 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<void> 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<TariWallet> restoreFromKeys(
TariRestoreWalletFromKeysCredentials credentials,
{bool? isTestnet}) async {
throw UnimplementedError();
}
@override
Future<TariWallet> restoreFromHardwareWallet(
TariNewWalletCredentials credentials) async {
throw UnimplementedError();
}
@override
Future<TariWallet> 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;
}
}

View file

@ -21,5 +21,7 @@ dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^5.0.0 flutter_lints: ^5.0.0
build_runner: ^2.4.7
mobx_codegen: ^2.0.7
flutter: flutter:

View file

@ -6,6 +6,7 @@ import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/solana/solana.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/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart'; import 'package:cake_wallet/zano/zano.dart';
@ -50,6 +51,8 @@ class SeedValidator extends Validator<MnemonicItem> {
return zano!.getWordList(language); return zano!.getWordList(language);
case WalletType.decred: case WalletType.decred:
return decred!.getDecredWordList(); return decred!.getDecredWordList();
case WalletType.tari:
return tari!.getTariWordList(language);
case WalletType.none: case WalletType.none:
case WalletType.haven: case WalletType.haven:
return []; return [];

View file

@ -91,6 +91,7 @@ class WalletCreationService {
case WalletType.banano: case WalletType.banano:
case WalletType.zano: case WalletType.zano:
case WalletType.decred: case WalletType.decred:
case WalletType.tari:
return false; return false;
} }
} }

View file

@ -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/exchange/provider/trocador_exchange_provider.dart';
import 'package:cake_wallet/src/screens/dev/monero_background_sync.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/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/dev/monero_background_sync.dart';
import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/tron/tron.dart';
@ -1115,6 +1116,8 @@ Future<void> setup({
return zano!.createZanoWalletService(_walletInfoSource); return zano!.createZanoWalletService(_walletInfoSource);
case WalletType.decred: case WalletType.decred:
return decred!.createDecredWalletService(_walletInfoSource, _unspentCoinsInfoSource); return decred!.createDecredWalletService(_walletInfoSource, _unspentCoinsInfoSource);
case WalletType.tari:
return tari!.createTariWalletService(_walletInfoSource);
case WalletType.none: case WalletType.none:
case WalletType.haven: case WalletType.haven:
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService'); throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');

View file

@ -46,6 +46,7 @@ const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568';
const zanoDefaultNodeUri = 'zano.cakewallet.com:11211'; const zanoDefaultNodeUri = 'zano.cakewallet.com:11211';
const moneroWorldNodeUri = '.moneroworld.com'; const moneroWorldNodeUri = '.moneroworld.com';
const decredDefaultUri = "default-spv-nodes"; const decredDefaultUri = "default-spv-nodes";
const tariDefaultUri = ''; // ToDo
Future<void> defaultSettingsMigration( Future<void> defaultSettingsMigration(
{required int version, {required int version,
@ -617,6 +618,8 @@ String _getDefaultNodeUri(WalletType type) {
return zanoDefaultNodeUri; return zanoDefaultNodeUri;
case WalletType.decred: case WalletType.decred:
return decredDefaultUri; return decredDefaultUri;
case WalletType.tari:
return tariDefaultUri;
case WalletType.banano: case WalletType.banano:
case WalletType.none: case WalletType.none:
return ''; return '';

View file

@ -46,6 +46,9 @@ Future<List<Node>> loadDefaultNodes(WalletType type) async {
case WalletType.decred: case WalletType.decred:
path = 'assets/decred_node_list.yml'; path = 'assets/decred_node_list.yml';
break; break;
case WalletType.tari:
path = 'assets/tari_node_list.yml';
break;
case WalletType.banano: case WalletType.banano:
case WalletType.none: case WalletType.none:
path = ''; path = '';

View file

@ -3,6 +3,7 @@ import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.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/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart'; import 'package:cake_wallet/zano/zano.dart';
import 'package:cake_wallet/decred/decred.dart'; import 'package:cake_wallet/decred/decred.dart';
@ -35,6 +36,8 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
return zano!.getTransactionPriorities(); return zano!.getTransactionPriorities();
case WalletType.decred: case WalletType.decred:
return decred!.getTransactionPriorities(); return decred!.getTransactionPriorities();
case WalletType.tari:
return tari!.getTransactionPriorities();
case WalletType.none: case WalletType.none:
case WalletType.haven: case WalletType.haven:
return []; return [];

View file

@ -17,6 +17,7 @@ bool isBIP39Wallet(WalletType walletType) {
case WalletType.haven: case WalletType.haven:
case WalletType.zano: case WalletType.zano:
case WalletType.decred: case WalletType.decred:
case WalletType.tari:
case WalletType.none: case WalletType.none:
return false; return false;
} }

43
lib/tari/cw_tari.dart Normal file
View file

@ -0,0 +1,43 @@
part of 'tari.dart';
class CWTari extends Tari {
List<String> getTariWordList(String language) {
return []; // ToDo
}
WalletService createTariWalletService(Box<WalletInfo> 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<TransactionPriority> getTransactionPriorities() {
return []; // ToDo
}
double formatterTariAmountToDouble({required int amount}) =>
cryptoAmountToDouble(amount: amount, divider: 1000000);
}

View file

@ -56,6 +56,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
case WalletType.haven: case WalletType.haven:
case WalletType.zano: case WalletType.zano:
case WalletType.decred: case WalletType.decred:
case WalletType.tari:
return false; return false;
} }
} }

View file

@ -697,6 +697,7 @@ abstract class DashboardViewModelBase with Store {
case WalletType.tron: case WalletType.tron:
case WalletType.wownero: case WalletType.wownero:
case WalletType.decred: case WalletType.decred:
case WalletType.tari:
return true; return true;
case WalletType.zano: case WalletType.zano:
case WalletType.haven: case WalletType.haven:

View file

@ -215,6 +215,7 @@ abstract class HomeSettingsViewModelBase with Store {
case WalletType.wownero: case WalletType.wownero:
case WalletType.bitcoinCash: case WalletType.bitcoinCash:
case WalletType.decred: case WalletType.decred:
case WalletType.tari:
return false; return false;
} }

View file

@ -7,6 +7,7 @@ import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/solana/solana.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/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart'; import 'package:cake_wallet/zano/zano.dart';
@ -224,6 +225,11 @@ class TransactionListItem extends ActionListItem with Keyable {
cryptoAmount: decred!.formatterDecredAmountToDouble(amount: transaction.amount), cryptoAmount: decred!.formatterDecredAmountToDouble(amount: transaction.amount),
price: price); price: price);
break; break;
case WalletType.tari:
amount = calculateFiatAmountRaw(
cryptoAmount: tari!.formatterTariAmountToDouble(amount: transaction.amount),
price: price);
break;
case WalletType.none: case WalletType.none:
case WalletType.banano: case WalletType.banano:
case WalletType.haven: case WalletType.haven:

View file

@ -783,6 +783,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
depositCurrency = CryptoCurrency.dcr; depositCurrency = CryptoCurrency.dcr;
receiveCurrency = CryptoCurrency.xmr; receiveCurrency = CryptoCurrency.xmr;
break; break;
case WalletType.tari:
depositCurrency = CryptoCurrency.tari;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.none: case WalletType.none:
break; break;
} }

View file

@ -87,6 +87,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.zano: case WalletType.zano:
case WalletType.decred: case WalletType.decred:
case WalletType.tari:
return false; return false;
} }
} }

View file

@ -96,6 +96,7 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.banano: case WalletType.banano:
case WalletType.solana: case WalletType.solana:
case WalletType.tron: case WalletType.tron:
case WalletType.tari:
return false; return false;
} }
} }

View file

@ -122,6 +122,7 @@ abstract class OutputBase with Store {
case WalletType.banano: case WalletType.banano:
case WalletType.solana: case WalletType.solana:
case WalletType.tron: case WalletType.tron:
case WalletType.tari:
break; break;
} }
@ -302,6 +303,7 @@ abstract class OutputBase with Store {
case WalletType.zano: case WalletType.zano:
case WalletType.nano: case WalletType.nano:
case WalletType.decred: case WalletType.decred:
case WalletType.tari:
maximumFractionDigits = 12; maximumFractionDigits = 12;
break; break;
case WalletType.bitcoin: case WalletType.bitcoin:

View file

@ -88,6 +88,9 @@ abstract class TransactionDetailsViewModelBase with Store {
case WalletType.decred: case WalletType.decred:
_addDecredListItems(tx, dateFormat); _addDecredListItems(tx, dateFormat);
break; break;
case WalletType.tari:
_addTariListItems(tx, dateFormat);
break;
case WalletType.none: case WalletType.none:
case WalletType.banano: case WalletType.banano:
break; break;
@ -193,6 +196,7 @@ abstract class TransactionDetailsViewModelBase with Store {
return 'https://explorer.zano.org/transaction/${txId}'; return 'https://explorer.zano.org/transaction/${txId}';
case WalletType.decred: case WalletType.decred:
return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}'; return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}';
case WalletType.tari: // ToDo (Konsti)
case WalletType.none: case WalletType.none:
return ''; return '';
} }
@ -227,6 +231,7 @@ abstract class TransactionDetailsViewModelBase with Store {
return S.current.view_transaction_on + 'explorer.zano.org'; return S.current.view_transaction_on + 'explorer.zano.org';
case WalletType.decred: case WalletType.decred:
return S.current.view_transaction_on + 'dcrdata.decred.org'; return S.current.view_transaction_on + 'dcrdata.decred.org';
case WalletType.tari: // ToDo (Konsti)
case WalletType.none: case WalletType.none:
return ''; return '';
} }
@ -854,4 +859,22 @@ abstract class TransactionDetailsViewModelBase with Store {
StandartListItem(title: S.current.transaction_details_title, value: comment), 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),
// ]);
}
} }

View file

@ -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 = <String, String>{};
final pathParts = <String>[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 { abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
WalletAddressListViewModelBase({ WalletAddressListViewModelBase({
required AppStore appStore, required AppStore appStore,
@ -331,6 +361,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
return ZanoURI(amount: amount, address: address.address); return ZanoURI(amount: amount, address: address.address);
case WalletType.decred: case WalletType.decred:
return DecredURI(amount: amount, address: address.address); 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: case WalletType.none:
throw Exception('Unexpected type: ${type.toString()}'); throw Exception('Unexpected type: ${type.toString()}');
} }

View file

@ -163,6 +163,7 @@ abstract class WalletKeysViewModelBase with Store {
case WalletType.bitcoinCash: case WalletType.bitcoinCash:
case WalletType.none: case WalletType.none:
case WalletType.haven: case WalletType.haven:
case WalletType.tari: // ToDo (Konsti)
// final keys = bitcoin!.getWalletKeys(_appStore.wallet!); // final keys = bitcoin!.getWalletKeys(_appStore.wallet!);
// //
// items.addAll([ // items.addAll([

View file

@ -1,5 +1,6 @@
import 'package:cake_wallet/core/new_wallet_arguments.dart'; import 'package:cake_wallet/core/new_wallet_arguments.dart';
import 'package:cake_wallet/ethereum/ethereum.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/zano/zano.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/solana/solana.dart';
@ -70,6 +71,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
return seedSettingsViewModel.nanoSeedType == NanoSeedType.bip39 return seedSettingsViewModel.nanoSeedType == NanoSeedType.bip39
? advancedPrivacySettingsViewModel.seedPhraseLength.value ? advancedPrivacySettingsViewModel.seedPhraseLength.value
: 24; : 24;
case WalletType.tari:
case WalletType.none: case WalletType.none:
return 24; return 24;
case WalletType.haven: case WalletType.haven:
@ -164,6 +166,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
); );
case WalletType.decred: case WalletType.decred:
return decred!.createDecredNewWalletCredentials(name: name); return decred!.createDecredNewWalletCredentials(name: name);
case WalletType.tari:
return tari!.createTariNewWalletCredentials(name: name);
case WalletType.none: case WalletType.none:
case WalletType.haven: case WalletType.haven:
throw Exception('Unexpected type: ${type.toString()}'); throw Exception('Unexpected type: ${type.toString()}');

View file

@ -9,6 +9,7 @@ import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/store/app_store.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/tron/tron.dart';
import 'package:cake_wallet/decred/decred.dart'; import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.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.litecoin:
case WalletType.bitcoinCash: case WalletType.bitcoinCash:
case WalletType.zano: case WalletType.zano:
case WalletType.tari:
case WalletType.none: case WalletType.none:
availableModes = [WalletRestoreMode.seed]; availableModes = [WalletRestoreMode.seed];
break; break;
@ -182,6 +184,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
mnemonic: seed, mnemonic: seed,
password: password, password: password,
); );
case WalletType.tari:
return tari!.createTariRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password,
);
case WalletType.none: case WalletType.none:
case WalletType.haven: case WalletType.haven:
break; break;

View file

@ -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");
});
});
});
}

View file

@ -30,6 +30,7 @@ Future<void> main(List<String> args) async {
final hasWownero = args.contains('${prefix}wownero'); final hasWownero = args.contains('${prefix}wownero');
final hasZano = args.contains('${prefix}zano'); final hasZano = args.contains('${prefix}zano');
final hasDecred = args.contains('${prefix}decred'); final hasDecred = args.contains('${prefix}decred');
final hasTari = args.contains('${prefix}tari');
final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage'); final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage');
await generateBitcoin(hasBitcoin); await generateBitcoin(hasBitcoin);
@ -44,6 +45,7 @@ Future<void> main(List<String> args) async {
await generateZano(hasZano); await generateZano(hasZano);
// await generateBanano(hasEthereum); // await generateBanano(hasEthereum);
await generateDecred(hasDecred); await generateDecred(hasDecred);
await generateTari(hasTari);
await generatePubspec( await generatePubspec(
hasMonero: hasMonero, hasMonero: hasMonero,
@ -59,6 +61,7 @@ Future<void> main(List<String> args) async {
hasWownero: hasWownero, hasWownero: hasWownero,
hasZano: hasZano, hasZano: hasZano,
hasDecred: hasDecred, hasDecred: hasDecred,
hasTari: hasTari,
); );
await generateWalletTypes( await generateWalletTypes(
hasMonero: hasMonero, hasMonero: hasMonero,
@ -73,6 +76,7 @@ Future<void> main(List<String> args) async {
hasWownero: hasWownero, hasWownero: hasWownero,
hasZano: hasZano, hasZano: hasZano,
hasDecred: hasDecred, hasDecred: hasDecred,
hasTari: hasTari,
); );
await injectSecureStorage(!excludeFlutterSecureStorage); await injectSecureStorage(!excludeFlutterSecureStorage);
} }
@ -1377,6 +1381,54 @@ abstract class Decred {
await outputFile.writeAsString(output); await outputFile.writeAsString(output);
} }
Future<void> 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<String> getTariWordList(String language);
WalletService createTariWalletService(Box<WalletInfo> 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<TransactionPriority> 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<void> generatePubspec({ Future<void> generatePubspec({
required bool hasMonero, required bool hasMonero,
required bool hasBitcoin, required bool hasBitcoin,
@ -1391,6 +1443,7 @@ Future<void> generatePubspec({
required bool hasWownero, required bool hasWownero,
required bool hasZano, required bool hasZano,
required bool hasDecred, required bool hasDecred,
required bool hasTari,
}) async { }) async {
const cwCore = """ const cwCore = """
cw_core: cw_core:
@ -1455,6 +1508,10 @@ Future<void> generatePubspec({
cw_decred: cw_decred:
path: ./cw_decred path: ./cw_decred
"""; """;
const cwTari = """
cw_tari:
path: ./cw_tari
""";
final inputFile = File(pubspecOutputPath); final inputFile = File(pubspecOutputPath);
final inputText = await inputFile.readAsString(); final inputText = await inputFile.readAsString();
final inputLines = inputText.split('\n'); final inputLines = inputText.split('\n');
@ -1520,6 +1577,10 @@ Future<void> generatePubspec({
output += '\n$cwZano'; output += '\n$cwZano';
} }
if (hasTari) {
output += '\n$cwTari';
}
final outputLines = output.split('\n'); final outputLines = output.split('\n');
inputLines.insertAll(dependenciesIndex + 1, outputLines); inputLines.insertAll(dependenciesIndex + 1, outputLines);
final outputContent = inputLines.join('\n'); final outputContent = inputLines.join('\n');
@ -1545,6 +1606,7 @@ Future<void> generateWalletTypes({
required bool hasWownero, required bool hasWownero,
required bool hasZano, required bool hasZano,
required bool hasDecred, required bool hasDecred,
required bool hasTari,
}) async { }) async {
final walletTypesFile = File(walletTypesPath); final walletTypesFile = File(walletTypesPath);
@ -1608,6 +1670,10 @@ Future<void> generateWalletTypes({
outputContent += '\tWalletType.wownero,\n'; outputContent += '\tWalletType.wownero,\n';
} }
if (hasWownero) {
outputContent += '\tWalletType.tari,\n';
}
outputContent += '];\n'; outputContent += '];\n';
await walletTypesFile.writeAsString(outputContent); await walletTypesFile.writeAsString(outputContent);
} }