From 27d319cece441175ac103a7cb1c9e04e147c83c5 Mon Sep 17 00:00:00 2001 From: dethe <76167420+detherminal@users.noreply.github.com> Date: Mon, 26 Aug 2024 20:38:58 +0300 Subject: [PATCH 1/7] feat: add cardano --- .../isar/models/blockchain_data/address.dart | 5 +- ...w_wallet_recovery_phrase_warning_view.dart | 7 + lib/services/price.dart | 1 + .../enums/derive_path_type_enum.dart | 6 +- lib/utilities/test_node_connection.dart | 36 +- .../api/cardano/blockfrost_http_provider.dart | 51 ++ .../crypto_currency/coins/cardano.dart | 123 ++++ .../crypto_currency/crypto_currency.dart | 1 + lib/wallets/wallet/impl/cardano_wallet.dart | 556 ++++++++++++++++++ lib/wallets/wallet/wallet.dart | 4 + pubspec.lock | 16 + scripts/app_config/configure_stack_wallet.sh | 1 + 12 files changed, 802 insertions(+), 5 deletions(-) create mode 100644 lib/wallets/api/cardano/blockfrost_http_provider.dart create mode 100644 lib/wallets/crypto_currency/coins/cardano.dart create mode 100644 lib/wallets/wallet/impl/cardano_wallet.dart diff --git a/lib/models/isar/models/blockchain_data/address.dart b/lib/models/isar/models/blockchain_data/address.dart index bcc7d22c5..29176adfb 100644 --- a/lib/models/isar/models/blockchain_data/address.dart +++ b/lib/models/isar/models/blockchain_data/address.dart @@ -165,7 +165,8 @@ enum AddressType { tezos, frostMS, p2tr, - solana; + solana, + cardanoShelley; String get readableName { switch (this) { @@ -201,6 +202,8 @@ enum AddressType { return "Solana"; case AddressType.p2tr: return "P2TR (taproot)"; + case AddressType.cardanoShelley: + return "Cardano Shelley"; } } } diff --git a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart index 42836e117..f1796ce5c 100644 --- a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart +++ b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart @@ -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,11 +562,16 @@ 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 // xmr/wow libraries handling their // own mnemonic generation + } else if (coin is Cardano) { + mnemonicPassphrase = ""; + mnemonic = Bip39MnemonicGenerator().fromWordsNumber(Bip39WordsNum.wordsNum15).toList().join(" "); } else if (wordCount > 0) { if (ref .read( diff --git a/lib/services/price.dart b/lib/services/price.dart index dc4f3d8d8..5a97e43cb 100644 --- a/lib/services/price.dart +++ b/lib/services/price.dart @@ -30,6 +30,7 @@ class PriceAPI { BitcoinFrost: "bitcoin", Litecoin: "litecoin", Bitcoincash: "bitcoin-cash", + Cardano: "cardano", Dash: "dash", Dogecoin: "dogecoin", Epiccash: "epic-cash", diff --git a/lib/utilities/enums/derive_path_type_enum.dart b/lib/utilities/enums/derive_path_type_enum.dart index 8981cf1c3..3d64fb45a 100644 --- a/lib/utilities/enums/derive_path_type_enum.dart +++ b/lib/utilities/enums/derive_path_type_enum.dart @@ -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; } } } diff --git a/lib/utilities/test_node_connection.dart b/lib/utilities/test_node_connection.dart index ec349445e..3bcc61f1d 100644 --- a/lib/utilities/test_node_connection.dart +++ b/lib/utilities/test_node_connection.dart @@ -4,11 +4,14 @@ 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 '../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 +110,9 @@ Future testNodeConnection({ case CryptonoteCurrency(): try { - final proxyInfo = ref.read(prefsChangeNotifierProvider).useTor + final proxyInfo = ref + .read(prefsChangeNotifierProvider) + .useTor ? ref.read(pTorService).getProxyInfo() : null; @@ -190,7 +195,7 @@ Future testNodeConnection({ case Stellar(): try { testPassed = - await testStellarNodeConnection(formData.host!, formData.port!); + await testStellarNodeConnection(formData.host!, formData.port!); } catch (_) {} break; @@ -206,7 +211,9 @@ Future testNodeConnection({ "action": "version", }, ), - proxyInfo: ref.read(prefsChangeNotifierProvider).useTor + proxyInfo: ref + .read(prefsChangeNotifierProvider) + .useTor ? ref.read(pTorService).getProxyInfo() : null, ); @@ -243,6 +250,29 @@ Future testNodeConnection({ testPassed = false; } break; + + case Cardano(): + try { + final blockfrostProvider = BlockforestProvider( + BlockfrostHttpProvider( + url: "${formData.host!}:${formData.port!}/api/v0", + ), + ); + + final health = await blockfrostProvider.request( + BlockfrostRequestBackendHealthStatus(), + ); + + Logging.instance.log( + "Cardano testNodeConnection \"health=$health\"", + level: LogLevel.Info, + ); + + return health; + } catch (_) { + testPassed = false; + } + break; } return testPassed; diff --git a/lib/wallets/api/cardano/blockfrost_http_provider.dart b/lib/wallets/api/cardano/blockfrost_http_provider.dart new file mode 100644 index 000000000..e39531d2a --- /dev/null +++ b/lib/wallets/api/cardano/blockfrost_http_provider.dart @@ -0,0 +1,51 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +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, + http.Client? client, + this.defaultRequestTimeout = const Duration(seconds: 30)}) + : client = client ?? http.Client(); + @override + final String url; + final String version; + final String? projectId; + final http.Client client; + final Duration defaultRequestTimeout; + + @override + Future get(BlockforestRequestDetails params, + [Duration? timeout]) async { + final response = + await client.get(Uri.parse(params.url(url, "api/$version")), headers: { + 'Content-Type': 'application/json', + "Accept": "application/json", + if (projectId != null) ...{"project_id": projectId!}, + }).timeout(timeout ?? defaultRequestTimeout); + final data = json.decode(response.body); + return data; + } + + @override + Future post(BlockforestRequestDetails params, + [Duration? timeout]) async { + final response = await client + .post(Uri.parse(params.url(url, "api/$version")), + headers: { + "Accept": "application/json", + if (projectId != null) ...{"project_id": projectId!}, + ...params.header + }, + body: params.body) + .timeout(timeout ?? defaultRequestTimeout); + final data = json.decode(response.body); + return data; + } +} \ No newline at end of file diff --git a/lib/wallets/crypto_currency/coins/cardano.dart b/lib/wallets/crypto_currency/coins/cardano.dart new file mode 100644 index 000000000..ed24d6bac --- /dev/null +++ b/lib/wallets/crypto_currency/coins/cardano.dart @@ -0,0 +1,123 @@ +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 get possibleMnemonicLengths => [defaultSeedPhraseLength]; + + @override + BigInt get satsPerCoin => BigInt.from(1000000); + + @override + int get targetBlockTimeSeconds => 20; + + @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"); + } + } +} diff --git a/lib/wallets/crypto_currency/crypto_currency.dart b/lib/wallets/crypto_currency/crypto_currency.dart index 788c87b4f..074536be6 100644 --- a/lib/wallets/crypto_currency/crypto_currency.dart +++ b/lib/wallets/crypto_currency/crypto_currency.dart @@ -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'; diff --git a/lib/wallets/wallet/impl/cardano_wallet.dart b/lib/wallets/wallet/impl/cardano_wallet.dart new file mode 100644 index 000000000..7b82edd08 --- /dev/null +++ b/lib/wallets/wallet/impl/cardano_wallet.dart @@ -0,0 +1,556 @@ +import 'dart:convert'; + +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 '../../../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 { + 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
_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 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 pingCheck() async { + try { + final currentNode = getCurrentNode(); + blockfrostProvider = BlockforestProvider( + BlockfrostHttpProvider( + url: "${currentNode.host}:${currentNode.port}/api/v0", + ), + ); + + 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 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 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 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; + var listOfUtxosToBeUsed = []; + 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 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 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; + var listOfUtxosToBeUsed = []; + 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 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 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 updateBalance() async { + try { + await updateProvider(); + + final addressUtxos = await blockfrostProvider!.request( + BlockfrostRequestAddressUTXOs( + ADAAddress.fromAddress( + (await getCurrentReceivingAddress())!.value, + ), + ), + ); + + 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 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 updateNode() async { + await refresh(); + } + + @override + Future updateTransactions() async { + try { + await updateProvider(); + + final currentAddr = (await getCurrentReceivingAddress())!.value; + + final txsList = await blockfrostProvider!.request( + BlockfrostRequestAddressTransactions( + ADAAddress.fromAddress( + currentAddr, + ), + ), + ); + + final parsedTxsList = + List>.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; + + var txOutputIndex = -1; + + for (final input in utxoInfo.inputs) { + if (input.address == currentAddr) { + txType = isar.TransactionType.outgoing; + } + } + + if (txType == isar.TransactionType.unknown) { + for (final output in utxoInfo.outputs) { + if (output.address == currentAddr) { + txType = isar.TransactionType.incoming; + txOutputIndex = output.outputIndex; + } + } + } + + var receiverAddr = "Unknown?"; + + if (txType == isar.TransactionType.incoming) { + receiverAddr = currentAddr; + } else if (txType == isar.TransactionType.outgoing) { + for (final output in utxoInfo.outputs) { + if (output.address != currentAddr) { + receiverAddr = output.address; + txOutputIndex = output.outputIndex; + } + } + } + + var amount = 0; + + // Temporary solution until https://github.com/mrtnetwork/On_chain/issues/9 is solved. + // Use the library when the mentioned issue is solved. + final currentNode = getCurrentNode(); + final response = await _httpClient.get( + url: Uri.parse( + "${currentNode.host}:${currentNode.port}/api/v0/txs/${tx.txHash}/utxos",), + proxyInfo: Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, + ); + final json = jsonDecode(response.body); + if (txOutputIndex != -1) { + amount = int.parse(json["outputs"][txOutputIndex]["amount"][0] + ["quantity"]! as String,); + } + // Temporary solution part end. + + 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.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 updateUTXOs() async { + // TODO: implement updateUTXOs + return false; + } + + Future updateProvider() async { + final currentNode = getCurrentNode(); + blockfrostProvider = BlockforestProvider( + BlockfrostHttpProvider( + url: "${currentNode.host}:${currentNode.port}/", + ), + ); + } +} diff --git a/lib/wallets/wallet/wallet.dart b/lib/wallets/wallet/wallet.dart index 4837eb8d3..1e711f19d 100644 --- a/lib/wallets/wallet/wallet.dart +++ b/lib/wallets/wallet/wallet.dart @@ -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 { case const (Bitcoincash): return BitcoincashWallet(net); + case const (Cardano): + return CardanoWallet(net); + case const (Dash): return DashWallet(net); diff --git a/pubspec.lock b/pubspec.lock index 001cb4b67..daa4609c0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -150,6 +150,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: @@ -1277,6 +1285,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + on_chain: + dependency: "direct main" + description: + name: on_chain + sha256: "53a17cc19c722e3cdef7a34931e7cd07c490443a92bf538f08cc755b854dfb59" + url: "https://pub.dev" + source: hosted + version: "3.9.0" package_config: dependency: transitive description: diff --git a/scripts/app_config/configure_stack_wallet.sh b/scripts/app_config/configure_stack_wallet.sh index 7f7c51ab7..6f4a87981 100755 --- a/scripts/app_config/configure_stack_wallet.sh +++ b/scripts/app_config/configure_stack_wallet.sh @@ -57,6 +57,7 @@ final List _supportedCoins = List.unmodifiable([ Banano(CryptoCurrencyNetwork.main), Bitcoincash(CryptoCurrencyNetwork.main), BitcoinFrost(CryptoCurrencyNetwork.main), + Cardano(CryptoCurrencyNetwork.main), Dash(CryptoCurrencyNetwork.main), Dogecoin(CryptoCurrencyNetwork.main), Ecash(CryptoCurrencyNetwork.main), From 5b324a0fc54a568d06f6f5dc35f906f8a657eadd Mon Sep 17 00:00:00 2001 From: dethe <76167420+detherminal@users.noreply.github.com> Date: Wed, 28 Aug 2024 02:46:53 +0300 Subject: [PATCH 2/7] fix: remove special lib usage --- .../new_wallet_recovery_phrase_warning_view.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart index f1796ce5c..275f7ff0e 100644 --- a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart +++ b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart @@ -569,9 +569,6 @@ class _NewWalletRecoveryPhraseWarningViewState // currently a special case due to the // xmr/wow libraries handling their // own mnemonic generation - } else if (coin is Cardano) { - mnemonicPassphrase = ""; - mnemonic = Bip39MnemonicGenerator().fromWordsNumber(Bip39WordsNum.wordsNum15).toList().join(" "); } else if (wordCount > 0) { if (ref .read( From 49ad183ff77a5c6a83bfc0d6c65b897516fb2f14 Mon Sep 17 00:00:00 2001 From: dethe <76167420+detherminal@users.noreply.github.com> Date: Wed, 28 Aug 2024 02:50:05 +0300 Subject: [PATCH 3/7] fix: add new packages used in cardano --- scripts/app_config/templates/pubspec.template | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/app_config/templates/pubspec.template b/scripts/app_config/templates/pubspec.template index dc0323d2b..e28d27667 100644 --- a/scripts/app_config/templates/pubspec.template +++ b/scripts/app_config/templates/pubspec.template @@ -198,6 +198,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: ^3.7.0 dev_dependencies: flutter_test: From e3d040b9b817c324f29fed3938e9370356fae0ff Mon Sep 17 00:00:00 2001 From: dethe <76167420+detherminal@users.noreply.github.com> Date: Sun, 1 Sep 2024 15:12:09 +0300 Subject: [PATCH 4/7] refactor: enhance ada txs --- lib/wallets/wallet/impl/cardano_wallet.dart | 54 ++++++++++--------- pubspec.lock | 4 +- scripts/app_config/templates/pubspec.template | 2 +- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/lib/wallets/wallet/impl/cardano_wallet.dart b/lib/wallets/wallet/impl/cardano_wallet.dart index 7b82edd08..31d645415 100644 --- a/lib/wallets/wallet/impl/cardano_wallet.dart +++ b/lib/wallets/wallet/impl/cardano_wallet.dart @@ -353,10 +353,11 @@ class CardanoWallet extends Bip39Wallet { await updateProvider(); final addressUtxos = await blockfrostProvider!.request( - BlockfrostRequestAddressUTXOs( - ADAAddress.fromAddress( + BlockfrostRequestAddressUTXOsOfAGivenAsset( + address: ADAAddress.fromAddress( (await getCurrentReceivingAddress())!.value, ), + asset: "lovelace", ), ); @@ -402,7 +403,7 @@ class CardanoWallet extends Bip39Wallet { await info.updateCachedChainHeight( newHeight: latestBlock.height == null ? 0 : latestBlock.height!, - isar: mainDB.isar); + isar: mainDB.isar,); } catch (e, s) { Logging.instance.log( "Error updating transactions in cardano_wallet.dart: $e\n$s", @@ -443,55 +444,56 @@ class CardanoWallet extends Bip39Wallet { ); var txType = isar.TransactionType.unknown; - var txOutputIndex = -1; - 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; - txOutputIndex = output.outputIndex; } } } var receiverAddr = "Unknown?"; + var amount = 0; if (txType == isar.TransactionType.incoming) { receiverAddr = currentAddr; + for (final output in utxoInfo.outputs) { + if (output.address == currentAddr) { + amount += int.parse(output.amount.first.quantity); + } + } } else if (txType == isar.TransactionType.outgoing) { for (final output in utxoInfo.outputs) { if (output.address != currentAddr) { receiverAddr = output.address; - txOutputIndex = output.outputIndex; + amount += int.parse(output.amount.first.quantity); } } + } else if (txType == isar.TransactionType.sentToSelf) { + receiverAddr = currentAddr; + for (final output in utxoInfo.outputs) { + amount += int.parse(output.amount.first.quantity); + } } - var amount = 0; - - // Temporary solution until https://github.com/mrtnetwork/On_chain/issues/9 is solved. - // Use the library when the mentioned issue is solved. - final currentNode = getCurrentNode(); - final response = await _httpClient.get( - url: Uri.parse( - "${currentNode.host}:${currentNode.port}/api/v0/txs/${tx.txHash}/utxos",), - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, - ); - final json = jsonDecode(response.body); - if (txOutputIndex != -1) { - amount = int.parse(json["outputs"][txOutputIndex]["amount"][0] - ["quantity"]! as String,); - } - // Temporary solution part end. - final transaction = isar.Transaction( walletId: walletId, txid: txInfo.hash, diff --git a/pubspec.lock b/pubspec.lock index daa4609c0..682c9a65c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1289,10 +1289,10 @@ packages: dependency: "direct main" description: name: on_chain - sha256: "53a17cc19c722e3cdef7a34931e7cd07c490443a92bf538f08cc755b854dfb59" + sha256: "4040c187be82f6f6414110139f6e70fe304f23f63111c3ee60438c539b1a1dce" url: "https://pub.dev" source: hosted - version: "3.9.0" + version: "4.0.1" package_config: dependency: transitive description: diff --git a/scripts/app_config/templates/pubspec.template b/scripts/app_config/templates/pubspec.template index e28d27667..b443f9f97 100644 --- a/scripts/app_config/templates/pubspec.template +++ b/scripts/app_config/templates/pubspec.template @@ -199,7 +199,7 @@ dependencies: camera_platform_interface: ^2.8.0 camera_macos: ^0.0.8 blockchain_utils: ^3.3.0 - on_chain: ^3.7.0 + on_chain: ^4.0.1 dev_dependencies: flutter_test: From 02dc5c941655284d71d53abf9dbfa2ca5645da81 Mon Sep 17 00:00:00 2001 From: dethe <76167420+detherminal@users.noreply.github.com> Date: Sun, 1 Sep 2024 15:24:15 +0300 Subject: [PATCH 5/7] refactor: show only lovelaces --- lib/wallets/wallet/impl/cardano_wallet.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/wallets/wallet/impl/cardano_wallet.dart b/lib/wallets/wallet/impl/cardano_wallet.dart index 31d645415..c8471ae31 100644 --- a/lib/wallets/wallet/impl/cardano_wallet.dart +++ b/lib/wallets/wallet/impl/cardano_wallet.dart @@ -476,13 +476,13 @@ class CardanoWallet extends Bip39Wallet { if (txType == isar.TransactionType.incoming) { receiverAddr = currentAddr; for (final output in utxoInfo.outputs) { - if (output.address == currentAddr) { + 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) { + if (output.address != currentAddr && output.amount.first.unit == "lovelace") { receiverAddr = output.address; amount += int.parse(output.amount.first.quantity); } @@ -490,7 +490,9 @@ class CardanoWallet extends Bip39Wallet { } else if (txType == isar.TransactionType.sentToSelf) { receiverAddr = currentAddr; for (final output in utxoInfo.outputs) { - amount += int.parse(output.amount.first.quantity); + if (output.amount.first.unit == "lovelace") { + amount += int.parse(output.amount.first.quantity); + } } } From 1ec7ee95d268e5f9bff29d04a77fee5a0f9ebb72 Mon Sep 17 00:00:00 2001 From: dethe <76167420+detherminal@users.noreply.github.com> Date: Sun, 1 Sep 2024 16:19:46 +0300 Subject: [PATCH 6/7] feat: total tor support for cardano --- lib/utilities/test_node_connection.dart | 13 ++++ .../api/cardano/blockfrost_http_provider.dart | 60 +++++++++---------- .../crypto_currency/coins/cardano.dart | 3 + lib/wallets/wallet/impl/cardano_wallet.dart | 23 ++++--- 4 files changed, 60 insertions(+), 39 deletions(-) diff --git a/lib/utilities/test_node_connection.dart b/lib/utilities/test_node_connection.dart index 0155710f0..458e04e4d 100644 --- a/lib/utilities/test_node_connection.dart +++ b/lib/utilities/test_node_connection.dart @@ -6,6 +6,7 @@ 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'; @@ -243,9 +244,21 @@ Future testNodeConnection({ 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, ), ); diff --git a/lib/wallets/api/cardano/blockfrost_http_provider.dart b/lib/wallets/api/cardano/blockfrost_http_provider.dart index e39531d2a..44a392fab 100644 --- a/lib/wallets/api/cardano/blockfrost_http_provider.dart +++ b/lib/wallets/api/cardano/blockfrost_http_provider.dart @@ -1,51 +1,49 @@ import 'dart:convert'; -import 'package:http/http.dart' as http; +import 'dart:io'; 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, - http.Client? client, - this.defaultRequestTimeout = const Duration(seconds: 30)}) - : client = client ?? http.Client(); + 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 http.Client client; + final HttpClient client; final Duration defaultRequestTimeout; @override Future get(BlockforestRequestDetails params, - [Duration? timeout]) async { - final response = - await client.get(Uri.parse(params.url(url, "api/$version")), headers: { - 'Content-Type': 'application/json', - "Accept": "application/json", - if (projectId != null) ...{"project_id": projectId!}, - }).timeout(timeout ?? defaultRequestTimeout); - final data = json.decode(response.body); + [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 post(BlockforestRequestDetails params, - [Duration? timeout]) async { - final response = await client - .post(Uri.parse(params.url(url, "api/$version")), - headers: { - "Accept": "application/json", - if (projectId != null) ...{"project_id": projectId!}, - ...params.header - }, - body: params.body) - .timeout(timeout ?? defaultRequestTimeout); - final data = json.decode(response.body); + [Duration? timeout,]) async { + final request = await client.postUrl(Uri.parse(params.url(url, "api/$version"))).timeout(timeout ?? defaultRequestTimeout); + request.headers.add("Content-Type", "application/json"); + request.headers.add("Accept", "application/json"); + if (projectId != null) { + request.headers.add("project_id", projectId!); + } + request.write(json.encode(params.body)); + final response = await request.close(); + final data = json.decode(await response.transform(utf8.decoder).join()); return data; } -} \ No newline at end of file +} diff --git a/lib/wallets/crypto_currency/coins/cardano.dart b/lib/wallets/crypto_currency/coins/cardano.dart index ed24d6bac..e9cf17c65 100644 --- a/lib/wallets/crypto_currency/coins/cardano.dart +++ b/lib/wallets/crypto_currency/coins/cardano.dart @@ -111,6 +111,9 @@ class Cardano extends Bip39Currency { @override int get targetBlockTimeSeconds => 20; + @override + bool get torSupport => true; + @override bool validateAddress(String address) { switch (network) { diff --git a/lib/wallets/wallet/impl/cardano_wallet.dart b/lib/wallets/wallet/impl/cardano_wallet.dart index c8471ae31..bf3abfd31 100644 --- a/lib/wallets/wallet/impl/cardano_wallet.dart +++ b/lib/wallets/wallet/impl/cardano_wallet.dart @@ -1,4 +1,5 @@ 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'; @@ -7,6 +8,7 @@ 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'; @@ -84,12 +86,7 @@ class CardanoWallet extends Bip39Wallet { @override Future pingCheck() async { try { - final currentNode = getCurrentNode(); - blockfrostProvider = BlockforestProvider( - BlockfrostHttpProvider( - url: "${currentNode.host}:${currentNode.port}/api/v0", - ), - ); + await updateProvider(); final health = await blockfrostProvider!.request( BlockfrostRequestBackendHealthStatus(), @@ -176,7 +173,7 @@ class CardanoWallet extends Bip39Wallet { ); var leftAmountForUtxos = txData.amount!.raw; - var listOfUtxosToBeUsed = []; + final listOfUtxosToBeUsed = []; var totalBalance = BigInt.zero; for (final utxo in utxos) { @@ -271,7 +268,7 @@ class CardanoWallet extends Bip39Wallet { var leftAmountForUtxos = txData.amount!.raw + txData.fee!.raw; - var listOfUtxosToBeUsed = []; + final listOfUtxosToBeUsed = []; var totalBalance = BigInt.zero; for (final utxo in utxos) { @@ -551,9 +548,19 @@ class CardanoWallet extends Bip39Wallet { Future 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, ), ); } From 6cbc37f42ce4b00b27a37db8c3b36884a0bbb8c9 Mon Sep 17 00:00:00 2001 From: dethe <76167420+detherminal@users.noreply.github.com> Date: Sat, 14 Sep 2024 21:38:57 +0300 Subject: [PATCH 7/7] fix: post req body --- .../api/cardano/blockfrost_http_provider.dart | 8 ++- pubspec.lock | 54 +++++++++---------- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/lib/wallets/api/cardano/blockfrost_http_provider.dart b/lib/wallets/api/cardano/blockfrost_http_provider.dart index 44a392fab..bf00691a9 100644 --- a/lib/wallets/api/cardano/blockfrost_http_provider.dart +++ b/lib/wallets/api/cardano/blockfrost_http_provider.dart @@ -1,8 +1,11 @@ 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, @@ -36,12 +39,13 @@ class BlockfrostHttpProvider implements BlockfrostServiceProvider { Future post(BlockforestRequestDetails params, [Duration? timeout,]) async { final request = await client.postUrl(Uri.parse(params.url(url, "api/$version"))).timeout(timeout ?? defaultRequestTimeout); - request.headers.add("Content-Type", "application/json"); + // 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.write(json.encode(params.body)); + request.add(params.body as List); final response = await request.close(); final data = json.decode(await response.transform(utf8.decoder).join()); return data; diff --git a/pubspec.lock b/pubspec.lock index aacb0951d..549f07561 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -530,10 +530,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: @@ -1113,18 +1113,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: @@ -1184,10 +1184,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: @@ -1200,10 +1200,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: @@ -1433,18 +1433,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: @@ -1793,32 +1793,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" @@ -2011,10 +2011,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.2.1" wakelock: dependency: "direct main" description: @@ -2121,7 +2121,7 @@ packages: source: hosted version: "1.2.1" win32: - dependency: "direct overridden" + dependency: transitive description: name: win32 sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" @@ -2194,5 +2194,5 @@ packages: source: hosted version: "0.2.3" sdks: - dart: ">=3.5.2 <4.0.0" - flutter: ">=3.24.2" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.19.6"