Merge remote-tracking branch 'detherminal/staging' into cardano

This commit is contained in:
sneurlax 2024-09-23 02:05:22 -05:00
commit 98d7ce3a8e
13 changed files with 854 additions and 29 deletions

View file

@ -174,7 +174,8 @@ enum AddressType {
tezos,
frostMS,
p2tr,
solana;
solana,
cardanoShelley;
String get readableName {
switch (this) {
@ -210,6 +211,8 @@ enum AddressType {
return "Solana";
case AddressType.p2tr:
return "P2TR (taproot)";
case AddressType.cardanoShelley:
return "Cardano Shelley";
}
}
}

View file

@ -12,6 +12,8 @@ import 'dart:async';
import 'dart:convert';
import 'package:bip39/bip39.dart' as bip39;
import 'package:blockchain_utils/bip/bip/bip39/bip39_mnemonic.dart';
import 'package:blockchain_utils/bip/bip/bip39/bip39_mnemonic_generator.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
@ -560,6 +562,8 @@ class _NewWalletRecoveryPhraseWarningViewState
wordCount = info
.coin.defaultSeedPhraseLength;
// TODO: Refactor these to generate each coin in their respective classes
// This code should not be in a random view page file
if (coin is Monero ||
coin is Wownero) {
// currently a special case due to the

View file

@ -30,6 +30,7 @@ class PriceAPI {
BitcoinFrost: "bitcoin",
Litecoin: "litecoin",
Bitcoincash: "bitcoin-cash",
Cardano: "cardano",
Dash: "dash",
Dogecoin: "dogecoin",
Epiccash: "epic-cash",

View file

@ -18,7 +18,8 @@ enum DerivePathType {
eth,
eCash44,
solana,
bip86;
bip86,
cardanoShelley;
AddressType getAddressType() {
switch (this) {
@ -41,6 +42,9 @@ enum DerivePathType {
case DerivePathType.bip86:
return AddressType.p2tr;
case DerivePathType.cardanoShelley:
return AddressType.cardanoShelley;
}
}
}

View file

@ -4,11 +4,15 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:on_chain/ada/ada.dart';
import 'package:on_chain/ada/src/provider/provider/provider.dart';
import 'package:socks5_proxy/socks.dart';
import '../networking/http.dart';
import '../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
import '../providers/global/prefs_provider.dart';
import '../services/tor_service.dart';
import '../wallets/api/cardano/blockfrost_http_provider.dart';
import '../wallets/api/tezos/tezos_rpc_api.dart';
import '../wallets/crypto_currency/crypto_currency.dart';
import '../wallets/crypto_currency/interfaces/electrumx_currency_interface.dart';
@ -107,7 +111,9 @@ Future<bool> testNodeConnection({
case CryptonoteCurrency():
try {
final proxyInfo = ref.read(prefsChangeNotifierProvider).useTor
final proxyInfo = ref
.read(prefsChangeNotifierProvider)
.useTor
? ref.read(pTorService).getProxyInfo()
: null;
@ -180,7 +186,7 @@ Future<bool> testNodeConnection({
case Stellar():
try {
testPassed =
await testStellarNodeConnection(formData.host!, formData.port!);
await testStellarNodeConnection(formData.host!, formData.port!);
} catch (_) {}
break;
@ -196,7 +202,9 @@ Future<bool> testNodeConnection({
"action": "version",
},
),
proxyInfo: ref.read(prefsChangeNotifierProvider).useTor
proxyInfo: ref
.read(prefsChangeNotifierProvider)
.useTor
? ref.read(pTorService).getProxyInfo()
: null,
);
@ -233,6 +241,41 @@ Future<bool> testNodeConnection({
testPassed = false;
}
break;
case Cardano():
try {
final client = HttpClient();
if (ref
.read(prefsChangeNotifierProvider)
.useTor) {
final proxyInfo = TorService.sharedInstance.getProxyInfo();
final proxySettings = ProxySettings(
proxyInfo.host,
proxyInfo.port,
);
SocksTCPClient.assignToHttpClient(client, [proxySettings]);
}
final blockfrostProvider = BlockforestProvider(
BlockfrostHttpProvider(
url: "${formData.host!}:${formData.port!}/api/v0",
client: client,
),
);
final health = await blockfrostProvider.request(
BlockfrostRequestBackendHealthStatus(),
);
Logging.instance.log(
"Cardano testNodeConnection \"health=$health\"",
level: LogLevel.Info,
);
return health;
} catch (_) {
testPassed = false;
}
break;
}
return testPassed;

View file

@ -0,0 +1,53 @@
import 'dart:convert';
import 'dart:io';
import 'package:cbor/simple.dart';
import 'package:on_chain/ada/src/provider/blockfrost/core/core.dart';
import 'package:on_chain/ada/src/provider/service/service.dart';
import '../../../utilities/logger.dart';
class BlockfrostHttpProvider implements BlockfrostServiceProvider {
BlockfrostHttpProvider({
required this.url,
this.version = "v0",
this.projectId,
HttpClient? client,
this.defaultRequestTimeout = const Duration(seconds: 30),
}) : client = client ?? HttpClient();
@override
final String url;
final String version;
final String? projectId;
final HttpClient client;
final Duration defaultRequestTimeout;
@override
Future<dynamic> get(BlockforestRequestDetails params,
[Duration? timeout,]) async {
final response = await client.getUrl(Uri.parse(params.url(url, "api/$version"))).timeout(timeout ?? defaultRequestTimeout);
response.headers.add("Content-Type", "application/json");
response.headers.add("Accept", "application/json");
if (projectId != null) {
response.headers.add("project_id", projectId!);
}
final responseStream = await response.close();
final data = json.decode(await responseStream.transform(utf8.decoder).join());
return data;
}
@override
Future<dynamic> post(BlockforestRequestDetails params,
[Duration? timeout,]) async {
final request = await client.postUrl(Uri.parse(params.url(url, "api/$version"))).timeout(timeout ?? defaultRequestTimeout);
// Need to change this for other operations than submitting transactions
request.headers.add("Content-Type", "application/cbor");
request.headers.add("Accept", "application/json");
if (projectId != null) {
request.headers.add("project_id", projectId!);
}
request.add(params.body as List<int>);
final response = await request.close();
final data = json.decode(await response.transform(utf8.decoder).join());
return data;
}
}

View file

@ -0,0 +1,126 @@
import '../../../models/isar/models/blockchain_data/address.dart';
import '../../../models/node_model.dart';
import '../../../utilities/default_nodes.dart';
import '../../../utilities/enums/derive_path_type_enum.dart';
import '../crypto_currency.dart';
import '../intermediate/bip39_currency.dart';
class Cardano extends Bip39Currency {
Cardano(super.network) {
_idMain = "cardano";
_uriScheme = "cardano";
switch (network) {
case CryptoCurrencyNetwork.main:
_id = _idMain;
_name = "Cardano";
_ticker = "ADA";
default:
throw Exception("Unsupported network: $network");
}
}
late final String _id;
@override
String get identifier => _id;
late final String _idMain;
@override
String get mainNetId => _idMain;
late final String _name;
@override
String get prettyName => _name;
late final String _uriScheme;
@override
String get uriScheme => _uriScheme;
late final String _ticker;
@override
String get ticker => _ticker;
@override
AddressType get defaultAddressType => AddressType.cardanoShelley;
@override
Uri defaultBlockExplorer(String txid) {
switch (network) {
case CryptoCurrencyNetwork.main:
return Uri.parse(
"https://explorer.cardano.org/en/transaction?id=$txid");
default:
throw Exception(
"Unsupported network for defaultBlockExplorer(): $network",
);
}
}
@override
DerivePathType get defaultDerivePathType => DerivePathType.cardanoShelley;
@override
NodeModel get defaultNode {
switch (network) {
case CryptoCurrencyNetwork.main:
return NodeModel(
host: "https://cardano.stackwallet.com",
port: 443,
name: DefaultNodes.defaultName,
id: DefaultNodes.buildId(this),
useSSL: true,
enabled: true,
coinName: identifier,
isFailover: true,
isDown: false,
);
default:
throw Exception("Unsupported network: $network");
}
}
@override
int get defaultSeedPhraseLength => 15;
@override
int get fractionDigits => 6;
@override
String get genesisHash => "f0f7892b5c333cffc4b3c4344de48af4cc63f55e44936196f365a9ef2244134f";
@override
bool get hasBuySupport => false;
@override
bool get hasMnemonicPassphraseSupport => false;
@override
int get minConfirms => 2;
@override
List<int> get possibleMnemonicLengths => [defaultSeedPhraseLength];
@override
BigInt get satsPerCoin => BigInt.from(1000000);
@override
int get targetBlockTimeSeconds => 20;
@override
bool get torSupport => true;
@override
bool validateAddress(String address) {
switch (network) {
case CryptoCurrencyNetwork.main:
return RegExp(r"^addr1[0-9a-zA-Z]{98}$").hasMatch(address);
default:
throw Exception("Unsupported network: $network");
}
}
}

View file

@ -6,6 +6,7 @@ export 'coins/banano.dart';
export 'coins/bitcoin.dart';
export 'coins/bitcoin_frost.dart';
export 'coins/bitcoincash.dart';
export 'coins/cardano.dart';
export 'coins/dash.dart';
export 'coins/dogecoin.dart';
export 'coins/ecash.dart';

View file

@ -0,0 +1,567 @@
import 'dart:convert';
import 'dart:io';
import 'package:blockchain_utils/bip/bip/bip44/base/bip44_base.dart';
import 'package:blockchain_utils/bip/cardano/bip32/cardano_icarus_bip32.dart';
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/mnemonic/cardano_icarus_seed_generator.dart';
import 'package:blockchain_utils/bip/cardano/shelley/cardano_shelley.dart';
import 'package:on_chain/ada/ada.dart';
import 'package:socks5_proxy/socks.dart';
import '../../../models/balance.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/paymint/fee_object_model.dart';
import '../../../networking/http.dart';
import '../../../services/tor_service.dart';
import '../../../utilities/amount/amount.dart';
import 'package:isar/isar.dart';
import '../../../utilities/logger.dart';
import '../../../utilities/prefs.dart';
import '../../api/cardano/blockfrost_http_provider.dart';
import '../../crypto_currency/crypto_currency.dart';
import '../../models/tx_data.dart';
import '../intermediate/bip39_wallet.dart';
class CardanoWallet extends Bip39Wallet<Cardano> {
CardanoWallet(CryptoCurrencyNetwork network) : super(Cardano(network));
// Source: https://cips.cardano.org/cip/CIP-1852
static const String _addressDerivationPath = "m/1852'/1815'/0'/0/0";
static final HTTP _httpClient = HTTP();
BlockforestProvider? blockfrostProvider;
@override
FilterOperation? get changeAddressFilterOperation => null;
@override
FilterOperation? get receivingAddressFilterOperation => null;
Future<Address> _getAddress() async {
final mnemonic = await getMnemonic();
final seed = CardanoIcarusSeedGenerator(mnemonic).generate();
final cip1852 = Cip1852.fromSeed(seed, Cip1852Coins.cardanoIcarus);
final derivationAccount = cip1852.purpose.coin.account(0);
final shelley = CardanoShelley.fromCip1852Object(derivationAccount)
.change(Bip44Changes.chainExt)
.addressIndex(0);
final paymentPublicKey = shelley.bip44.publicKey.compressed;
final stakePublicKey = shelley.bip44Sk.publicKey.compressed;
final addressStr = ADABaseAddress.fromPublicKey(
basePubkeyBytes: paymentPublicKey,
stakePubkeyBytes: stakePublicKey,
).address;
return Address(
walletId: walletId,
value: addressStr,
publicKey: paymentPublicKey,
derivationIndex: 0,
derivationPath: DerivationPath()..value = _addressDerivationPath,
type: AddressType.cardanoShelley,
subType: AddressSubType.receiving,
);
}
@override
Future<void> checkSaveInitialReceivingAddress() async {
try {
final Address? address = await getCurrentReceivingAddress();
if (address == null) {
final address = await _getAddress();
await mainDB.updateOrPutAddresses([address]);
}
} catch (e, s) {
Logging.instance.log(
"$runtimeType checkSaveInitialReceivingAddress() failed: $e\n$s",
level: LogLevel.Error,
);
}
}
@override
Future<bool> pingCheck() async {
try {
await updateProvider();
final health = await blockfrostProvider!.request(
BlockfrostRequestBackendHealthStatus(),
);
return Future.value(health);
} catch (e, s) {
Logging.instance.log(
"Error ping checking in cardano_wallet.dart: $e\n$s",
level: LogLevel.Error,
);
return Future.value(false);
}
}
@override
Future<Amount> estimateFeeFor(Amount amount, int feeRate) async {
await updateProvider();
if (info.cachedBalance.spendable.raw == BigInt.zero) {
return Amount(
rawValue: BigInt.zero,
fractionDigits: cryptoCurrency.fractionDigits,
);
}
final params = await blockfrostProvider!.request(
BlockfrostRequestLatestEpochProtocolParameters(),
);
final fee = params.calculateFee(284);
return Amount(
rawValue: fee,
fractionDigits: cryptoCurrency.fractionDigits,
);
}
@override
Future<FeeObject> get fees async {
try {
await updateProvider();
final params = await blockfrostProvider!.request(
BlockfrostRequestLatestEpochProtocolParameters(),
);
// 284 is the size of a basic transaction with one input and two outputs (change and recipient)
final fee = params.calculateFee(284).toInt();
return FeeObject(
numberOfBlocksFast: 2,
numberOfBlocksAverage: 2,
numberOfBlocksSlow: 2,
fast: fee,
medium: fee,
slow: fee,
);
} catch (e, s) {
Logging.instance.log(
"Error getting fees in cardano_wallet.dart: $e\n$s",
level: LogLevel.Error,
);
rethrow;
}
}
@override
Future<TxData> prepareSend({required TxData txData}) async {
try {
await updateProvider();
if (txData.amount!.raw < ADAHelper.toLovelaces("1")) {
throw Exception("By network rules, you can send minimum 1 ADA");
}
final utxos = await blockfrostProvider!.request(
BlockfrostRequestAddressUTXOsOfAGivenAsset(
address: ADAAddress.fromAddress(
(await getCurrentReceivingAddress())!.value,
),
asset: "lovelace",
),
);
var leftAmountForUtxos = txData.amount!.raw;
final listOfUtxosToBeUsed = <ADAAccountUTXOResponse>[];
var totalBalance = BigInt.zero;
for (final utxo in utxos) {
if (!(leftAmountForUtxos <= BigInt.parse("0"))) {
leftAmountForUtxos -= BigInt.parse(utxo.amount.first.quantity);
listOfUtxosToBeUsed.add(utxo);
}
totalBalance += BigInt.parse(utxo.amount.first.quantity);
}
if (leftAmountForUtxos > BigInt.parse("0") || totalBalance < txData.amount!.raw) {
throw Exception("Insufficient balance");
}
final bip32 = CardanoIcarusBip32.fromSeed(CardanoIcarusSeedGenerator(await getMnemonic()).generate());
final spend = bip32.derivePath("1852'/1815'/0'/0/0");
final privateKey = AdaPrivateKey.fromBytes(spend.privateKey.raw);
// Calculate fees with example tx
final exampleFee = ADAHelper.toLovelaces("0.10");
final change = TransactionOutput(address: ADABaseAddress((await getCurrentReceivingAddress())!.value), amount: Value(coin: totalBalance - (txData.amount!.raw)));
final body = TransactionBody(
inputs: listOfUtxosToBeUsed.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,
);
final exampleTx = ADATransaction(
body: body,
witnessSet: TransactionWitnessSet(vKeys: [
privateKey.createSignatureWitness(body.toHash().data),
],)
,);
final params = await blockfrostProvider!.request(BlockfrostRequestLatestEpochProtocolParameters());
final fee = params.calculateFee(exampleTx.size);
// Check if we are sending all balance, which means no change and only one output for recipient.
if (totalBalance == txData.amount!.raw) {
final List<TxRecipient> newRecipients = [(
address: txData.recipients!.first.address,
amount: Amount(
rawValue: txData.amount!.raw - fee,
fractionDigits: cryptoCurrency.fractionDigits,
),
isChange: txData.recipients!.first.isChange,
),];
return txData.copyWith(
fee: Amount(
rawValue: fee,
fractionDigits: cryptoCurrency.fractionDigits,
),
recipients: newRecipients,
);
} else {
if (txData.amount!.raw + fee > totalBalance) {
throw Exception("Insufficient balance for fee");
}
// 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")) {
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(
fee: Amount(
rawValue: fee,
fractionDigits: cryptoCurrency.fractionDigits,
),
);
}
} catch (e, s) {
Logging.instance.log(
"$runtimeType Cardano prepareSend failed: $e\n$s",
level: LogLevel.Error,
);
rethrow;
}
}
@override
Future<TxData> confirmSend({required TxData txData}) async {
try {
await updateProvider();
final utxos = await blockfrostProvider!.request(
BlockfrostRequestAddressUTXOsOfAGivenAsset(
address: ADAAddress.fromAddress(
(await getCurrentReceivingAddress())!.value,
),
asset: "lovelace",
),
);
var leftAmountForUtxos = txData.amount!.raw + txData.fee!.raw;
final listOfUtxosToBeUsed = <ADAAccountUTXOResponse>[];
var totalBalance = BigInt.zero;
for (final utxo in utxos) {
if (!(leftAmountForUtxos <= BigInt.parse("0"))) {
leftAmountForUtxos -= BigInt.parse(utxo.amount.first.quantity);
listOfUtxosToBeUsed.add(utxo);
}
totalBalance += BigInt.parse(utxo.amount.first.quantity);
}
var totalUtxoAmount = BigInt.zero;
for (final utxo in listOfUtxosToBeUsed) {
totalUtxoAmount += BigInt.parse(utxo.amount.first.quantity);
}
final bip32 = CardanoIcarusBip32.fromSeed(CardanoIcarusSeedGenerator(await getMnemonic()).generate());
final spend = bip32.derivePath("1852'/1815'/0'/0/0");
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)));
List<TransactionOutput> outputs = [];
if (totalBalance == (txData.amount!.raw + txData.fee!.raw)) {
outputs = [TransactionOutput(address: ADABaseAddress(txData.recipients!.first.address), amount: Value(coin: txData.amount!.raw))];
} else {
outputs = [change, TransactionOutput(address: ADABaseAddress(txData.recipients!.first.address), amount: Value(coin: txData.amount!.raw))];
}
final body = TransactionBody(
inputs: listOfUtxosToBeUsed.map((e) => TransactionInput(transactionId: TransactionHash.fromHex(e.txHash), index: e.outputIndex)).toList(),
outputs: outputs,
fee: txData.fee!.raw,
);
final tx = ADATransaction(
body: body,
witnessSet: TransactionWitnessSet(vKeys: [
privateKey.createSignatureWitness(body.toHash().data),
],)
,);
final sentTx = await blockfrostProvider!.request(BlockfrostRequestSubmitTransaction(
transactionCborBytes: tx.serialize(),),);
return txData.copyWith(
txid: sentTx,
);
} catch (e, s) {
Logging.instance.log(
"$runtimeType Cardano confirmSend failed: $e\n$s",
level: LogLevel.Error,
);
rethrow;
}
}
@override
Future<void> recover({required bool isRescan}) async {
await refreshMutex.protect(() async {
final addressStruct = await _getAddress();
await mainDB.updateOrPutAddresses([addressStruct]);
if (info.cachedReceivingAddress != addressStruct.value) {
await info.updateReceivingAddress(
newAddress: addressStruct.value,
isar: mainDB.isar,
);
}
await Future.wait([
updateBalance(),
updateChainHeight(),
updateTransactions(),
]);
});
}
@override
Future<void> updateBalance() async {
try {
await updateProvider();
final addressUtxos = await blockfrostProvider!.request(
BlockfrostRequestAddressUTXOsOfAGivenAsset(
address: ADAAddress.fromAddress(
(await getCurrentReceivingAddress())!.value,
),
asset: "lovelace",
),
);
BigInt totalBalanceInLovelace = BigInt.parse("0");
for (final utxo in addressUtxos) {
totalBalanceInLovelace += BigInt.parse(utxo.amount.first.quantity);
}
final balance = Balance(
total: Amount(
rawValue: totalBalanceInLovelace,
fractionDigits: cryptoCurrency.fractionDigits,),
spendable: Amount(
rawValue: totalBalanceInLovelace,
fractionDigits: cryptoCurrency.fractionDigits,),
blockedTotal: Amount(
rawValue: BigInt.zero,
fractionDigits: cryptoCurrency.fractionDigits,
),
pendingSpendable: Amount(
rawValue: BigInt.zero,
fractionDigits: cryptoCurrency.fractionDigits,
),
);
await info.updateBalance(newBalance: balance, isar: mainDB.isar);
} catch (e, s) {
Logging.instance.log(
"Error getting balance in cardano_wallet.dart: $e\n$s",
level: LogLevel.Error,
);
}
}
@override
Future<void> updateChainHeight() async {
try {
await updateProvider();
final latestBlock = await blockfrostProvider!.request(
BlockfrostRequestLatestBlock(),
);
await info.updateCachedChainHeight(
newHeight: latestBlock.height == null ? 0 : latestBlock.height!,
isar: mainDB.isar,);
} catch (e, s) {
Logging.instance.log(
"Error updating transactions in cardano_wallet.dart: $e\n$s",
level: LogLevel.Error,
);
}
}
@override
Future<void> updateNode() async {
await refresh();
}
@override
Future<void> updateTransactions() async {
try {
await updateProvider();
final currentAddr = (await getCurrentReceivingAddress())!.value;
final txsList = await blockfrostProvider!.request(
BlockfrostRequestAddressTransactions(
ADAAddress.fromAddress(
currentAddr,
),
),
);
final parsedTxsList =
List<Tuple2<isar.Transaction, Address>>.empty(growable: true);
for (final tx in txsList) {
final txInfo = await blockfrostProvider!.request(
BlockfrostRequestSpecificTransaction(tx.txHash),
);
final utxoInfo = await blockfrostProvider!.request(
BlockfrostRequestTransactionUTXOs(tx.txHash),
);
var txType = isar.TransactionType.unknown;
for (final input in utxoInfo.inputs) {
if (input.address == currentAddr) {
txType = isar.TransactionType.outgoing;
}
}
if (txType == isar.TransactionType.outgoing) {
var isSelfTx = true;
for (final output in utxoInfo.outputs) {
if (output.address != currentAddr) {
isSelfTx = false;
}
}
if (isSelfTx) {
txType = isar.TransactionType.sentToSelf;
}
}
if (txType == isar.TransactionType.unknown) {
for (final output in utxoInfo.outputs) {
if (output.address == currentAddr) {
txType = isar.TransactionType.incoming;
}
}
}
var receiverAddr = "Unknown?";
var amount = 0;
if (txType == isar.TransactionType.incoming) {
receiverAddr = currentAddr;
for (final output in utxoInfo.outputs) {
if (output.address == currentAddr && output.amount.first.unit == "lovelace") {
amount += int.parse(output.amount.first.quantity);
}
}
} else if (txType == isar.TransactionType.outgoing) {
for (final output in utxoInfo.outputs) {
if (output.address != currentAddr && output.amount.first.unit == "lovelace") {
receiverAddr = output.address;
amount += int.parse(output.amount.first.quantity);
}
}
} else if (txType == isar.TransactionType.sentToSelf) {
receiverAddr = currentAddr;
for (final output in utxoInfo.outputs) {
if (output.amount.first.unit == "lovelace") {
amount += int.parse(output.amount.first.quantity);
}
}
}
final transaction = isar.Transaction(
walletId: walletId,
txid: txInfo.hash,
timestamp: tx.blockTime,
type: txType,
subType: isar.TransactionSubType.none,
amount: amount,
amountString: Amount(
rawValue: BigInt.from(amount),
fractionDigits: cryptoCurrency.fractionDigits,
).toJsonString(),
fee: int.parse(txInfo.fees),
height: txInfo.blockHeight,
isCancelled: false,
isLelantus: false,
slateId: null,
otherData: null,
inputs: [],
outputs: [],
nonce: null,
numberOfMessages: 0,
);
final txAddress = Address(
walletId: walletId,
value: receiverAddr,
publicKey: List<int>.empty(),
derivationIndex: 0,
derivationPath: DerivationPath()..value = _addressDerivationPath,
type: AddressType.cardanoShelley,
subType: txType == isar.TransactionType.outgoing
? AddressSubType.unknown
: AddressSubType.receiving,
);
parsedTxsList.add(Tuple2(transaction, txAddress));
}
await mainDB.addNewTransactionData(parsedTxsList, walletId);
} catch (e, s) {
Logging.instance.log(
"Error updating transactions in cardano_wallet.dart: $e\n$s",
level: LogLevel.Error,
);
}
}
@override
Future<bool> updateUTXOs() async {
// TODO: implement updateUTXOs
return false;
}
Future<void> updateProvider() async {
final currentNode = getCurrentNode();
final client = HttpClient();
if (prefs.useTor) {
final proxyInfo = TorService.sharedInstance.getProxyInfo();
final proxySettings = ProxySettings(
proxyInfo.host,
proxyInfo.port,
);
SocksTCPClient.assignToHttpClient(client, [proxySettings]);
}
blockfrostProvider = BlockforestProvider(
BlockfrostHttpProvider(
url: "${currentNode.host}:${currentNode.port}/",
client: client,
),
);
}
}

View file

@ -28,6 +28,7 @@ import 'impl/banano_wallet.dart';
import 'impl/bitcoin_frost_wallet.dart';
import 'impl/bitcoin_wallet.dart';
import 'impl/bitcoincash_wallet.dart';
import 'impl/cardano_wallet.dart';
import 'impl/dash_wallet.dart';
import 'impl/dogecoin_wallet.dart';
import 'impl/ecash_wallet.dart';
@ -324,6 +325,9 @@ abstract class Wallet<T extends CryptoCurrency> {
case const (Bitcoincash):
return BitcoincashWallet(net);
case const (Cardano):
return CardanoWallet(net);
case const (Dash):
return DashWallet(net);

View file

@ -155,6 +155,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.0"
blockchain_utils:
dependency: "direct main"
description:
name: blockchain_utils
sha256: aebc3a32b927b34f638817c4bfdb85f86a97e6ad35f0cd962660b0c6e8d5c56b
url: "https://pub.dev"
source: hosted
version: "3.3.0"
boolean_selector:
dependency: transitive
description:
@ -527,10 +535,10 @@ packages:
dependency: "direct main"
description:
name: device_info_plus
sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074
sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110"
url: "https://pub.dev"
source: hosted
version: "10.1.2"
version: "9.1.2"
device_info_plus_platform_interface:
dependency: transitive
description:
@ -1118,18 +1126,18 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
url: "https://pub.dev"
source: hosted
version: "10.0.5"
version: "10.0.4"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
version: "3.0.3"
leak_tracker_testing:
dependency: transitive
description:
@ -1229,10 +1237,10 @@ packages:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.11.1"
version: "0.8.0"
memoize:
dependency: transitive
description:
@ -1245,10 +1253,10 @@ packages:
dependency: "direct main"
description:
name: meta
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
url: "https://pub.dev"
source: hosted
version: "1.15.0"
version: "1.12.0"
mime:
dependency: transitive
description:
@ -1338,6 +1346,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.2"
on_chain:
dependency: "direct main"
description:
name: on_chain
sha256: "4040c187be82f6f6414110139f6e70fe304f23f63111c3ee60438c539b1a1dce"
url: "https://pub.dev"
source: hosted
version: "4.0.1"
package_config:
dependency: transitive
description:
@ -1486,18 +1502,18 @@ packages:
dependency: "direct overridden"
description:
name: pinenacl
sha256: "57e907beaacbc3c024a098910b6240758e899674de07d6949a67b52fd984cbdf"
sha256: e5fb0bce1717b7f136f35ee98b5c02b3e6383211f8a77ca882fa7812232a07b9
url: "https://pub.dev"
source: hosted
version: "0.6.0"
version: "0.3.4"
platform:
dependency: transitive
description:
name: platform
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
version: "3.1.4"
plugin_platform_interface:
dependency: transitive
description:
@ -1854,32 +1870,32 @@ packages:
dependency: transitive
description:
name: test
sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e"
sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073"
url: "https://pub.dev"
source: hosted
version: "1.25.7"
version: "1.25.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
version: "0.7.0"
test_core:
dependency: transitive
description:
name: test_core
sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696"
sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4"
url: "https://pub.dev"
source: hosted
version: "0.6.4"
version: "0.6.0"
tezart:
dependency: "direct main"
description:
path: "."
ref: d000cc245e51d3ff50e6467960fb3d9159d5b2a9
resolved-ref: d000cc245e51d3ff50e6467960fb3d9159d5b2a9
ref: "13fa937ea9a9fc34caf047e068df9535f65c27ad"
resolved-ref: "13fa937ea9a9fc34caf047e068df9535f65c27ad"
url: "https://github.com/cypherstack/tezart.git"
source: git
version: "2.0.5"
@ -2072,7 +2088,7 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
url: "https://pub.dev"
source: hosted
version: "14.2.5"
@ -2174,7 +2190,7 @@ packages:
source: hosted
version: "1.2.1"
win32:
dependency: "direct overridden"
dependency: transitive
description:
name: win32
sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a"

View file

@ -57,6 +57,7 @@ final List<CryptoCurrency> _supportedCoins = List.unmodifiable([
Banano(CryptoCurrencyNetwork.main),
Bitcoincash(CryptoCurrencyNetwork.main),
BitcoinFrost(CryptoCurrencyNetwork.main),
Cardano(CryptoCurrencyNetwork.main),
Dash(CryptoCurrencyNetwork.main),
Dogecoin(CryptoCurrencyNetwork.main),
Ecash(CryptoCurrencyNetwork.main),

View file

@ -204,6 +204,8 @@ dependencies:
path: packages/camera/camera_windows
camera_platform_interface: ^2.8.0
camera_macos: ^0.0.8
blockchain_utils: ^3.3.0
on_chain: ^4.0.1
dev_dependencies:
flutter_test: