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/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

View file

View file

@ -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;

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_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<TariBalance, TariTransactionHistory, TariTransactionInfo>
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<CryptoCurrency, TariBalance>.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<Map<String, EVMChainTransactionInfo>> fetchTransactions() async {
Future<Map<String, TariTransactionInfo>> fetchTransactions() async {
// ToDo
throw UnimplementedError();
}
@ -144,7 +143,8 @@ abstract class TariWalletBase extends WalletBase<
@override
Future<void> updateBalance() async {
balance[CryptoCurrency.tari] = TariBalance.fromFfi(_walletFfi.getBalance());
balance[CryptoCurrency.tari] =
TariBalance.fromTariBalanceInfo(_walletFfi.getBalance());
}
@override

View file

@ -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;

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:
sdk: flutter
flutter_lints: ^5.0.0
build_runner: ^2.4.7
mobx_codegen: ^2.0.7
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/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<MnemonicItem> {
return zano!.getWordList(language);
case WalletType.decred:
return decred!.getDecredWordList();
case WalletType.tari:
return tari!.getTariWordList(language);
case WalletType.none:
case WalletType.haven:
return [];

View file

@ -91,6 +91,7 @@ class WalletCreationService {
case WalletType.banano:
case WalletType.zano:
case WalletType.decred:
case WalletType.tari:
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/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<void> 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');

View file

@ -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<void> 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 '';

View file

@ -46,6 +46,9 @@ Future<List<Node>> 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 = '';

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/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<TransactionPriority> 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 [];

View file

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

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.zano:
case WalletType.decred:
case WalletType.tari:
return false;
}
}

View file

@ -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:

View file

@ -215,6 +215,7 @@ abstract class HomeSettingsViewModelBase with Store {
case WalletType.wownero:
case WalletType.bitcoinCash:
case WalletType.decred:
case WalletType.tari:
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/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:

View file

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

View file

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

View file

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

View file

@ -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:

View file

@ -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),
// ]);
}
}

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 {
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()}');
}

View file

@ -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([

View file

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

View file

@ -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;

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 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<void> main(List<String> args) async {
await generateZano(hasZano);
// await generateBanano(hasEthereum);
await generateDecred(hasDecred);
await generateTari(hasTari);
await generatePubspec(
hasMonero: hasMonero,
@ -59,6 +61,7 @@ Future<void> main(List<String> args) async {
hasWownero: hasWownero,
hasZano: hasZano,
hasDecred: hasDecred,
hasTari: hasTari,
);
await generateWalletTypes(
hasMonero: hasMonero,
@ -73,6 +76,7 @@ Future<void> main(List<String> 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<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({
required bool hasMonero,
required bool hasBitcoin,
@ -1391,6 +1443,7 @@ Future<void> generatePubspec({
required bool hasWownero,
required bool hasZano,
required bool hasDecred,
required bool hasTari,
}) async {
const cwCore = """
cw_core:
@ -1455,6 +1508,10 @@ Future<void> 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<void> 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<void> generateWalletTypes({
required bool hasWownero,
required bool hasZano,
required bool hasDecred,
required bool hasTari,
}) async {
final walletTypesFile = File(walletTypesPath);
@ -1608,6 +1670,10 @@ Future<void> generateWalletTypes({
outputContent += '\tWalletType.wownero,\n';
}
if (hasWownero) {
outputContent += '\tWalletType.tari,\n';
}
outputContent += '];\n';
await walletTypesFile.writeAsString(outputContent);
}