Cw 688 avoid wallet file corruption (#1582)

* CW-688 Store Seed and keys in .keys file

* CW-688 Open wallet from keys in .keys file and migrate wallets using the old file

* CW-688 Open wallet from keys in .keys file and migrate wallets using the old file

* CW-688 Restore .keys file from .keys.backup

* CW-688 Restore .keys file from .keys.backup

* CW-688 Move saving .keys files into the save function instead of the service

* CW-688 Handle corrupt wallets

* CW-688 Handle corrupt wallets

* CW-688 Remove code duplication

* CW-688 Reduce cache dependency

* wrap any file reading/writing function with try/catch [skip ci]

---------

Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>
This commit is contained in:
Omar Hatem 2024-08-09 23:15:30 +03:00 committed by GitHub
parent 8e4082d680
commit fb33a6f23d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 433 additions and 144 deletions

View file

@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/node.dart';
@ -12,6 +13,7 @@ import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_addresses.dart';
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_solana/default_spl_tokens.dart';
import 'package:cw_solana/file.dart';
import 'package:cw_solana/solana_balance.dart';
@ -36,7 +38,8 @@ part 'solana_wallet.g.dart';
class SolanaWallet = SolanaWalletBase with _$SolanaWallet;
abstract class SolanaWalletBase
extends WalletBase<SolanaBalance, SolanaTransactionHistory, SolanaTransactionInfo> with Store {
extends WalletBase<SolanaBalance, SolanaTransactionHistory, SolanaTransactionInfo>
with Store, WalletKeysFile {
SolanaWalletBase({
required WalletInfo walletInfo,
String? mnemonic,
@ -121,6 +124,9 @@ abstract class SolanaWalletBase
return privateKey;
}
@override
WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey);
Future<void> init() async {
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${SPLToken.boxName}";
@ -336,6 +342,11 @@ abstract class SolanaWalletBase
@override
Future<void> save() async {
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
await saveKeysFile(_password);
saveKeysFile(_password, true);
}
await walletAddresses.updateAddressesInBox();
final path = await makePath();
await write(path: path, password: _password, data: toJSON());
@ -361,8 +372,6 @@ abstract class SolanaWalletBase
}
}
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
String toJSON() => json.encode({
'mnemonic': _mnemonic,
'private_key': _hexPrivateKey,
@ -374,18 +383,36 @@ abstract class SolanaWalletBase
required String password,
required WalletInfo walletInfo,
}) async {
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
final path = await pathForWallet(name: name, type: walletInfo.type);
final jsonSource = await read(path: path, password: password);
final data = json.decode(jsonSource) as Map;
final mnemonic = data['mnemonic'] as String?;
final privateKey = data['private_key'] as String?;
final balance = SolanaBalance.fromJSON(data['balance'] as String) ?? SolanaBalance(0.0);
Map<String, dynamic>? data;
try {
final jsonSource = await read(path: path, password: password);
data = json.decode(jsonSource) as Map<String, dynamic>;
} catch (e) {
if (!hasKeysFile) rethrow;
}
final balance = SolanaBalance.fromJSON(data?['balance'] as String) ?? SolanaBalance(0.0);
final WalletKeysData keysData;
// Migrate wallet from the old scheme to then new .keys file scheme
if (!hasKeysFile) {
final mnemonic = data!['mnemonic'] as String?;
final privateKey = data['private_key'] as String?;
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey);
} else {
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
}
return SolanaWallet(
walletInfo: walletInfo,
password: password,
mnemonic: mnemonic,
privateKey: privateKey,
mnemonic: keysData.mnemonic,
privateKey: keysData.privateKey,
initialBalance: balance,
);
}