mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-22 10:34:32 +00:00
Merge branch 'testing' into tor
This commit is contained in:
commit
3abc097471
2 changed files with 111 additions and 65 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue