mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-17 09:47:37 +00:00
solana tor wip
This commit is contained in:
parent
00cff96131
commit
11a5ed33e5
1 changed files with 115 additions and 50 deletions
|
@ -1,33 +1,40 @@
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:solana/dto.dart';
|
import 'package:solana/dto.dart';
|
||||||
import 'package:solana/solana.dart';
|
import 'package:solana/solana.dart';
|
||||||
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart' as isar;
|
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
|
||||||
|
import 'package:stackwallet/models/balance.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'
|
||||||
|
as isar;
|
||||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||||
|
import 'package:stackwallet/models/node_model.dart';
|
||||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||||
|
import 'package:stackwallet/services/node_service.dart';
|
||||||
|
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/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';
|
||||||
import 'package:stackwallet/wallets/models/tx_data.dart';
|
import 'package:stackwallet/wallets/models/tx_data.dart';
|
||||||
import 'package:stackwallet/wallets/wallet/intermediate/bip39_wallet.dart';
|
import 'package:stackwallet/wallets/wallet/intermediate/bip39_wallet.dart';
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
|
||||||
import 'package:stackwallet/models/balance.dart';
|
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
|
||||||
import 'package:stackwallet/models/node_model.dart';
|
|
||||||
import 'package:stackwallet/services/node_service.dart';
|
|
||||||
|
|
||||||
class SolanaWallet extends Bip39Wallet<Solana> {
|
class SolanaWallet extends Bip39Wallet<Solana> {
|
||||||
SolanaWallet(CryptoCurrencyNetwork network) : super(Solana(network));
|
SolanaWallet(CryptoCurrencyNetwork network) : super(Solana(network));
|
||||||
|
|
||||||
NodeModel? _solNode;
|
NodeModel? _solNode;
|
||||||
|
|
||||||
|
ElectrumXClient? electrumXClient; // Used for Tor.
|
||||||
|
RpcClient? rpcClient; // The Solana RpcClient.
|
||||||
|
|
||||||
Future<Ed25519HDKeyPair> _getKeyPair() async {
|
Future<Ed25519HDKeyPair> _getKeyPair() async {
|
||||||
return Ed25519HDKeyPair.fromMnemonic(await getMnemonic(), account: 0, change: 0);
|
return Ed25519HDKeyPair.fromMnemonic(await getMnemonic(),
|
||||||
|
account: 0, change: 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Address> _getCurrentAddress() async {
|
Future<Address> _getCurrentAddress() async {
|
||||||
|
@ -43,9 +50,9 @@ class SolanaWallet extends Bip39Wallet<Solana> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> _getCurrentBalanceInLamports() async {
|
Future<int> _getCurrentBalanceInLamports() async {
|
||||||
var rpcClient = RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
|
await _checkClients(); // Check electrumXClient and rpcClient.
|
||||||
var balance = await rpcClient.getBalance((await _getKeyPair()).address);
|
var balance = await rpcClient?.getBalance((await _getKeyPair()).address);
|
||||||
return balance.value;
|
return balance!.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -78,6 +85,8 @@ class SolanaWallet extends Bip39Wallet<Solana> {
|
||||||
@override
|
@override
|
||||||
Future<TxData> prepareSend({required TxData txData}) async {
|
Future<TxData> prepareSend({required TxData txData}) async {
|
||||||
try {
|
try {
|
||||||
|
await _checkClients(); // Check ElectrumXClient and Solana RpcClient.
|
||||||
|
|
||||||
if (txData.recipients == null || txData.recipients!.length != 1) {
|
if (txData.recipients == null || txData.recipients!.length != 1) {
|
||||||
throw Exception("$runtimeType prepareSend requires 1 recipient");
|
throw Exception("$runtimeType prepareSend requires 1 recipient");
|
||||||
}
|
}
|
||||||
|
@ -104,11 +113,17 @@ class SolanaWallet extends Bip39Wallet<Solana> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rent exemption of Solana
|
// Rent exemption of Solana
|
||||||
final rpcClient = RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
|
final accInfo =
|
||||||
final accInfo = await rpcClient.getAccountInfo((await _getKeyPair()).address);
|
await rpcClient?.getAccountInfo((await _getKeyPair()).address);
|
||||||
final minimumRent = await rpcClient.getMinimumBalanceForRentExemption(accInfo.value!.data.toString().length);
|
int minimumRent = await rpcClient?.getMinimumBalanceForRentExemption(
|
||||||
if (minimumRent > ((await _getCurrentBalanceInLamports()) - txData.amount!.raw.toInt() - feeAmount)) {
|
accInfo!.value!.data.toString().length) ??
|
||||||
throw Exception("Insufficient remaining balance for rent exemption, minimum rent: ${minimumRent / pow(10, cryptoCurrency.fractionDigits)}");
|
0; // TODO revisit null condition.
|
||||||
|
if (minimumRent >
|
||||||
|
((await _getCurrentBalanceInLamports()) -
|
||||||
|
txData.amount!.raw.toInt() -
|
||||||
|
feeAmount)) {
|
||||||
|
throw Exception(
|
||||||
|
"Insufficient remaining balance for rent exemption, minimum rent: ${minimumRent / pow(10, cryptoCurrency.fractionDigits)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return txData.copyWith(
|
return txData.copyWith(
|
||||||
|
@ -129,18 +144,24 @@ class SolanaWallet extends Bip39Wallet<Solana> {
|
||||||
@override
|
@override
|
||||||
Future<TxData> confirmSend({required TxData txData}) async {
|
Future<TxData> confirmSend({required TxData txData}) async {
|
||||||
try {
|
try {
|
||||||
|
await _checkClients(); // Check ElectrumXClient and Solana RpcClient.
|
||||||
|
|
||||||
final keyPair = await _getKeyPair();
|
final keyPair = await _getKeyPair();
|
||||||
final rpcClient = RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
|
|
||||||
var recipientAccount = txData.recipients!.first;
|
var recipientAccount = txData.recipients!.first;
|
||||||
var recipientPubKey = Ed25519HDPublicKey.fromBase58(recipientAccount.address);
|
var recipientPubKey =
|
||||||
|
Ed25519HDPublicKey.fromBase58(recipientAccount.address);
|
||||||
final message = Message(
|
final message = Message(
|
||||||
instructions: [
|
instructions: [
|
||||||
SystemInstruction.transfer(fundingAccount: keyPair.publicKey, recipientAccount: recipientPubKey, lamports: txData.amount!.raw.toInt()),
|
SystemInstruction.transfer(
|
||||||
ComputeBudgetInstruction.setComputeUnitPrice(microLamports: txData.fee!.raw.toInt()),
|
fundingAccount: keyPair.publicKey,
|
||||||
|
recipientAccount: recipientPubKey,
|
||||||
|
lamports: txData.amount!.raw.toInt()),
|
||||||
|
ComputeBudgetInstruction.setComputeUnitPrice(
|
||||||
|
microLamports: txData.fee!.raw.toInt()),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
final txid = await rpcClient.signAndSendTransaction(message, [keyPair]);
|
final txid = await rpcClient?.signAndSendTransaction(message, [keyPair]);
|
||||||
return txData.copyWith(
|
return txData.copyWith(
|
||||||
txid: txid,
|
txid: txid,
|
||||||
);
|
);
|
||||||
|
@ -155,6 +176,8 @@ class SolanaWallet extends Bip39Wallet<Solana> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Amount> estimateFeeFor(Amount amount, int feeRate) async {
|
Future<Amount> estimateFeeFor(Amount amount, int feeRate) async {
|
||||||
|
await _checkClients(); // Check ElectrumXClient and Solana RpcClient.
|
||||||
|
|
||||||
if (info.cachedBalance.spendable.raw == BigInt.zero) {
|
if (info.cachedBalance.spendable.raw == BigInt.zero) {
|
||||||
return Amount(
|
return Amount(
|
||||||
rawValue: BigInt.zero,
|
rawValue: BigInt.zero,
|
||||||
|
@ -162,27 +185,28 @@ class SolanaWallet extends Bip39Wallet<Solana> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final rpcClient = RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
|
final fee = await rpcClient?.getFees();
|
||||||
final fee = await rpcClient.getFees();
|
// TODO [prio=low]: handle null fee.
|
||||||
|
|
||||||
return Amount(
|
return Amount(
|
||||||
rawValue: BigInt.from(fee.value.feeCalculator.lamportsPerSignature),
|
rawValue: BigInt.from(fee!.value.feeCalculator.lamportsPerSignature),
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,
|
fractionDigits: cryptoCurrency.fractionDigits,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<FeeObject> get fees async {
|
Future<FeeObject> get fees async {
|
||||||
final rpcClient = RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
|
await _checkClients(); // Check ElectrumXClient and Solana RpcClient.
|
||||||
final fees = await rpcClient.getFees();
|
|
||||||
|
final fees = await rpcClient?.getFees();
|
||||||
|
// TODO [prio=low]: handle null fees.
|
||||||
return FeeObject(
|
return FeeObject(
|
||||||
numberOfBlocksFast: 1,
|
numberOfBlocksFast: 1,
|
||||||
numberOfBlocksAverage: 1,
|
numberOfBlocksAverage: 1,
|
||||||
numberOfBlocksSlow: 1,
|
numberOfBlocksSlow: 1,
|
||||||
fast: fees.value.feeCalculator.lamportsPerSignature,
|
fast: fees!.value.feeCalculator.lamportsPerSignature,
|
||||||
medium: fees.value.feeCalculator.lamportsPerSignature,
|
medium: fees!.value.feeCalculator.lamportsPerSignature,
|
||||||
slow: fees.value.feeCalculator.lamportsPerSignature
|
slow: fees!.value.feeCalculator.lamportsPerSignature);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -219,13 +243,20 @@ class SolanaWallet extends Bip39Wallet<Solana> {
|
||||||
@override
|
@override
|
||||||
Future<void> updateBalance() async {
|
Future<void> updateBalance() async {
|
||||||
try {
|
try {
|
||||||
var rpcClient = RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
|
await _checkClients(); // Check ElectrumXClient and Solana RpcClient.
|
||||||
var balance = await rpcClient.getBalance(info.cachedReceivingAddress);
|
|
||||||
|
var balance = await rpcClient?.getBalance(info.cachedReceivingAddress);
|
||||||
|
|
||||||
// Rent exemption of Solana
|
// Rent exemption of Solana
|
||||||
final accInfo = await rpcClient.getAccountInfo((await _getKeyPair()).address);
|
final accInfo =
|
||||||
final minimumRent = await rpcClient.getMinimumBalanceForRentExemption(accInfo.value!.data.toString().length);
|
await rpcClient?.getAccountInfo((await _getKeyPair()).address);
|
||||||
var spendableBalance = balance.value - minimumRent;
|
// TODO [prio=low]: handle null account info.
|
||||||
|
final int minimumRent =
|
||||||
|
await rpcClient?.getMinimumBalanceForRentExemption(
|
||||||
|
accInfo!.value!.data.toString().length) ??
|
||||||
|
0;
|
||||||
|
// TODO [prio=low]: revisit null condition.
|
||||||
|
var spendableBalance = balance!.value - minimumRent;
|
||||||
|
|
||||||
final newBalance = Balance(
|
final newBalance = Balance(
|
||||||
total: Amount(
|
total: Amount(
|
||||||
|
@ -258,8 +289,10 @@ class SolanaWallet extends Bip39Wallet<Solana> {
|
||||||
@override
|
@override
|
||||||
Future<void> updateChainHeight() async {
|
Future<void> updateChainHeight() async {
|
||||||
try {
|
try {
|
||||||
var rpcClient = RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
|
await _checkClients(); // Check ElectrumXClient and Solana RpcClient.
|
||||||
var blockHeight = await rpcClient.getSlot();
|
|
||||||
|
int blockHeight = await rpcClient?.getSlot() ?? 0;
|
||||||
|
// TODO [prio=low]: Revisit null condition.
|
||||||
|
|
||||||
await info.updateCachedChainHeight(
|
await info.updateCachedChainHeight(
|
||||||
newHeight: blockHeight,
|
newHeight: blockHeight,
|
||||||
|
@ -291,20 +324,30 @@ class SolanaWallet extends Bip39Wallet<Solana> {
|
||||||
@override
|
@override
|
||||||
Future<void> updateTransactions() async {
|
Future<void> updateTransactions() async {
|
||||||
try {
|
try {
|
||||||
var rpcClient = RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
|
await _checkClients(); // Check ElectrumXClient and Solana RpcClient.
|
||||||
var transactionsList = await rpcClient.getTransactionsList((await _getKeyPair()).publicKey, encoding: Encoding.jsonParsed);
|
|
||||||
var txsList = List<Tuple2<isar.Transaction, Address>>.empty(growable: true);
|
|
||||||
|
|
||||||
for (final tx in transactionsList) {
|
var transactionsList = await rpcClient?.getTransactionsList(
|
||||||
var senderAddress = (tx.transaction as ParsedTransaction).message.accountKeys[0].pubkey;
|
(await _getKeyPair()).publicKey,
|
||||||
var receiverAddress = (tx.transaction as ParsedTransaction).message.accountKeys[1].pubkey;
|
encoding: Encoding.jsonParsed);
|
||||||
|
var txsList =
|
||||||
|
List<Tuple2<isar.Transaction, Address>>.empty(growable: true);
|
||||||
|
|
||||||
|
// TODO [prio=low]: Revisit null assertion below.
|
||||||
|
|
||||||
|
for (final tx in transactionsList!) {
|
||||||
|
var senderAddress =
|
||||||
|
(tx.transaction as ParsedTransaction).message.accountKeys[0].pubkey;
|
||||||
|
var receiverAddress =
|
||||||
|
(tx.transaction as ParsedTransaction).message.accountKeys[1].pubkey;
|
||||||
var txType = isar.TransactionType.unknown;
|
var txType = isar.TransactionType.unknown;
|
||||||
var txAmount = Amount(
|
var txAmount = Amount(
|
||||||
rawValue: BigInt.from(tx.meta!.postBalances[1] - tx.meta!.preBalances[1]),
|
rawValue:
|
||||||
|
BigInt.from(tx.meta!.postBalances[1] - tx.meta!.preBalances[1]),
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,
|
fractionDigits: cryptoCurrency.fractionDigits,
|
||||||
);
|
);
|
||||||
|
|
||||||
if ((senderAddress == (await _getKeyPair()).address) && (receiverAddress == (await _getKeyPair()).address) ){
|
if ((senderAddress == (await _getKeyPair()).address) &&
|
||||||
|
(receiverAddress == (await _getKeyPair()).address)) {
|
||||||
txType = isar.TransactionType.sentToSelf;
|
txType = isar.TransactionType.sentToSelf;
|
||||||
} else if (senderAddress == (await _getKeyPair()).address) {
|
} else if (senderAddress == (await _getKeyPair()).address) {
|
||||||
txType = isar.TransactionType.outgoing;
|
txType = isar.TransactionType.outgoing;
|
||||||
|
@ -339,8 +382,9 @@ class SolanaWallet extends Bip39Wallet<Solana> {
|
||||||
derivationIndex: 0,
|
derivationIndex: 0,
|
||||||
derivationPath: null,
|
derivationPath: null,
|
||||||
type: AddressType.solana,
|
type: AddressType.solana,
|
||||||
subType: txType == isar.TransactionType.outgoing ? AddressSubType.unknown : AddressSubType.receiving
|
subType: txType == isar.TransactionType.outgoing
|
||||||
);
|
? AddressSubType.unknown
|
||||||
|
: AddressSubType.receiving);
|
||||||
|
|
||||||
txsList.add(Tuple2(transaction, txAddress));
|
txsList.add(Tuple2(transaction, txAddress));
|
||||||
}
|
}
|
||||||
|
@ -359,4 +403,25 @@ class SolanaWallet extends Bip39Wallet<Solana> {
|
||||||
// No UTXOs in Solana
|
// No UTXOs in Solana
|
||||||
return Future.value(false);
|
return Future.value(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check that the ElectrumXClient is active and usable by a Solana RpcClient.
|
||||||
|
Future<void> _checkClients() async {
|
||||||
|
if (prefs.useTor) {
|
||||||
|
electrumXClient ??= ElectrumXClient(
|
||||||
|
host: getCurrentNode().host,
|
||||||
|
port: getCurrentNode().port,
|
||||||
|
useSSL: getCurrentNode().useSSL,
|
||||||
|
failovers: [],
|
||||||
|
prefs: prefs,
|
||||||
|
coin: Coin.solana,
|
||||||
|
);
|
||||||
|
int torPort = electrumXClient?.rpcClient?.proxyInfo?.port ??
|
||||||
|
TorService.sharedInstance.getProxyInfo().port;
|
||||||
|
rpcClient = RpcClient("${InternetAddress.loopbackIPv4}:$torPort");
|
||||||
|
} else {
|
||||||
|
rpcClient =
|
||||||
|
RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue