2023-12-14 04:51:16 +00:00
|
|
|
import 'dart:convert';
|
2023-10-02 14:17:35 +00:00
|
|
|
import 'dart:io';
|
2023-12-16 08:49:23 +00:00
|
|
|
|
2023-10-02 14:17:35 +00:00
|
|
|
import 'package:collection/collection.dart';
|
2023-12-14 04:51:16 +00:00
|
|
|
import 'package:cw_core/crypto_currency.dart';
|
2023-11-17 17:40:23 +00:00
|
|
|
import 'package:cw_core/node.dart';
|
2023-12-16 08:49:23 +00:00
|
|
|
import 'package:cw_core/pathForWallet.dart';
|
2023-10-02 14:17:35 +00:00
|
|
|
import 'package:cw_core/wallet_base.dart';
|
|
|
|
import 'package:cw_core/wallet_credentials.dart';
|
|
|
|
import 'package:cw_core/wallet_info.dart';
|
2023-12-16 08:49:23 +00:00
|
|
|
import 'package:cw_core/wallet_service.dart';
|
2023-10-02 14:17:35 +00:00
|
|
|
import 'package:cw_core/wallet_type.dart';
|
2024-03-06 06:48:59 +00:00
|
|
|
import 'package:cw_zano/api/api_calls.dart' as calls;
|
|
|
|
import 'package:cw_zano/api/api_calls.dart';
|
2023-12-16 12:19:11 +00:00
|
|
|
import 'package:cw_zano/api/consts.dart';
|
|
|
|
import 'package:cw_zano/api/exceptions/already_exists_exception.dart';
|
2023-12-16 15:00:22 +00:00
|
|
|
import 'package:cw_zano/api/exceptions/create_wallet_exception.dart';
|
2023-12-16 12:19:11 +00:00
|
|
|
import 'package:cw_zano/api/exceptions/restore_from_seed_exception.dart';
|
|
|
|
import 'package:cw_zano/api/exceptions/wrong_seed_exception.dart';
|
2023-12-16 08:49:23 +00:00
|
|
|
import 'package:cw_zano/api/model/create_wallet_result.dart';
|
|
|
|
import 'package:cw_zano/zano_balance.dart';
|
|
|
|
import 'package:cw_zano/zano_wallet.dart';
|
|
|
|
import 'package:hive/hive.dart';
|
2023-12-14 04:51:16 +00:00
|
|
|
import 'package:mobx/mobx.dart';
|
2023-10-02 14:17:35 +00:00
|
|
|
|
|
|
|
class ZanoNewWalletCredentials extends WalletCredentials {
|
2024-03-08 10:50:34 +00:00
|
|
|
ZanoNewWalletCredentials({required String name, String? password}) : super(name: name, password: password);
|
2023-10-02 14:17:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class ZanoRestoreWalletFromSeedCredentials extends WalletCredentials {
|
2024-03-08 10:50:34 +00:00
|
|
|
ZanoRestoreWalletFromSeedCredentials({required String name, required String password, required int height, required this.mnemonic})
|
2023-10-02 14:17:35 +00:00
|
|
|
: super(name: name, password: password, height: height);
|
|
|
|
|
|
|
|
final String mnemonic;
|
|
|
|
}
|
|
|
|
|
|
|
|
class ZanoRestoreWalletFromKeysCredentials extends WalletCredentials {
|
|
|
|
ZanoRestoreWalletFromKeysCredentials(
|
2024-03-08 10:50:34 +00:00
|
|
|
{required String name, required String password, required this.language, required this.address, required this.viewKey, required this.spendKey, required int height})
|
2023-10-02 14:17:35 +00:00
|
|
|
: super(name: name, password: password, height: height);
|
|
|
|
|
|
|
|
final String language;
|
|
|
|
final String address;
|
|
|
|
final String viewKey;
|
|
|
|
final String spendKey;
|
|
|
|
}
|
|
|
|
|
2024-03-08 10:50:34 +00:00
|
|
|
class ZanoWalletService extends WalletService<ZanoNewWalletCredentials, ZanoRestoreWalletFromSeedCredentials, ZanoRestoreWalletFromKeysCredentials> {
|
2023-10-02 14:17:35 +00:00
|
|
|
ZanoWalletService(this.walletInfoSource);
|
|
|
|
|
|
|
|
final Box<WalletInfo> walletInfoSource;
|
|
|
|
|
2024-03-08 10:50:34 +00:00
|
|
|
static bool walletFilesExist(String path) => !File(path).existsSync() && !File('$path.keys').existsSync();
|
2023-10-02 14:17:35 +00:00
|
|
|
|
2023-11-17 17:40:23 +00:00
|
|
|
int hWallet = 0;
|
|
|
|
|
2023-10-02 14:17:35 +00:00
|
|
|
@override
|
|
|
|
WalletType getType() => WalletType.zano;
|
|
|
|
|
|
|
|
@override
|
2023-12-14 04:51:16 +00:00
|
|
|
Future<ZanoWallet> create(WalletCredentials credentials) async {
|
2023-10-02 14:17:35 +00:00
|
|
|
try {
|
2023-12-14 04:51:16 +00:00
|
|
|
final wallet = ZanoWallet(credentials.walletInfo!);
|
|
|
|
await wallet.connectToNode(node: Node());
|
2023-10-02 14:17:35 +00:00
|
|
|
final path = await pathForWallet(name: credentials.name, type: getType());
|
2024-03-10 02:51:30 +00:00
|
|
|
final result = ApiCalls.createWallet(language: '', path: path, password: credentials.password!);
|
2023-12-14 04:51:16 +00:00
|
|
|
final map = json.decode(result) as Map<String, dynamic>;
|
2024-03-10 02:51:30 +00:00
|
|
|
_checkForCreateWalletError(map);
|
2023-12-16 15:00:22 +00:00
|
|
|
final createWalletResult = CreateWalletResult.fromJson(map['result'] as Map<String, dynamic>);
|
|
|
|
_parseCreateWalletResult(createWalletResult, wallet);
|
2024-03-06 06:48:59 +00:00
|
|
|
await wallet.store();
|
2023-12-16 15:00:22 +00:00
|
|
|
await wallet.init(createWalletResult.wi.address);
|
2023-10-02 14:17:35 +00:00
|
|
|
return wallet;
|
|
|
|
} catch (e) {
|
|
|
|
// TODO: Implement Exception for wallet list service.
|
|
|
|
print('ZanoWalletsManager Error: ${e.toString()}');
|
|
|
|
rethrow;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<bool> isWalletExit(String name) async {
|
|
|
|
try {
|
|
|
|
final path = await pathForWallet(name: name, type: getType());
|
2024-03-06 06:48:59 +00:00
|
|
|
return ApiCalls.isWalletExist(path: path);
|
2023-10-02 14:17:35 +00:00
|
|
|
} catch (e) {
|
|
|
|
// TODO: Implement Exception for wallet list service.
|
|
|
|
print('ZanoWalletsManager Error: $e');
|
|
|
|
rethrow;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<ZanoWallet> openWallet(String name, String password) async {
|
|
|
|
try {
|
|
|
|
final path = await pathForWallet(name: name, type: getType());
|
|
|
|
|
|
|
|
if (walletFilesExist(path)) {
|
|
|
|
await repairOldAndroidWallet(name);
|
|
|
|
}
|
|
|
|
|
2024-03-08 10:50:34 +00:00
|
|
|
final walletInfo = walletInfoSource.values.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
|
2023-12-14 04:51:16 +00:00
|
|
|
final wallet = ZanoWallet(walletInfo);
|
|
|
|
await wallet.connectToNode(node: Node());
|
|
|
|
final result = wallet.loadWallet(path, password);
|
2024-03-10 02:51:30 +00:00
|
|
|
print('load wallet result $result');
|
2023-12-14 04:51:16 +00:00
|
|
|
final map = json.decode(result) as Map<String, dynamic>;
|
2024-03-10 02:51:30 +00:00
|
|
|
_checkForCreateWalletError(map);
|
2023-12-16 15:00:22 +00:00
|
|
|
final createWalletResult = CreateWalletResult.fromJson(map['result'] as Map<String, dynamic>);
|
|
|
|
_parseCreateWalletResult(createWalletResult, wallet);
|
2024-03-06 06:48:59 +00:00
|
|
|
await wallet.store();
|
2023-12-16 15:00:22 +00:00
|
|
|
await wallet.init(createWalletResult.wi.address);
|
2023-10-02 14:17:35 +00:00
|
|
|
return wallet;
|
|
|
|
} catch (e) {
|
|
|
|
rethrow;
|
2024-03-10 02:51:30 +00:00
|
|
|
// TODO: uncomment after merge
|
|
|
|
//await restoreWalletFilesFromBackup(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _checkForCreateWalletError(Map<String, dynamic> map) {
|
|
|
|
if (map['error'] != null) {
|
|
|
|
final code = map['error']!['code'] ?? '';
|
|
|
|
final message = map['error']!['message'] ?? '';
|
|
|
|
throw CreateWalletException('Error creating/loading wallet $code $message');
|
|
|
|
}
|
|
|
|
if (map['result'] == null) {
|
|
|
|
throw CreateWalletException('Error creating/loading wallet, empty response');
|
2023-10-02 14:17:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-14 04:51:16 +00:00
|
|
|
void _parseCreateWalletResult(CreateWalletResult result, ZanoWallet wallet) {
|
|
|
|
hWallet = result.walletId;
|
|
|
|
wallet.hWallet = hWallet;
|
|
|
|
wallet.walletAddresses.address = result.wi.address;
|
|
|
|
final balance = result.wi.balances.first;
|
2024-03-08 10:50:34 +00:00
|
|
|
wallet.defaultAsssetId = balance.assetInfo.assetId;
|
|
|
|
wallet.balance = ObservableMap.of({CryptoCurrency.zano: ZanoBalance(total: balance.total, unlocked: balance.unlocked)});
|
2023-12-14 04:51:16 +00:00
|
|
|
if (result.recentHistory.history != null) {
|
|
|
|
wallet.history = result.recentHistory.history!;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-02 14:17:35 +00:00
|
|
|
@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);
|
|
|
|
}
|
|
|
|
|
2024-03-08 10:50:34 +00:00
|
|
|
final walletInfo = walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(wallet, getType()));
|
2023-10-02 14:17:35 +00:00
|
|
|
await walletInfoSource.delete(walletInfo.key);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2023-12-14 04:51:16 +00:00
|
|
|
Future<void> rename(String currentName, String password, String newName) async {
|
2024-03-08 10:50:34 +00:00
|
|
|
final currentWalletInfo = walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
|
2023-12-14 04:51:16 +00:00
|
|
|
final currentWallet = ZanoWallet(currentWalletInfo);
|
2023-10-02 14:17:35 +00:00
|
|
|
|
|
|
|
await currentWallet.renameWalletFiles(newName);
|
|
|
|
|
|
|
|
final newWalletInfo = currentWalletInfo;
|
|
|
|
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
|
|
|
newWalletInfo.name = newName;
|
|
|
|
|
|
|
|
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2023-12-14 04:51:16 +00:00
|
|
|
Future<ZanoWallet> restoreFromKeys(ZanoRestoreWalletFromKeysCredentials credentials) async {
|
2024-03-10 02:51:30 +00:00
|
|
|
throw UnimplementedError('Restore from keys not implemented');
|
2023-10-02 14:17:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2023-12-14 04:51:16 +00:00
|
|
|
Future<ZanoWallet> restoreFromSeed(ZanoRestoreWalletFromSeedCredentials credentials) async {
|
2023-10-02 14:17:35 +00:00
|
|
|
try {
|
2023-12-14 04:51:16 +00:00
|
|
|
final wallet = ZanoWallet(credentials.walletInfo!);
|
|
|
|
await wallet.connectToNode(node: Node());
|
2023-10-02 14:17:35 +00:00
|
|
|
final path = await pathForWallet(name: credentials.name, type: getType());
|
2024-03-06 06:48:59 +00:00
|
|
|
final result = ApiCalls.restoreWalletFromSeed(path: path, password: credentials.password!, seed: credentials.mnemonic);
|
2023-12-16 12:19:11 +00:00
|
|
|
final map = json.decode(result) as Map<String, dynamic>;
|
2023-12-14 04:51:16 +00:00
|
|
|
if (map['result'] != null) {
|
2024-03-08 10:50:34 +00:00
|
|
|
final createWalletResult = CreateWalletResult.fromJson(map['result'] as Map<String, dynamic>);
|
2023-12-14 04:51:16 +00:00
|
|
|
_parseCreateWalletResult(createWalletResult, wallet);
|
2024-03-06 06:48:59 +00:00
|
|
|
await wallet.store();
|
2023-12-16 15:00:22 +00:00
|
|
|
await wallet.init(createWalletResult.wi.address);
|
|
|
|
return wallet;
|
2023-12-16 12:19:11 +00:00
|
|
|
} else if (map['error'] != null) {
|
|
|
|
final code = map['error']['code'] as String;
|
|
|
|
final message = map['error']['message'] as String;
|
|
|
|
if (code == Consts.errorWrongSeed) {
|
|
|
|
throw WrongSeedException(message);
|
|
|
|
} else if (code == Consts.errorAlreadyExists) {
|
|
|
|
throw AlreadyExistsException(message);
|
|
|
|
}
|
|
|
|
throw RestoreFromSeedException(code, message);
|
2023-12-14 04:51:16 +00:00
|
|
|
}
|
2024-03-08 10:50:34 +00:00
|
|
|
throw RestoreFromSeedException('', '');
|
2023-10-02 14:17:35 +00:00
|
|
|
} catch (e) {
|
|
|
|
// TODO: Implement Exception for wallet list service.
|
|
|
|
print('ZanoWalletsManager Error: $e');
|
|
|
|
rethrow;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> repairOldAndroidWallet(String name) async {
|
|
|
|
try {
|
|
|
|
if (!Platform.isAndroid) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-12-14 04:51:16 +00:00
|
|
|
final oldAndroidWalletDirPath = await outdatedAndroidPathForWalletDir(name: name);
|
2023-10-02 14:17:35 +00:00
|
|
|
final dir = Directory(oldAndroidWalletDirPath);
|
|
|
|
|
|
|
|
if (!dir.existsSync()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-12-14 04:51:16 +00:00
|
|
|
final newWalletDirPath = await pathForWalletDir(name: name, type: getType());
|
2023-10-02 14:17:35 +00:00
|
|
|
|
|
|
|
dir.listSync().forEach((f) {
|
|
|
|
final file = File(f.path);
|
|
|
|
final name = f.path.split('/').last;
|
|
|
|
final newPath = newWalletDirPath + '/$name';
|
|
|
|
final newFile = File(newPath);
|
|
|
|
|
|
|
|
if (!newFile.existsSync()) {
|
|
|
|
newFile.createSync();
|
|
|
|
}
|
|
|
|
newFile.writeAsBytesSync(file.readAsBytesSync());
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
print(e.toString());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|