mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-03 09:19:22 +00:00
make cardano aware of tor node settings
This commit is contained in:
parent
4197ff40f4
commit
7b603cd7f9
2 changed files with 143 additions and 53 deletions
|
@ -1 +1,8 @@
|
||||||
class NodeTorMismatchConfigException implements Exception {}
|
class NodeTorMismatchConfigException implements Exception {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
NodeTorMismatchConfigException({required this.message});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => message;
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:blockchain_utils/bip/bip/bip44/base/bip44_base.dart';
|
import 'package:blockchain_utils/bip/bip/bip44/base/bip44_base.dart';
|
||||||
|
@ -7,19 +6,21 @@ import 'package:blockchain_utils/bip/cardano/cip1852/cip1852.dart';
|
||||||
import 'package:blockchain_utils/bip/cardano/cip1852/conf/cip1852_coins.dart';
|
import 'package:blockchain_utils/bip/cardano/cip1852/conf/cip1852_coins.dart';
|
||||||
import 'package:blockchain_utils/bip/cardano/mnemonic/cardano_icarus_seed_generator.dart';
|
import 'package:blockchain_utils/bip/cardano/mnemonic/cardano_icarus_seed_generator.dart';
|
||||||
import 'package:blockchain_utils/bip/cardano/shelley/cardano_shelley.dart';
|
import 'package:blockchain_utils/bip/cardano/shelley/cardano_shelley.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
import 'package:on_chain/ada/ada.dart';
|
import 'package:on_chain/ada/ada.dart';
|
||||||
import 'package:socks5_proxy/socks.dart';
|
import 'package:socks5_proxy/socks.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
import '../../../exceptions/wallet/node_tor_mismatch_config_exception.dart';
|
||||||
import '../../../models/balance.dart';
|
import '../../../models/balance.dart';
|
||||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
|
||||||
import '../../../models/isar/models/blockchain_data/transaction.dart' as isar;
|
import '../../../models/isar/models/blockchain_data/transaction.dart' as isar;
|
||||||
import '../../../models/paymint/fee_object_model.dart';
|
import '../../../models/paymint/fee_object_model.dart';
|
||||||
import '../../../networking/http.dart';
|
|
||||||
import '../../../services/tor_service.dart';
|
import '../../../services/tor_service.dart';
|
||||||
import '../../../utilities/amount/amount.dart';
|
import '../../../utilities/amount/amount.dart';
|
||||||
import 'package:isar/isar.dart';
|
|
||||||
import '../../../utilities/logger.dart';
|
import '../../../utilities/logger.dart';
|
||||||
import '../../../utilities/prefs.dart';
|
import '../../../utilities/prefs.dart';
|
||||||
|
import '../../../utilities/tor_plain_net_option_enum.dart';
|
||||||
import '../../api/cardano/blockfrost_http_provider.dart';
|
import '../../api/cardano/blockfrost_http_provider.dart';
|
||||||
import '../../crypto_currency/crypto_currency.dart';
|
import '../../crypto_currency/crypto_currency.dart';
|
||||||
import '../../models/tx_data.dart';
|
import '../../models/tx_data.dart';
|
||||||
|
@ -30,7 +31,6 @@ class CardanoWallet extends Bip39Wallet<Cardano> {
|
||||||
|
|
||||||
// Source: https://cips.cardano.org/cip/CIP-1852
|
// Source: https://cips.cardano.org/cip/CIP-1852
|
||||||
static const String _addressDerivationPath = "m/1852'/1815'/0'/0/0";
|
static const String _addressDerivationPath = "m/1852'/1815'/0'/0/0";
|
||||||
static final HTTP _httpClient = HTTP();
|
|
||||||
|
|
||||||
BlockforestProvider? blockfrostProvider;
|
BlockforestProvider? blockfrostProvider;
|
||||||
|
|
||||||
|
@ -184,41 +184,59 @@ class CardanoWallet extends Bip39Wallet<Cardano> {
|
||||||
totalBalance += BigInt.parse(utxo.amount.first.quantity);
|
totalBalance += BigInt.parse(utxo.amount.first.quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (leftAmountForUtxos > BigInt.parse("0") || totalBalance < txData.amount!.raw) {
|
if (leftAmountForUtxos > BigInt.parse("0") ||
|
||||||
|
totalBalance < txData.amount!.raw) {
|
||||||
throw Exception("Insufficient balance");
|
throw Exception("Insufficient balance");
|
||||||
}
|
}
|
||||||
|
|
||||||
final bip32 = CardanoIcarusBip32.fromSeed(CardanoIcarusSeedGenerator(await getMnemonic()).generate());
|
final bip32 = CardanoIcarusBip32.fromSeed(
|
||||||
|
CardanoIcarusSeedGenerator(await getMnemonic()).generate());
|
||||||
final spend = bip32.derivePath("1852'/1815'/0'/0/0");
|
final spend = bip32.derivePath("1852'/1815'/0'/0/0");
|
||||||
final privateKey = AdaPrivateKey.fromBytes(spend.privateKey.raw);
|
final privateKey = AdaPrivateKey.fromBytes(spend.privateKey.raw);
|
||||||
|
|
||||||
// Calculate fees with example tx
|
// Calculate fees with example tx
|
||||||
final exampleFee = ADAHelper.toLovelaces("0.10");
|
final exampleFee = ADAHelper.toLovelaces("0.10");
|
||||||
final change = TransactionOutput(address: ADABaseAddress((await getCurrentReceivingAddress())!.value), amount: Value(coin: totalBalance - (txData.amount!.raw)));
|
final change = TransactionOutput(
|
||||||
|
address: ADABaseAddress((await getCurrentReceivingAddress())!.value),
|
||||||
|
amount: Value(coin: totalBalance - (txData.amount!.raw)));
|
||||||
final body = TransactionBody(
|
final body = TransactionBody(
|
||||||
inputs: listOfUtxosToBeUsed.map((e) => TransactionInput(transactionId: TransactionHash.fromHex(e.txHash), index: e.outputIndex)).toList(),
|
inputs: listOfUtxosToBeUsed
|
||||||
outputs: [change, TransactionOutput(address: ADABaseAddress(txData.recipients!.first.address), amount: Value(coin: txData.amount!.raw - exampleFee))],
|
.map((e) => TransactionInput(
|
||||||
|
transactionId: TransactionHash.fromHex(e.txHash),
|
||||||
|
index: e.outputIndex))
|
||||||
|
.toList(),
|
||||||
|
outputs: [
|
||||||
|
change,
|
||||||
|
TransactionOutput(
|
||||||
|
address: ADABaseAddress(txData.recipients!.first.address),
|
||||||
|
amount: Value(coin: txData.amount!.raw - exampleFee))
|
||||||
|
],
|
||||||
fee: exampleFee,
|
fee: exampleFee,
|
||||||
);
|
);
|
||||||
final exampleTx = ADATransaction(
|
final exampleTx = ADATransaction(
|
||||||
body: body,
|
body: body,
|
||||||
witnessSet: TransactionWitnessSet(vKeys: [
|
witnessSet: TransactionWitnessSet(
|
||||||
|
vKeys: [
|
||||||
privateKey.createSignatureWitness(body.toHash().data),
|
privateKey.createSignatureWitness(body.toHash().data),
|
||||||
],)
|
],
|
||||||
,);
|
),
|
||||||
final params = await blockfrostProvider!.request(BlockfrostRequestLatestEpochProtocolParameters());
|
);
|
||||||
|
final params = await blockfrostProvider!
|
||||||
|
.request(BlockfrostRequestLatestEpochProtocolParameters());
|
||||||
final fee = params.calculateFee(exampleTx.size);
|
final fee = params.calculateFee(exampleTx.size);
|
||||||
|
|
||||||
// Check if we are sending all balance, which means no change and only one output for recipient.
|
// Check if we are sending all balance, which means no change and only one output for recipient.
|
||||||
if (totalBalance == txData.amount!.raw) {
|
if (totalBalance == txData.amount!.raw) {
|
||||||
final List<TxRecipient> newRecipients = [(
|
final List<TxRecipient> newRecipients = [
|
||||||
|
(
|
||||||
address: txData.recipients!.first.address,
|
address: txData.recipients!.first.address,
|
||||||
amount: Amount(
|
amount: Amount(
|
||||||
rawValue: txData.amount!.raw - fee,
|
rawValue: txData.amount!.raw - fee,
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,
|
fractionDigits: cryptoCurrency.fractionDigits,
|
||||||
),
|
),
|
||||||
isChange: txData.recipients!.first.isChange,
|
isChange: txData.recipients!.first.isChange,
|
||||||
),];
|
),
|
||||||
|
];
|
||||||
return txData.copyWith(
|
return txData.copyWith(
|
||||||
fee: Amount(
|
fee: Amount(
|
||||||
rawValue: fee,
|
rawValue: fee,
|
||||||
|
@ -232,8 +250,10 @@ class CardanoWallet extends Bip39Wallet<Cardano> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minimum change in Cardano is 1 ADA and we need to have enough balance for that
|
// Minimum change in Cardano is 1 ADA and we need to have enough balance for that
|
||||||
if (totalBalance - (txData.amount!.raw + fee) < ADAHelper.toLovelaces("1")) {
|
if (totalBalance - (txData.amount!.raw + fee) <
|
||||||
throw Exception("Not enough balance for change. By network rules, please either send all balance or leave at least 1 ADA change.");
|
ADAHelper.toLovelaces("1")) {
|
||||||
|
throw Exception(
|
||||||
|
"Not enough balance for change. By network rules, please either send all balance or leave at least 1 ADA change.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return txData.copyWith(
|
return txData.copyWith(
|
||||||
|
@ -266,7 +286,6 @@ class CardanoWallet extends Bip39Wallet<Cardano> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
var leftAmountForUtxos = txData.amount!.raw + txData.fee!.raw;
|
var leftAmountForUtxos = txData.amount!.raw + txData.fee!.raw;
|
||||||
final listOfUtxosToBeUsed = <ADAAccountUTXOResponse>[];
|
final listOfUtxosToBeUsed = <ADAAccountUTXOResponse>[];
|
||||||
var totalBalance = BigInt.zero;
|
var totalBalance = BigInt.zero;
|
||||||
|
@ -285,31 +304,53 @@ class CardanoWallet extends Bip39Wallet<Cardano> {
|
||||||
totalUtxoAmount += BigInt.parse(utxo.amount.first.quantity);
|
totalUtxoAmount += BigInt.parse(utxo.amount.first.quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
final bip32 = CardanoIcarusBip32.fromSeed(CardanoIcarusSeedGenerator(await getMnemonic()).generate());
|
final bip32 = CardanoIcarusBip32.fromSeed(
|
||||||
|
CardanoIcarusSeedGenerator(await getMnemonic()).generate());
|
||||||
final spend = bip32.derivePath("1852'/1815'/0'/0/0");
|
final spend = bip32.derivePath("1852'/1815'/0'/0/0");
|
||||||
final privateKey = AdaPrivateKey.fromBytes(spend.privateKey.raw);
|
final privateKey = AdaPrivateKey.fromBytes(spend.privateKey.raw);
|
||||||
|
|
||||||
final change = TransactionOutput(address: ADABaseAddress((await getCurrentReceivingAddress())!.value), amount: Value(coin: totalUtxoAmount - (txData.amount!.raw + txData.fee!.raw)));
|
final change = TransactionOutput(
|
||||||
|
address: ADABaseAddress((await getCurrentReceivingAddress())!.value),
|
||||||
|
amount: Value(
|
||||||
|
coin: totalUtxoAmount - (txData.amount!.raw + txData.fee!.raw)));
|
||||||
List<TransactionOutput> outputs = [];
|
List<TransactionOutput> outputs = [];
|
||||||
if (totalBalance == (txData.amount!.raw + txData.fee!.raw)) {
|
if (totalBalance == (txData.amount!.raw + txData.fee!.raw)) {
|
||||||
outputs = [TransactionOutput(address: ADABaseAddress(txData.recipients!.first.address), amount: Value(coin: txData.amount!.raw))];
|
outputs = [
|
||||||
|
TransactionOutput(
|
||||||
|
address: ADABaseAddress(txData.recipients!.first.address),
|
||||||
|
amount: Value(coin: txData.amount!.raw))
|
||||||
|
];
|
||||||
} else {
|
} else {
|
||||||
outputs = [change, TransactionOutput(address: ADABaseAddress(txData.recipients!.first.address), amount: Value(coin: txData.amount!.raw))];
|
outputs = [
|
||||||
|
change,
|
||||||
|
TransactionOutput(
|
||||||
|
address: ADABaseAddress(txData.recipients!.first.address),
|
||||||
|
amount: Value(coin: txData.amount!.raw))
|
||||||
|
];
|
||||||
}
|
}
|
||||||
final body = TransactionBody(
|
final body = TransactionBody(
|
||||||
inputs: listOfUtxosToBeUsed.map((e) => TransactionInput(transactionId: TransactionHash.fromHex(e.txHash), index: e.outputIndex)).toList(),
|
inputs: listOfUtxosToBeUsed
|
||||||
|
.map((e) => TransactionInput(
|
||||||
|
transactionId: TransactionHash.fromHex(e.txHash),
|
||||||
|
index: e.outputIndex))
|
||||||
|
.toList(),
|
||||||
outputs: outputs,
|
outputs: outputs,
|
||||||
fee: txData.fee!.raw,
|
fee: txData.fee!.raw,
|
||||||
);
|
);
|
||||||
final tx = ADATransaction(
|
final tx = ADATransaction(
|
||||||
body: body,
|
body: body,
|
||||||
witnessSet: TransactionWitnessSet(vKeys: [
|
witnessSet: TransactionWitnessSet(
|
||||||
|
vKeys: [
|
||||||
privateKey.createSignatureWitness(body.toHash().data),
|
privateKey.createSignatureWitness(body.toHash().data),
|
||||||
],)
|
],
|
||||||
,);
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final sentTx = await blockfrostProvider!.request(BlockfrostRequestSubmitTransaction(
|
final sentTx = await blockfrostProvider!.request(
|
||||||
transactionCborBytes: tx.serialize(),),);
|
BlockfrostRequestSubmitTransaction(
|
||||||
|
transactionCborBytes: tx.serialize(),
|
||||||
|
),
|
||||||
|
);
|
||||||
return txData.copyWith(
|
return txData.copyWith(
|
||||||
txid: sentTx,
|
txid: sentTx,
|
||||||
);
|
);
|
||||||
|
@ -366,10 +407,12 @@ class CardanoWallet extends Bip39Wallet<Cardano> {
|
||||||
final balance = Balance(
|
final balance = Balance(
|
||||||
total: Amount(
|
total: Amount(
|
||||||
rawValue: totalBalanceInLovelace,
|
rawValue: totalBalanceInLovelace,
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,),
|
fractionDigits: cryptoCurrency.fractionDigits,
|
||||||
|
),
|
||||||
spendable: Amount(
|
spendable: Amount(
|
||||||
rawValue: totalBalanceInLovelace,
|
rawValue: totalBalanceInLovelace,
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,),
|
fractionDigits: cryptoCurrency.fractionDigits,
|
||||||
|
),
|
||||||
blockedTotal: Amount(
|
blockedTotal: Amount(
|
||||||
rawValue: BigInt.zero,
|
rawValue: BigInt.zero,
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,
|
fractionDigits: cryptoCurrency.fractionDigits,
|
||||||
|
@ -400,7 +443,8 @@ class CardanoWallet extends Bip39Wallet<Cardano> {
|
||||||
|
|
||||||
await info.updateCachedChainHeight(
|
await info.updateCachedChainHeight(
|
||||||
newHeight: latestBlock.height == null ? 0 : latestBlock.height!,
|
newHeight: latestBlock.height == null ? 0 : latestBlock.height!,
|
||||||
isar: mainDB.isar,);
|
isar: mainDB.isar,
|
||||||
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
"Error updating transactions in cardano_wallet.dart: $e\n$s",
|
"Error updating transactions in cardano_wallet.dart: $e\n$s",
|
||||||
|
@ -473,13 +517,15 @@ class CardanoWallet extends Bip39Wallet<Cardano> {
|
||||||
if (txType == isar.TransactionType.incoming) {
|
if (txType == isar.TransactionType.incoming) {
|
||||||
receiverAddr = currentAddr;
|
receiverAddr = currentAddr;
|
||||||
for (final output in utxoInfo.outputs) {
|
for (final output in utxoInfo.outputs) {
|
||||||
if (output.address == currentAddr && output.amount.first.unit == "lovelace") {
|
if (output.address == currentAddr &&
|
||||||
|
output.amount.first.unit == "lovelace") {
|
||||||
amount += int.parse(output.amount.first.quantity);
|
amount += int.parse(output.amount.first.quantity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (txType == isar.TransactionType.outgoing) {
|
} else if (txType == isar.TransactionType.outgoing) {
|
||||||
for (final output in utxoInfo.outputs) {
|
for (final output in utxoInfo.outputs) {
|
||||||
if (output.address != currentAddr && output.amount.first.unit == "lovelace") {
|
if (output.address != currentAddr &&
|
||||||
|
output.amount.first.unit == "lovelace") {
|
||||||
receiverAddr = output.address;
|
receiverAddr = output.address;
|
||||||
amount += int.parse(output.amount.first.quantity);
|
amount += int.parse(output.amount.first.quantity);
|
||||||
}
|
}
|
||||||
|
@ -532,6 +578,8 @@ class CardanoWallet extends Bip39Wallet<Cardano> {
|
||||||
}
|
}
|
||||||
|
|
||||||
await mainDB.addNewTransactionData(parsedTxsList, walletId);
|
await mainDB.addNewTransactionData(parsedTxsList, walletId);
|
||||||
|
} on NodeTorMismatchConfigException {
|
||||||
|
rethrow;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
"Error updating transactions in cardano_wallet.dart: $e\n$s",
|
"Error updating transactions in cardano_wallet.dart: $e\n$s",
|
||||||
|
@ -548,6 +596,7 @@ class CardanoWallet extends Bip39Wallet<Cardano> {
|
||||||
|
|
||||||
Future<void> updateProvider() async {
|
Future<void> updateProvider() async {
|
||||||
final currentNode = getCurrentNode();
|
final currentNode = getCurrentNode();
|
||||||
|
|
||||||
final client = HttpClient();
|
final client = HttpClient();
|
||||||
if (prefs.useTor) {
|
if (prefs.useTor) {
|
||||||
final proxyInfo = TorService.sharedInstance.getProxyInfo();
|
final proxyInfo = TorService.sharedInstance.getProxyInfo();
|
||||||
|
@ -557,11 +606,45 @@ class CardanoWallet extends Bip39Wallet<Cardano> {
|
||||||
);
|
);
|
||||||
SocksTCPClient.assignToHttpClient(client, [proxySettings]);
|
SocksTCPClient.assignToHttpClient(client, [proxySettings]);
|
||||||
}
|
}
|
||||||
blockfrostProvider = BlockforestProvider(
|
blockfrostProvider = CustomBlockForestProvider(
|
||||||
BlockfrostHttpProvider(
|
BlockfrostHttpProvider(
|
||||||
url: "${currentNode.host}:${currentNode.port}/",
|
url: "${currentNode.host}:${currentNode.port}/",
|
||||||
client: client,
|
client: client,
|
||||||
),
|
),
|
||||||
|
prefs,
|
||||||
|
TorPlainNetworkOption.fromNodeData(
|
||||||
|
currentNode.torEnabled,
|
||||||
|
currentNode.clearnetEnabled,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CustomBlockForestProvider extends BlockforestProvider {
|
||||||
|
CustomBlockForestProvider(super.rpc, this.prefs, this.netOption);
|
||||||
|
|
||||||
|
final Prefs prefs;
|
||||||
|
final TorPlainNetworkOption netOption;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T> request<T, E>(
|
||||||
|
BlockforestRequestParam<T, E> request, [
|
||||||
|
Duration? timeout,
|
||||||
|
]) async {
|
||||||
|
if (prefs.useTor) {
|
||||||
|
if (netOption == TorPlainNetworkOption.clear) {
|
||||||
|
throw NodeTorMismatchConfigException(
|
||||||
|
message: "TOR enabled but node set to clearnet only",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (netOption == TorPlainNetworkOption.tor) {
|
||||||
|
throw NodeTorMismatchConfigException(
|
||||||
|
message: "TOR off but node set to TOR only",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.request(request, timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue