Merge branch 'testing' into tor

This commit is contained in:
julian-CStack 2024-05-03 14:13:17 -06:00 committed by GitHub
commit 3abc097471
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 111 additions and 65 deletions

View file

@ -50,4 +50,12 @@ class Solana extends Bip39Currency {
@override @override
String get genesisHash => throw UnimplementedError(); String get genesisHash => throw UnimplementedError();
@override
bool operator ==(Object other) {
return other is Solana && other.network == network;
}
@override
int get hashCode => Object.hash(Solana, network);
} }

View file

@ -1,6 +1,8 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:decimal/decimal.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:socks5_proxy/socks_client.dart'; import 'package:socks5_proxy/socks_client.dart';
import 'package:solana/dto.dart'; import 'package:solana/dto.dart';
@ -16,7 +18,6 @@ import 'package:stackwallet/services/tor_service.dart';
import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/solana.dart'; import 'package:stackwallet/wallets/crypto_currency/coins/solana.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
@ -27,24 +28,30 @@ import 'package:tuple/tuple.dart';
class SolanaWallet extends Bip39Wallet<Solana> { class SolanaWallet extends Bip39Wallet<Solana> {
SolanaWallet(CryptoCurrencyNetwork network) : super(Solana(network)); SolanaWallet(CryptoCurrencyNetwork network) : super(Solana(network));
static const String _addressDerivationPath = "m/44'/501'/0'/0'";
NodeModel? _solNode; NodeModel? _solNode;
RpcClient? _rpcClient; // The Solana RpcClient. RpcClient? _rpcClient; // The Solana RpcClient.
Future<Ed25519HDKeyPair> _getKeyPair() async { Future<Ed25519HDKeyPair> _getKeyPair() async {
return Ed25519HDKeyPair.fromMnemonic(await getMnemonic(), return Ed25519HDKeyPair.fromMnemonic(
account: 0, change: 0); await getMnemonic(),
account: 0,
change: 0,
);
} }
Future<Address> _getCurrentAddress() async { Future<Address> _generateAddress() async {
final addressStruct = Address( final addressStruct = Address(
walletId: walletId, walletId: walletId,
value: (await _getKeyPair()).address, value: (await _getKeyPair()).address,
publicKey: List<int>.empty(), publicKey: List<int>.empty(),
derivationIndex: 0, derivationIndex: 0,
derivationPath: DerivationPath()..value = "m/44'/501'/0'/0'", derivationPath: DerivationPath()..value = _addressDerivationPath,
type: cryptoCurrency.coin.primaryAddressType, type: cryptoCurrency.coin.primaryAddressType,
subType: AddressSubType.unknown); subType: AddressSubType.receiving,
);
return addressStruct; return addressStruct;
} }
@ -54,6 +61,26 @@ class SolanaWallet extends Bip39Wallet<Solana> {
return balance!.value; return balance!.value;
} }
Future<int?> _getEstimatedNetworkFee(Amount transferAmount) async {
final latestBlockhash = await _rpcClient?.getLatestBlockhash();
final pubKey = (await _getKeyPair()).publicKey;
final compiledMessage = Message(instructions: [
SystemInstruction.transfer(
fundingAccount: pubKey,
recipientAccount: pubKey,
lamports: transferAmount.raw.toInt(),
)
]).compile(
recentBlockhash: latestBlockhash!.value.blockhash,
feePayer: pubKey,
);
return await _rpcClient?.getFeeForMessage(
base64Encode(compiledMessage.toByteArray().toList()),
);
}
@override @override
FilterOperation? get changeAddressFilterOperation => FilterOperation? get changeAddressFilterOperation =>
throw UnimplementedError(); throw UnimplementedError();
@ -61,18 +88,13 @@ class SolanaWallet extends Bip39Wallet<Solana> {
@override @override
Future<void> checkSaveInitialReceivingAddress() async { Future<void> checkSaveInitialReceivingAddress() async {
try { try {
final address = (await _getKeyPair()).address; Address? address = await getCurrentReceivingAddress();
await mainDB.updateOrPutAddresses([ if (address == null) {
Address( address = await _generateAddress();
walletId: walletId,
value: address, await mainDB.updateOrPutAddresses([address]);
publicKey: List<int>.empty(), }
derivationIndex: 0,
derivationPath: DerivationPath()..value = "m/44'/501'/0'/0'",
type: cryptoCurrency.coin.primaryAddressType,
subType: AddressSubType.unknown)
]);
} catch (e, s) { } catch (e, s) {
Logging.instance.log( Logging.instance.log(
"$runtimeType checkSaveInitialReceivingAddress() failed: $e\n$s", "$runtimeType checkSaveInitialReceivingAddress() failed: $e\n$s",
@ -96,24 +118,16 @@ class SolanaWallet extends Bip39Wallet<Solana> {
throw Exception("Insufficient available balance"); throw Exception("Insufficient available balance");
} }
int feeAmount; final feeAmount = await _getEstimatedNetworkFee(sendAmount);
final currentFees = await fees; if (feeAmount == null) {
switch (txData.feeRateType) { throw Exception(
case FeeRateType.fast: "Failed to get fees, please check your node connection.");
feeAmount = currentFees.fast;
break;
case FeeRateType.slow:
feeAmount = currentFees.slow;
break;
case FeeRateType.average:
default:
feeAmount = currentFees.medium;
break;
} }
final address = await getCurrentReceivingAddress();
// Rent exemption of Solana // Rent exemption of Solana
final accInfo = final accInfo = await _rpcClient?.getAccountInfo(address!.value);
await _rpcClient?.getAccountInfo((await _getKeyPair()).address);
final int minimumRent = final int minimumRent =
await _rpcClient?.getMinimumBalanceForRentExemption( await _rpcClient?.getMinimumBalanceForRentExemption(
accInfo!.value!.data.toString().length) ?? accInfo!.value!.data.toString().length) ??
@ -123,7 +137,9 @@ class SolanaWallet extends Bip39Wallet<Solana> {
txData.amount!.raw.toInt() - txData.amount!.raw.toInt() -
feeAmount)) { feeAmount)) {
throw Exception( throw Exception(
"Insufficient remaining balance for rent exemption, minimum rent: ${minimumRent / pow(10, cryptoCurrency.fractionDigits)}"); "Insufficient remaining balance for rent exemption, minimum rent: "
"${minimumRent / pow(10, cryptoCurrency.fractionDigits)}",
);
} }
return txData.copyWith( return txData.copyWith(
@ -157,7 +173,12 @@ class SolanaWallet extends Bip39Wallet<Solana> {
recipientAccount: recipientPubKey, recipientAccount: recipientPubKey,
lamports: txData.amount!.raw.toInt()), lamports: txData.amount!.raw.toInt()),
ComputeBudgetInstruction.setComputeUnitPrice( ComputeBudgetInstruction.setComputeUnitPrice(
microLamports: txData.fee!.raw.toInt()), microLamports: txData.fee!.raw.toInt() - 5000),
// 5000 lamports is the base fee for a transaction. This instruction adds the necessary fee on top of base fee if it is needed.
ComputeBudgetInstruction.setComputeUnitLimit(units: 1000000),
// 1000000 is the multiplication number to turn the compute unit price of microLamports to lamports.
// These instructions also help the user to not pay more than the shown fee.
// See: https://solanacookbook.com/references/basic-transactions.html#how-to-change-compute-budget-fee-priority-for-a-transaction
], ],
); );
@ -185,11 +206,13 @@ class SolanaWallet extends Bip39Wallet<Solana> {
); );
} }
final fee = await _rpcClient?.getFees(); final fee = await _getEstimatedNetworkFee(amount);
// TODO [prio=low]: handle null fee. if (fee == null) {
throw Exception("Failed to get fees, please check your node connection.");
}
return Amount( return Amount(
rawValue: BigInt.from(fee!.value.feeCalculator.lamportsPerSignature), rawValue: BigInt.from(fee),
fractionDigits: cryptoCurrency.fractionDigits, fractionDigits: cryptoCurrency.fractionDigits,
); );
} }
@ -198,15 +221,23 @@ class SolanaWallet extends Bip39Wallet<Solana> {
Future<FeeObject> get fees async { Future<FeeObject> get fees async {
_checkClient(); _checkClient();
final fees = await _rpcClient?.getFees(); final fee = await _getEstimatedNetworkFee(
// TODO [prio=low]: handle null fees. Amount.fromDecimal(
Decimal.one, // 1 SOL
fractionDigits: cryptoCurrency.fractionDigits,
),
);
if (fee == null) {
throw Exception("Failed to get fees, please check your node connection.");
}
return FeeObject( return FeeObject(
numberOfBlocksFast: 1, numberOfBlocksFast: 1,
numberOfBlocksAverage: 1, numberOfBlocksAverage: 1,
numberOfBlocksSlow: 1, numberOfBlocksSlow: 1,
fast: fees!.value.feeCalculator.lamportsPerSignature, fast: fee,
medium: fees!.value.feeCalculator.lamportsPerSignature, medium: fee,
slow: fees!.value.feeCalculator.lamportsPerSignature); slow: fee);
} }
@override @override
@ -231,7 +262,7 @@ class SolanaWallet extends Bip39Wallet<Solana> {
@override @override
Future<void> recover({required bool isRescan}) async { Future<void> recover({required bool isRescan}) async {
await refreshMutex.protect(() async { await refreshMutex.protect(() async {
final addressStruct = await _getCurrentAddress(); final addressStruct = await _generateAddress();
await mainDB.updateOrPutAddresses([addressStruct]); await mainDB.updateOrPutAddresses([addressStruct]);
@ -253,13 +284,13 @@ class SolanaWallet extends Bip39Wallet<Solana> {
@override @override
Future<void> updateBalance() async { Future<void> updateBalance() async {
try { try {
final address = await getCurrentReceivingAddress();
_checkClient(); _checkClient();
final balance = await _rpcClient?.getBalance(info.cachedReceivingAddress); final balance = await _rpcClient?.getBalance(address!.value);
// Rent exemption of Solana // Rent exemption of Solana
final accInfo = final accInfo = await _rpcClient?.getAccountInfo(address!.value);
await _rpcClient?.getAccountInfo((await _getKeyPair()).address);
// TODO [prio=low]: handle null account info. // TODO [prio=low]: handle null account info.
final int minimumRent = final int minimumRent =
await _rpcClient?.getMinimumBalanceForRentExemption( await _rpcClient?.getMinimumBalanceForRentExemption(
@ -342,12 +373,14 @@ class SolanaWallet extends Bip39Wallet<Solana> {
final txsList = final txsList =
List<Tuple2<isar.Transaction, Address>>.empty(growable: true); List<Tuple2<isar.Transaction, Address>>.empty(growable: true);
final myAddress = (await getCurrentReceivingAddress())!;
// TODO [prio=low]: Revisit null assertion below. // TODO [prio=low]: Revisit null assertion below.
for (final tx in transactionsList!) { for (final tx in transactionsList!) {
final senderAddress = final senderAddress =
(tx.transaction as ParsedTransaction).message.accountKeys[0].pubkey; (tx.transaction as ParsedTransaction).message.accountKeys[0].pubkey;
final receiverAddress = var receiverAddress =
(tx.transaction as ParsedTransaction).message.accountKeys[1].pubkey; (tx.transaction as ParsedTransaction).message.accountKeys[1].pubkey;
var txType = isar.TransactionType.unknown; var txType = isar.TransactionType.unknown;
final txAmount = Amount( final txAmount = Amount(
@ -356,12 +389,16 @@ class SolanaWallet extends Bip39Wallet<Solana> {
fractionDigits: cryptoCurrency.fractionDigits, fractionDigits: cryptoCurrency.fractionDigits,
); );
if ((senderAddress == (await _getKeyPair()).address) && if ((senderAddress == myAddress.value) &&
(receiverAddress == (await _getKeyPair()).address)) { (receiverAddress == "11111111111111111111111111111111")) {
// The account that is only 1's are System Program accounts which
// means there is no receiver except the sender,
// see: https://explorer.solana.com/address/11111111111111111111111111111111
txType = isar.TransactionType.sentToSelf; txType = isar.TransactionType.sentToSelf;
} else if (senderAddress == (await _getKeyPair()).address) { receiverAddress = senderAddress;
} else if (senderAddress == myAddress.value) {
txType = isar.TransactionType.outgoing; txType = isar.TransactionType.outgoing;
} else if (receiverAddress == (await _getKeyPair()).address) { } else if (receiverAddress == myAddress.value) {
txType = isar.TransactionType.incoming; txType = isar.TransactionType.incoming;
} }
@ -390,11 +427,12 @@ class SolanaWallet extends Bip39Wallet<Solana> {
value: receiverAddress, value: receiverAddress,
publicKey: List<int>.empty(), publicKey: List<int>.empty(),
derivationIndex: 0, derivationIndex: 0,
derivationPath: DerivationPath()..value = "m/44'/501'/0'/0'", derivationPath: DerivationPath()..value = _addressDerivationPath,
type: AddressType.solana, type: AddressType.solana,
subType: txType == isar.TransactionType.outgoing subType: txType == isar.TransactionType.outgoing
? AddressSubType.unknown ? AddressSubType.unknown
: AddressSubType.receiving); : AddressSubType.receiving,
);
txsList.add(Tuple2(transaction, txAddress)); txsList.add(Tuple2(transaction, txAddress));
} }