diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index b9b021896..8cb442447 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -9,7 +9,7 @@ jobs: PR_test_build: runs-on: [Linux, amd64, forlinux] container: - image: ghcr.io/cake-tech/cake_wallet:debian13-flutter3.32.0-ndkr28-go1.24.1-ruststablenightly + image: ghcr.io/cake-tech/cake_wallet:debian13-flutter3.41.9-ndkr28-go1.24.1-ruststablenightly env: STORE_PASS: test@cake_wallet KEY_PASS: test@cake_wallet @@ -227,6 +227,9 @@ jobs: xmessage -timeout 30 "restore_wallet_through_seeds_flow_test" & rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart + - name: Test [cw_core] + timeout-minutes: 15 + run: cd cw_core && flutter test - name: Test [cw_monero] timeout-minutes: 15 run: cd cw_monero && flutter test diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 5d475c71a..c6082d4e0 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -21,7 +21,7 @@ jobs: PR_test_build: runs-on: [Linux, amd64, android] container: - image: ghcr.io/cake-tech/cake_wallet:debian13-flutter3.32.0-ndkr28-go1.24.1-ruststablenightly + image: ghcr.io/cake-tech/cake_wallet:debian13-flutter3.41.9-ndkr28-go1.24.1-ruststablenightly env: STORE_PASS: test@cake_wallet KEY_PASS: test@cake_wallet diff --git a/Dockerfile b/Dockerfile index 773ac6822..7a0c8c022 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# docker buildx build --push --pull --platform linux/amd64,linux/arm64 . -f Dockerfile -t ghcr.io/cake-tech/cake_wallet:debian13-flutter3.32.0-ndkr28-go1.24.1-ruststablenightly +# docker buildx build --push --pull --platform linux/amd64,linux/arm64 . -f Dockerfile -t ghcr.io/cake-tech/cake_wallet:debian13-flutter3.41.9-ndkr28-go1.24.1-ruststablenightly # Heavily inspired by cirrusci images # https://github.com/cirruslabs/docker-images-android/blob/master/sdk/tools/Dockerfile @@ -15,7 +15,7 @@ LABEL org.opencontainers.image.source=https://github.com/cake-tech/cake_wallet ENV GOLANG_VERSION=1.24.1 # Pin Flutter version to latest known-working version -ENV FLUTTER_VERSION=3.32.0 +ENV FLUTTER_VERSION=3.41.9 # Pin Android Studio, platform, and build tools versions to latest known-working version # Comes from https://developer.android.com/studio/#command-tools diff --git a/android/gradle.properties b/android/gradle.properties index 184d30421..7be3f195e 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,4 @@ org.gradle.jvmargs=-Xmx6144M -android.enableR8=true android.useAndroidX=true android.enableJetifier=true -android.ndk.suppressMinSdkVersionError=21 +android.ndk.suppressMinSdkVersionError=21 \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 0cc4e42b9..c494406ec 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 468f9b55f..b6764328b 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,8 +18,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.7.1" apply false - id "org.jetbrains.kotlin.android" version "2.0.21" apply false + id "com.android.application" version "8.9.1" apply false + id "org.jetbrains.kotlin.android" version "2.3.20" apply false id "com.google.gms.google-services" version "4.3.8" apply false } diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 144c2eafd..a2b2f798f 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -24,10 +24,10 @@ import 'package:cw_bitcoin/psbt/signer.dart'; import 'package:cw_bitcoin/psbt/transaction_builder.dart'; import 'package:cw_bitcoin/psbt/v0_deserialize.dart'; import 'package:cw_bitcoin/psbt/v0_finalizer.dart'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/output_info.dart'; -import 'package:cw_core/parse_fixed.dart'; import 'package:cw_core/payjoin_session.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/unspent_coin_type.dart'; @@ -347,13 +347,15 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { final lBalance = await lightningWallet!.getBalance(); - this.balance[CryptoCurrency.btcln] = - ElectrumBalance(confirmed: lBalance.toInt(), unconfirmed: 0, frozen: 0); + this.balance[CryptoCurrency.btcln] = ElectrumBalance( + confirmed: lBalance, + unconfirmed: Money.zero(CryptoCurrency.btcln), + frozen: Money.zero(CryptoCurrency.btcln)); return ElectrumBalance( confirmed: balance.confirmed, unconfirmed: balance.unconfirmed, - frozen: balance.frozen.toInt(), + frozen: balance.frozen ?? Money.zero(currency), ); } @@ -493,19 +495,19 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { final isLNCompatible = await lightningWallet?.isCompatible(lnAddr); if ((credentials.coinTypeToSpendFrom == UnspentCoinType.lightning && lightningWallet != null) || isLNCompatible == true) { - BigInt amount; + Money amount; if (credentials.outputs.first.sendAll) { amount = await lightningWallet!.getBalance(); } else { - amount = parseFixed( - credentials.outputs.first.cryptoAmount?.isNotEmpty == true - ? credentials.outputs.first.cryptoAmount! - : "0", - 8); + amount = credentials.outputs.first.cryptoAmount; } - return lightningWallet!.createTransaction(lnAddr, amount > BigInt.zero ? amount : null, - credentials.priority, credentials.outputs.first.sendAll); + + return lightningWallet!.createTransaction( + lnAddr, + amount.amount > BigInt.zero ? amount.amount : null, + credentials.priority, + credentials.outputs.first.sendAll,); } final tx = (await super.createTransaction(credentials)) as PendingBitcoinTransaction; @@ -524,7 +526,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { )) .toList(), cwOutputs: credentials.outputs, - fee: BigInt.from(tx.fee), + fee: tx.fee.amount, network: network, memo: credentials.outputs.first.memo, outputOrdering: BitcoinOrdering.none, @@ -543,8 +545,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { tx.commitOverride = () async { final sender = await payjoinManager.initSender(payjoinUri!, originalPsbt, int.parse(tx.feeRate)); - payjoinManager.spawnNewSender( - sender: sender, pjUrl: payjoinUri, amount: BigInt.from(tx.amount)); + payjoinManager.spawnNewSender(sender: sender, pjUrl: payjoinUri, amount: tx.amount.amount); }; return tx; @@ -564,8 +565,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { btcTx, type, electrumClient: electrumClient, - amount: 0, - fee: 0, + amount: Money.zero(currency), + fee: Money.zero(currency), feeRate: "", network: network, hasChange: true, @@ -622,8 +623,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { btcTx, type, electrumClient: electrumClient, - amount: 0, - fee: 0, + amount: Money.zero(currency), + fee: Money.zero(currency), feeRate: "", network: network, hasChange: true, diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index be03f26d0..6abce9689 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -153,11 +153,24 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S @override String get addressForExchange { - if (addressPageType == LightningAddressType.p2l) { - final addresses = receiveAddresses - .where((element) => element.type == SegwitAddresType.p2wpkh && !element.isUsed); - return addresses.first.address; + + final current = getFreshAddress(); + final availableReceiveAddresses = receiveAddresses.where((element) => + !element.isUsed && + !element.isHidden && + !hiddenAddresses.contains(element.address)); + + final bool isSilentPaymentsPage = addressPageType == SilentPaymentsAddresType.p2sp; + final bool isLightningPage = addressPageType == LightningAddressType.p2l; + + if (isSilentPaymentsPage || isLightningPage) { + final segwit = availableReceiveAddresses + .where((e) => e.type == SegwitAddresType.p2wpkh) + .map((e) => e.address) + .toList(); + + return segwit.isNotEmpty ? segwit.first : current; } - return address; + return current; } } diff --git a/cw_bitcoin/lib/electrum_balance.dart b/cw_bitcoin/lib/electrum_balance.dart index a1529f92f..02569887c 100644 --- a/cw_bitcoin/lib/electrum_balance.dart +++ b/cw_bitcoin/lib/electrum_balance.dart @@ -1,49 +1,52 @@ import 'dart:convert'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/balance.dart'; +import 'package:cw_core/currency.dart'; class ElectrumBalance extends Balance { ElectrumBalance({ required this.confirmed, required this.unconfirmed, - required int frozen, - this.secondConfirmed = 0, - this.secondUnconfirmed = 0, - }) : this._frozen = frozen, super.fromInt(confirmed, unconfirmed, - secondAvailable: secondConfirmed, secondAdditional: secondUnconfirmed, frozen: frozen); + required this.frozen, + this.secondConfirmed, + this.secondUnconfirmed, + }) : super(confirmed, unconfirmed, + secondAvailable: secondConfirmed, secondUnavailable: secondUnconfirmed); - static ElectrumBalance? fromJSON(String? jsonSource) { + Money confirmed; + Money unconfirmed; + Money? secondConfirmed; + Money? secondUnconfirmed; + + @override + Money get available => (confirmed + unconfirmed) - frozen; + + @override + Money get unavailable => unconfirmed; + + @override + Money frozen; + + static ElectrumBalance? fromJSON(String? jsonSource, Currency currency) { if (jsonSource == null) return null; final decoded = json.decode(jsonSource) as Map; return ElectrumBalance( - confirmed: decoded['confirmed'] as int? ?? 0, - unconfirmed: decoded['unconfirmed'] as int? ?? 0, - frozen: decoded['frozen'] as int? ?? 0, - secondConfirmed: decoded['secondConfirmed'] as int? ?? 0, - secondUnconfirmed: decoded['secondUnconfirmed'] as int? ?? 0, + confirmed: Money.fromInt(decoded['confirmed'] as int? ?? 0, currency), + unconfirmed: Money.fromInt(decoded['unconfirmed'] as int? ?? 0, currency), + frozen: Money.fromInt(decoded['frozen'] as int? ?? 0, currency), + secondConfirmed: Money.fromInt(decoded['secondConfirmed'] as int? ?? 0, currency), + secondUnconfirmed: Money.fromInt(decoded['secondUnconfirmed'] as int? ?? 0, currency), ); } - int confirmed; - int unconfirmed; - final int _frozen; - - @override - BigInt get frozen => BigInt.from(_frozen); - - int secondConfirmed = 0; - int secondUnconfirmed = 0; - - @override - BigInt get fullAvailableBalance => BigInt.from((confirmed + unconfirmed) + secondConfirmed - _frozen); - String toJSON() => json.encode({ - 'confirmed': confirmed, - 'unconfirmed': unconfirmed, - 'frozen': _frozen, - 'secondConfirmed': secondConfirmed, - 'secondUnconfirmed': secondUnconfirmed, + 'confirmed': confirmed.amount.toInt(), + 'unconfirmed': unavailable.amount.toInt(), + 'frozen': frozen.amount.toInt(), + 'secondConfirmed': secondAvailable?.amount.toInt() ?? 0, + 'secondUnconfirmed': secondUnavailable?.amount.toInt() ?? 0, }); } diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index cee1f9b9f..9e18d0e60 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -5,10 +5,11 @@ import 'package:cw_bitcoin/address_from_output.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart'; +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; -import 'package:cw_core/format_amount.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:hex/hex.dart'; @@ -31,8 +32,8 @@ class ElectrumTransactionInfo extends TransactionInfo { this.type, { required String id, int? height, - required int amount, - int? fee, + required Money amount, + Money? fee, List? inputAddresses, List? outputAddresses, required TransactionDirection direction, @@ -106,9 +107,9 @@ class ElectrumTransactionInfo extends TransactionInfo { height: height, isPending: false, isReplaced: false, - fee: fee, + fee: Money.fromInt(fee, walletTypeToCryptoCurrency(type)), direction: direction, - amount: amount, + amount: Money.fromInt(amount, walletTypeToCryptoCurrency(type)), date: date, confirmations: confirmations); } @@ -193,9 +194,9 @@ class ElectrumTransactionInfo extends TransactionInfo { isReplaced: false, inputAddresses: inputAddresses, outputAddresses: outputAddresses, - fee: fee, + fee: Money.fromInt(fee, walletTypeToCryptoCurrency(type)), direction: direction, - amount: amount, + amount: Money.fromInt(amount, walletTypeToCryptoCurrency(type)), date: date, isHogEx: isHogEx, confirmations: bundle.confirmations); @@ -205,13 +206,17 @@ class ElectrumTransactionInfo extends TransactionInfo { final inputAddresses = data['inputAddresses'] as List? ?? []; final outputAddresses = data['outputAddresses'] as List? ?? []; final unspents = data['unspents'] as List? ?? []; + final additionalInfo = data['additionalInfo'] as Map?; + final isLightning = (additionalInfo?['isLightning'] as bool?) == true; + + final currency = isLightning ? CryptoCurrency.btcln : walletTypeToCryptoCurrency(type); return ElectrumTransactionInfo( type, id: data['id'] as String, height: data['height'] as int?, - amount: data['amount'] as int, - fee: data['fee'] as int, + amount: Money.fromInt(data['amount'] as int, currency), + fee: data['fee'] != null ? Money.fromInt(data['fee'] as int, currency) : null, direction: parseTransactionDirectionFromInt(data['direction'] as int), date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), isPending: data['isPending'] as bool, @@ -227,29 +232,12 @@ class ElectrumTransactionInfo extends TransactionInfo { BitcoinSilentPaymentsUnspent.fromJSON(null, unspent as Map)) .toList(), isReceivedSilentPayment: data['isReceivedSilentPayment'] as bool? ?? false, - additionalInfo: data['additionalInfo'] as Map?, + additionalInfo: additionalInfo, ); } final WalletType type; - String? _fiatAmount; - - @override - String amountFormatted() => - '${walletTypeToCryptoCurrency(type).formatAmount(BigInt.from(amount))} ${walletTypeToCryptoCurrency(type).title}'; - - @override - String? feeFormatted() => fee != null - ? '${walletTypeToCryptoCurrency(type).formatAmount(BigInt.from(fee!))} ${walletTypeToCryptoCurrency(type).title}' - : ''; - - @override - String fiatAmount() => _fiatAmount ?? ''; - - @override - void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); - ElectrumTransactionInfo updated(ElectrumTransactionInfo info) { return ElectrumTransactionInfo(info.type, id: id, @@ -270,13 +258,13 @@ class ElectrumTransactionInfo extends TransactionInfo { final m = {}; m['id'] = id; m['height'] = height; - m['amount'] = amount; + m['amount'] = amount.amount.toInt(); m['direction'] = direction.index; m['date'] = date.millisecondsSinceEpoch; m['isPending'] = isPending; m['isReplaced'] = isReplaced; m['confirmations'] = confirmations; - m['fee'] = fee; + m['fee'] = fee?.amount.toInt(); m['to'] = to; m['unspents'] = unspents?.map((e) => e.toJson()).toList() ?? []; m['inputAddresses'] = inputAddresses; diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 3231914db..095d1a45e 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -19,6 +19,7 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart'; +import 'package:cw_bitcoin/bitcoin_wallet.dart'; import 'package:cw_bitcoin/bitcoin_wallet_keys.dart'; import 'package:cw_bitcoin/electrum.dart' as electrum; import 'package:cw_bitcoin/electrum_balance.dart'; @@ -27,20 +28,26 @@ import 'package:cw_bitcoin/electrum_transaction_history.dart'; import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/exceptions.dart'; +import 'package:cw_bitcoin/litecoin_wallet.dart'; import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/utils.dart'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/get_height_by_date.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/output_info.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/root_dir.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/unspent_coin_type.dart'; import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/utils/socket_health_logger.dart'; import 'package:cw_core/utils/tor/abstract.dart'; import 'package:cw_core/wallet_base.dart'; @@ -52,6 +59,7 @@ import 'package:hex/hex.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:sp_scanner/sp_scanner.dart'; part 'electrum_wallet.g.dart'; @@ -94,9 +102,9 @@ abstract class ElectrumWalletBase ? { currency: initialBalance ?? ElectrumBalance( - confirmed: 0, - unconfirmed: 0, - frozen: 0, + confirmed: Money.zero(currency), + unconfirmed: Money.zero(currency), + frozen: Money.zero(currency), ) } : {}), @@ -311,7 +319,7 @@ abstract class ElectrumWalletBase bool get useLightning => _useLightning && LightningWallet.isAvailable; set useLightning(bool val) => _useLightning = val && LightningWallet.isAvailable; - + @observable bool _useLightning; @@ -597,13 +605,13 @@ abstract class ElectrumWalletBase : newUnspents[0].value; if (existingTxInfo.direction == TransactionDirection.incoming) { - existingTxInfo.amount += newAmount; + existingTxInfo.amount += Money.fromInt(newAmount, currency); } // Updates existing TX transactionHistory.addOne(existingTxInfo); // Update balance record - balance[currency]!.confirmed += newAmount; + balance[currency]!.confirmed += Money.fromInt(newAmount, currency); } } else { // else: First time seeing this TX after scanning @@ -865,9 +873,9 @@ abstract class ElectrumWalletBase } } - int get networkDustAmount => 546; + BigInt get networkDustAmount => BigInt.from(546); - bool _isBelowDust(int amount) => amount <= networkDustAmount && network != BitcoinNetwork.testnet; + bool _isBelowDust(BigInt amount) => amount <= networkDustAmount && network != BitcoinNetwork.testnet; UtxoDetails _createUTXOS({ required bool sendAll, @@ -1031,9 +1039,9 @@ abstract class ElectrumWalletBase } // Here, when sending all, the output amount equals to the input value - fee to fully spend every input on the transaction and have no amount left for change - int amount = utxoDetails.allInputsAmount - fee; + final amount = BigInt.from(utxoDetails.allInputsAmount - fee); - if (amount <= 0) { + if (amount <= BigInt.zero) { throw BitcoinTransactionWrongBalanceException(amount: utxoDetails.allInputsAmount + fee); } @@ -1045,7 +1053,7 @@ abstract class ElectrumWalletBase if (outputs.length == 1) { outputs[0] = BitcoinOutput( address: outputs.last.address, - value: BigInt.from(amount), + value: amount, isSilentPayment: hasSilentPayment, ); } @@ -1054,8 +1062,8 @@ abstract class ElectrumWalletBase utxos: utxoDetails.utxos, inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, publicKeys: utxoDetails.publicKeys, - fee: fee, - amount: amount, + fee: Money.fromInt(fee, currency), + amount: Money(amount, currency), isSendAll: true, hasChange: false, memo: memo, @@ -1065,7 +1073,7 @@ abstract class ElectrumWalletBase } Future estimateTxForAmount( - int credentialsAmount, + Money credentialsAmount, List outputs, List updatedOutputs, int feeRate, { @@ -1076,7 +1084,7 @@ abstract class ElectrumWalletBase UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, }) async { // Attempting to send less than the dust limit - if (_isBelowDust(credentialsAmount)) { + if (_isBelowDust(credentialsAmount.amount)) { throw BitcoinTransactionNoDustException(); } @@ -1122,7 +1130,7 @@ abstract class ElectrumWalletBase final utxoDetails = _createUTXOS( sendAll: false, - credentialsAmount: credentialsAmount, + credentialsAmount: credentialsAmount.amount.toInt(), inputsCount: inputsCount, paysToSilentPayment: hasSilentPayment, coinTypeToSpendFrom: coinTypeToSpendFrom, @@ -1133,8 +1141,7 @@ abstract class ElectrumWalletBase utxoDetails.utxos.length == utxoDetails.availableInputs.length - utxoDetails.unconfirmedCoins.length; - // How much is being spent - how much is being sent - int amountLeftForChangeAndFee = utxoDetails.allInputsAmount - credentialsAmount; + final amountLeftForChangeAndFee = utxoDetails.allInputsAmount - credentialsAmount.amount.toInt(); if (amountLeftForChangeAndFee <= 0) { if (!spendingAllCoins) { @@ -1213,9 +1220,9 @@ abstract class ElectrumWalletBase throw BitcoinTransactionNoFeeException(); } - int amount = credentialsAmount; + var amount = credentialsAmount; final lastOutput = updatedOutputs.last; - final amountLeftForChange = amountLeftForChangeAndFee - fee; + final amountLeftForChange = BigInt.from(amountLeftForChangeAndFee - fee); if (_isBelowDust(amountLeftForChange)) { // If has change that is lower than dust, will end up with tx rejected by network rules @@ -1226,7 +1233,7 @@ abstract class ElectrumWalletBase // If the computed change is negative or below dust: // - negative: try a no-change tx (recalculate fee without change) // - non-negative but dust: drop change and add remainder to fee - if (amountLeftForChange < 0) { + if (amountLeftForChange < BigInt.zero) { final tempNoChange = outputs.map((o) => o).toList(); final feeNoChange = await calcFee( utxos: utxoDetails.utxos, @@ -1237,7 +1244,7 @@ abstract class ElectrumWalletBase inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, vinOutpoints: utxoDetails.vinOutpoints, ); - final leftover = utxoDetails.allInputsAmount - credentialsAmount - feeNoChange; + final leftover = utxoDetails.allInputsAmount - credentialsAmount.amount.toInt() - feeNoChange; if (leftover >= 0) { final finalFee = feeNoChange + leftover; // absorb tiny remainder @@ -1245,7 +1252,7 @@ abstract class ElectrumWalletBase utxos: utxoDetails.utxos, inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, publicKeys: utxoDetails.publicKeys, - fee: finalFee, + fee: Money.fromInt(finalFee, currency), amount: amount, hasChange: false, isSendAll: spendingAllCoins, @@ -1274,13 +1281,13 @@ abstract class ElectrumWalletBase // if the amount left for change is less than dust, but not less than 0 // then add it to the fees - fee += amountLeftForChange; + fee += amountLeftForChange.toInt(); return EstimatedTxResult( utxos: utxoDetails.utxos, inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, publicKeys: utxoDetails.publicKeys, - fee: fee, + fee: Money.fromInt(fee, currency), amount: amount, hasChange: false, isSendAll: spendingAllCoins, @@ -1292,13 +1299,13 @@ abstract class ElectrumWalletBase // Here, lastOutput already is change, return the amount left without the fee to the user's address. updatedOutputs[updatedOutputs.length - 1] = BitcoinOutput( address: lastOutput.address, - value: BigInt.from(amountLeftForChange), + value: amountLeftForChange, isSilentPayment: lastOutput.isSilentPayment, isChange: true, ); outputs[outputs.length - 1] = BitcoinOutput( address: lastOutput.address, - value: BigInt.from(amountLeftForChange), + value: amountLeftForChange, isSilentPayment: lastOutput.isSilentPayment, isChange: true, ); @@ -1307,7 +1314,7 @@ abstract class ElectrumWalletBase utxos: utxoDetails.utxos, inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, publicKeys: utxoDetails.publicKeys, - fee: fee, + fee: Money.fromInt(fee, currency), amount: amount, hasChange: true, isSendAll: spendingAllCoins, @@ -1318,7 +1325,7 @@ abstract class ElectrumWalletBase } } - Future _maxSpendableNoChangeAmount({ + Future _maxSpendableNoChangeAmount({ required BitcoinOutput initialOutput, required int feeRate, String? memo, @@ -1350,7 +1357,7 @@ abstract class ElectrumWalletBase ); final maxSpendable = utxoDetailsAll.allInputsAmount - feeNoChange; - return maxSpendable > 0 ? maxSpendable : 0; + return Money.fromInt(maxSpendable > 0 ? maxSpendable : 0, currency); } Future calcFee({ @@ -1397,13 +1404,13 @@ abstract class ElectrumWalletBase final memo = transactionCredentials.outputs.first.memo; final coinTypeToSpendFrom = transactionCredentials.coinTypeToSpendFrom; - int credentialsAmount = 0; - bool hasSilentPayment = false; + var credentialsAmount = Money.zero(currency); + var hasSilentPayment = false; for (final out in transactionCredentials.outputs) { - final outputAmount = out.formattedCryptoAmount!; + final outputAmount = out.cryptoAmount; - if (!sendAll && _isBelowDust(outputAmount)) { + if (!sendAll && _isBelowDust(outputAmount.amount)) { throw BitcoinTransactionNoDustException(); } @@ -1427,13 +1434,13 @@ abstract class ElectrumWalletBase // The value will be changed after estimating the Tx size and deducting the fee from the total to be sent outputs.add(BitcoinOutput( address: address, - value: BigInt.from(0), + value: BigInt.zero, isSilentPayment: isSilentPayment, )); } else { outputs.add(BitcoinOutput( address: address, - value: BigInt.from(outputAmount), + value: outputAmount.amount, isSilentPayment: isSilentPayment, )); } @@ -1486,7 +1493,7 @@ abstract class ElectrumWalletBase utxos: estimatedTx.utxos, outputs: updatedOutputs, publicKeys: estimatedTx.publicKeys, - fee: BigInt.from(estimatedTx.fee), + fee: estimatedTx.fee.amount, network: network, memo: estimatedTx.memo, outputOrdering: BitcoinOrdering.none, @@ -1504,7 +1511,8 @@ abstract class ElectrumWalletBase network: network, hasChange: estimatedTx.hasChange, isSendAll: estimatedTx.isSendAll, - hasTaprootInputs: false, // ToDo: (Konsti) Support Taproot, + hasTaprootInputs: false, + // ToDo: (Konsti) Support Taproot, isViewOnly: false, )..addListener((transaction) async { transactionHistory.addOne(transaction); @@ -1518,7 +1526,7 @@ abstract class ElectrumWalletBase txb = ForkedTransactionBuilder( utxos: estimatedTx.utxos, outputs: updatedOutputs, - fee: BigInt.from(estimatedTx.fee), + fee: estimatedTx.fee.amount, network: network, memo: estimatedTx.memo, outputOrdering: BitcoinOrdering.none, @@ -1528,7 +1536,7 @@ abstract class ElectrumWalletBase txb = BitcoinTransactionBuilder( utxos: estimatedTx.utxos, outputs: updatedOutputs, - fee: BigInt.from(estimatedTx.fee), + fee: estimatedTx.fee.amount, network: network, memo: estimatedTx.memo, outputOrdering: BitcoinOrdering.none, @@ -2038,6 +2046,7 @@ abstract class ElectrumWalletBase unspentCoinsInfo.values.where((record) => record.walletId == id); for (final element in currentWalletUnspentCoins) { + if (element.isFrozen) continue; if (RegexUtils.addressTypeFromStr(element.address, network) is MwebAddress) continue; final existUnspentCoins = unspentCoins.where((coin) => element == coin); @@ -2114,12 +2123,12 @@ abstract class ElectrumWalletBase allInputsAmount += outTransaction.amount.toInt(); } - int totalOutAmount = bundle.originalTransaction.outputs + final totalOutAmount = bundle.originalTransaction.outputs .fold(0, (previousValue, element) => previousValue + element.amount.toInt()); var currentFee = allInputsAmount - totalOutAmount; - int remainingFee = (newFee - currentFee > 0) ? newFee - currentFee : newFee; - return totalBalance - receiverAmount - remainingFee >= networkDustAmount; + final remainingFee = (newFee - currentFee > 0) ? newFee - currentFee : newFee; + return totalBalance - receiverAmount - remainingFee >= networkDustAmount.toInt(); } Future replaceByFee(String hash, int newFee) async { @@ -2194,14 +2203,14 @@ abstract class ElectrumWalletBase int totalOutAmount = outputs.fold(0, (previousValue, output) => previousValue + output.value.toInt()); int currentFee = allInputsAmount - totalOutAmount; - int remainingFee = newFee - currentFee; + var remainingFee = BigInt.from(newFee - currentFee); - if (remainingFee <= 0) { + if (remainingFee <= BigInt.zero) { throw Exception("New fee must be higher than the current fee."); } // Deduct fee from change outputs first, if possible - if (remainingFee > 0) { + if (remainingFee > BigInt.zero) { final changeAddresses = walletAddresses.allAddresses.where((element) => element.isHidden); for (int i = outputs.length - 1; i >= 0; i--) { final output = outputs[i]; @@ -2209,23 +2218,23 @@ abstract class ElectrumWalletBase .any((element) => element.address == output.address.toAddress(network)); if (isChange) { - int outputAmount = output.value.toInt(); + final outputAmount = output.value; if (outputAmount > networkDustAmount) { - int deduction = (outputAmount - networkDustAmount >= remainingFee) + final deduction = (outputAmount - networkDustAmount >= remainingFee) ? remainingFee : outputAmount - networkDustAmount; outputs[i] = BitcoinOutput( - address: output.address, value: BigInt.from(outputAmount - deduction)); + address: output.address, value: outputAmount - deduction); remainingFee -= deduction; - if (remainingFee <= 0) break; + if (remainingFee <= BigInt.zero) break; } } } } // If still not enough, add UTXOs until the fee is covered - if (remainingFee > 0) { + if (remainingFee > BigInt.zero) { final unusedUtxos = unspentCoins .where((utxo) => utxo.isSending && !utxo.isFrozen && utxo.confirmations! > 0) .toList(); @@ -2253,52 +2262,51 @@ abstract class ElectrumWalletBase )); allInputsAmount += utxo.value; - remainingFee -= utxo.value; + remainingFee -= BigInt.from(utxo.value); - if (remainingFee < 0) { + if (remainingFee < BigInt.zero) { final changeOutput = outputs.firstWhereOrNull((output) => walletAddresses.allAddresses .any((addr) => addr.address == output.address.toAddress(network))); if (changeOutput != null) { - final newValue = changeOutput.value.toInt() + (-remainingFee); + final newValue = changeOutput.value + (-remainingFee); outputs[outputs.indexOf(changeOutput)] = - BitcoinOutput(address: changeOutput.address, value: BigInt.from(newValue)); + BitcoinOutput(address: changeOutput.address, value: newValue); } else { final changeAddress = await walletAddresses.getChangeAddress(); outputs.add(BitcoinOutput( address: RegexUtils.addressTypeFromStr(changeAddress.address, network), - value: BigInt.from(-remainingFee))); + value: -remainingFee)); } - remainingFee = 0; + remainingFee = BigInt.zero; break; } - if (remainingFee <= 0) break; + if (remainingFee <= BigInt.zero) break; } } // Deduct from the receiver's output if remaining fee is still greater than 0 - if (remainingFee > 0) { + if (remainingFee > BigInt.zero) { for (int i = 0; i < outputs.length; i++) { final output = outputs[i]; - int outputAmount = output.value.toInt(); + final outputAmount = output.value; if (outputAmount > networkDustAmount) { - int deduction = (outputAmount - networkDustAmount >= remainingFee) + final deduction = (outputAmount - networkDustAmount >= remainingFee) ? remainingFee : outputAmount - networkDustAmount; - outputs[i] = BitcoinOutput( - address: output.address, value: BigInt.from(outputAmount - deduction)); + outputs[i] = BitcoinOutput(address: output.address, value: outputAmount - deduction); remainingFee -= deduction; - if (remainingFee <= 0) break; + if (remainingFee <= BigInt.zero) break; } } } // Final check if the remaining fee couldn't be deducted - if (remainingFee > 0) { + if (remainingFee > BigInt.zero) { throw Exception("Not enough funds to cover the fee."); } @@ -2343,8 +2351,8 @@ abstract class ElectrumWalletBase transaction, type, electrumClient: electrumClient, - amount: sendingAmount, - fee: newFee, + amount: Money.fromInt(sendingAmount, currency), + fee: Money.fromInt(newFee, currency), network: network, hasChange: changeOutputs.isNotEmpty, feeRate: newFee.toString(), @@ -2585,16 +2593,15 @@ abstract class ElectrumWalletBase final history = await electrumClient.getHistory(addressRecord.getScriptHash(network)); - if (history.isNotEmpty) { addressRecord.setAsUsed(); walletAddresses.clearLockIfMatches(addressRecord.type, addressRecord.address); - if(this is BitcoinWallet) { + if (this is BitcoinWallet) { //removes transactions no longer returned by the api, presumed replaced/invalid. transactionHistory.transactions.removeWhere( - (hash, tx) => - tx.outputAddresses != null && + (hash, tx) => + tx.outputAddresses != null && tx.outputAddresses!.contains(addressRecord.address) && !history.any((newTransaction) => newTransaction['tx_hash'] == hash), ); @@ -3480,7 +3487,12 @@ abstract class ElectrumWalletBase // if we got null balance responses from the server, set our connection status to lost and return our last known balance: printV("got null balance responses from the server, setting connection status to lost"); syncStatus = LostConnectionSyncStatus(); - return balance[currency] ?? ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0); + return balance[currency] ?? + ElectrumBalance( + confirmed: Money.zero(currency), + unconfirmed: Money.zero(currency), + frozen: Money.zero(currency), + ); } for (var i = 0; i < balances.length; i++) { @@ -3499,9 +3511,9 @@ abstract class ElectrumWalletBase } return ElectrumBalance( - confirmed: totalConfirmed, - unconfirmed: totalUnconfirmed, - frozen: totalFrozen, + confirmed: Money.fromInt(totalConfirmed, currency), + unconfirmed: Money.fromInt(totalUnconfirmed, currency), + frozen: Money.fromInt(totalFrozen, currency), ); } @@ -3819,12 +3831,6 @@ abstract class ElectrumWalletBase } } - @override - String formatCryptoAmount(String amount) { - final amountBigInt = BigInt.parse(amount); - return currency.formatAmount(amountBigInt); - } - /// Checks the health of the socket connection /// and triggers a full reconnection if needed @override @@ -4285,8 +4291,8 @@ Future _handleScanSilentPayments(ScanData scanData) async { WalletType.bitcoin, id: txid, height: tweakHeight, - amount: 0, - fee: 0, + amount: Money.zero(CryptoCurrency.btc), + fee: Money.zero(CryptoCurrency.btc), direction: TransactionDirection.incoming, isReplaced: false, date: scanData.network == BitcoinNetwork.mainnet @@ -4352,7 +4358,7 @@ Future _handleScanSilentPayments(ScanData scanData) async { txInfo.unspents!.add(unspent); } - txInfo.amount += unspent.value; + txInfo.amount += Money.fromInt(unspent.value, txInfo.amount.currency); }); }); }); @@ -4422,8 +4428,8 @@ class EstimatedTxResult { final List utxos; final List inputPrivKeyInfos; final Map publicKeys; // PubKey to derivationPath - final int fee; - final int amount; + final Money fee; + final Money amount; final bool spendsSilentPayment; // final bool sendsToSilentPayment; diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index e692816b6..239fa491d 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -170,9 +170,22 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @computed List get allAddresses => _addresses; + @observable + bool addressRefreshToggle = false; + + @action + String getFreshAddress() { + addressRefreshToggle = !addressRefreshToggle; + return address; + } + + @override + String get addressForExchange => getFreshAddress(); + @override @computed String get address { + final _ = addressRefreshToggle; if (addressPageType == SilentPaymentsAddresType.p2sp) { if (activeSilentAddress != null) { return activeSilentAddress!; @@ -198,7 +211,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { ]; final typeMatchingReceiveAddressesAll = - typeMatchingAddressesAll.where((addr) => !addr.isUsed).toList(); + typeMatchingAddressesAll.where((addr) => !addr.isUsed && !hiddenAddresses.contains(addr.address)).toList(); final typeMatchingReceiveAddresses = [ ...typeMatchingReceiveAddressesAll.where((a) => !a.isLegacyDerivation), ...typeMatchingReceiveAddressesAll.where((a) => a.isLegacyDerivation), @@ -221,10 +234,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } final locked = lockedReceiveAddressByType[addressPageType]; - if (locked != null) return locked; + if (locked != null && !hiddenAddresses.contains(locked)) return locked; final prev = previousAddressRecord; - if (prev != null && prev.type == addressPageType && !prev.isUsed && !prev.isLegacyDerivation) { + if (prev != null && + prev.type == addressPageType && + !prev.isUsed && + !prev.isLegacyDerivation && + !hiddenAddresses.contains(prev.address)) { return prev.address; } diff --git a/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart index 810706374..67c822f01 100644 --- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart +++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart @@ -2,6 +2,8 @@ import 'dart:convert'; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/electrum_balance.dart'; +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_bitcoin/electrum_derivations.dart'; import 'package:cw_core/pathForWallet.dart'; @@ -92,9 +94,15 @@ class ElectrumWalletSnapshot { final useLightning = data['useLightning'] as bool? ?? true; final cachedLightningAddress = data['cachedLightningAddress'] as String?; - final balance = ElectrumBalance.fromJSON(data['balance'] as String?) ?? - ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0); - final lightningBalance = ElectrumBalance.fromJSON(data['lightningBalance'] as String?); + final currency = walletTypeToCryptoCurrency(type); + final balance = ElectrumBalance.fromJSON(data['balance'] as String?, currency) ?? + ElectrumBalance( + confirmed: Money.zero(currency), + unconfirmed: Money.zero(currency), + frozen: Money.zero(currency), + ); + final lightningBalance = + ElectrumBalance.fromJSON(data['lightningBalance'] as String?, currency); var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0}; var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0}; diff --git a/cw_bitcoin/lib/lightning/lightning_wallet.dart b/cw_bitcoin/lib/lightning/lightning_wallet.dart index 31ba4b60a..d78c88d13 100644 --- a/cw_bitcoin/lib/lightning/lightning_wallet.dart +++ b/cw_bitcoin/lib/lightning/lightning_wallet.dart @@ -6,6 +6,9 @@ import 'package:breez_sdk_spark_flutter/breez_sdk_spark.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_bitcoin/lightning/pending_lightning_transaction.dart'; +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/currency.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_type.dart'; @@ -20,7 +23,7 @@ class LightningWallet { final String apiKey; final String lnurlDomain; final Network network; - late BreezSdk sdk; + BreezSdk? _sdk; String? cachedAddress; @@ -38,6 +41,10 @@ class LightningWallet { static bool get isAvailable => Platform.isIOS || Platform.isAndroid || Platform.isMacOS; + Currency get currency => CryptoCurrency.btcln; + + BreezSdk get sdk => _sdk!; + StreamSubscription? _eventSubscription; Stream? _eventStream; @@ -47,9 +54,25 @@ class LightningWallet { void _subscribeToLogStream(File logFile) { _logSubscription = _logStream?.listen((logEntry) { - logFile.writeAsStringSync("[${logEntry.level}] ${logEntry.line}\n", mode: FileMode.append); + try { + // Check if file exists before writing (optional, but safer) + if (!logFile.existsSync()) { + logFile.createSync(recursive: true); + } + logFile.writeAsStringSync("[${logEntry.level}] ${logEntry.line}\n", mode: FileMode.append); + } catch (e) { + // Silently fail or use printV(e) so it doesn't crash the app + printV("Failed to write to log: $e"); + } }, onError: (e) { - logFile.writeAsStringSync("[ERROR] $e\n", mode: FileMode.append); + try { + if (!logFile.existsSync()) { + logFile.createSync(recursive: true); + } + logFile.writeAsStringSync("[ERROR] $e\n", mode: FileMode.append); + } catch (err) { + printV("Failed to write error to log: $err"); + } }); } @@ -76,7 +99,7 @@ class LightningWallet { storageDir: "$appPath/.breez/", ); - sdk = await connect(request: connectRequest); + _sdk = await connect(request: connectRequest); _eventStream ??= sdk.addEventListener().asBroadcastStream(); _logStream ??= initLogging().asBroadcastStream(); @@ -100,7 +123,7 @@ class LightningWallet { Future close() async { _eventSubscription?.cancel(); - await sdk.disconnect(); + await _sdk?.disconnect(); _logSubscription?.cancel(); } @@ -126,13 +149,14 @@ class LightningWallet { request: ReceivePaymentRequest(paymentMethod: ReceivePaymentMethod.bitcoinAddress()))) .paymentRequest; - Future getBalance() async { + Future getBalance() async { try { - return (await sdk.getInfo(request: GetInfoRequest(ensureSynced: true))).balanceSats; + return Money((await sdk.getInfo(request: GetInfoRequest(ensureSynced: true))).balanceSats, + CryptoCurrency.btcln); } on SdkError_Generic catch (_) { } on SdkError_NetworkError catch (_) {} - return BigInt.zero; + return Money.zero(CryptoCurrency.btcln); } Future registerAddress(String username) async { @@ -188,11 +212,16 @@ class LightningWallet { final lightningFeeSats = paymentMethod.lightningFeeSats; final sparkTransferFeeSats = paymentMethod.sparkTransferFeeSats; + final baseAmount = request.amount ?? amountSats; + final amount = baseAmount != null + ? Money(baseAmount, currency) + : Money(paymentMethod.invoiceDetails.amountMsat ?? BigInt.zero, currency) / + BigInt.from(1000); + return PendingLightningTransaction( id: paymentMethod.invoiceDetails.paymentHash, - amount: request.amount?.toInt() ?? amountSats?.toInt() ?? - ((paymentMethod.invoiceDetails.amountMsat?.toInt() ?? 0) / 1000).round(), - fee: lightningFeeSats.toInt() + (sparkTransferFeeSats?.toInt() ?? 0), + amount: amount, + fee: Money(lightningFeeSats + (sparkTransferFeeSats ?? BigInt.zero), currency), commitOverride: () async { try { final res = await sdk.sendPayment( @@ -230,12 +259,10 @@ class LightningWallet { final prepareResponse = await sdk.prepareLnurlPay(request: request); - final feeSats = prepareResponse.feeSats; - return PendingLightningTransaction( id: prepareResponse.invoiceDetails.paymentHash, - amount: prepareResponse.amountSats.toInt(), - fee: feeSats.toInt(), + amount: Money(prepareResponse.amountSats, currency), + fee: Money(prepareResponse.feeSats, currency), commitOverride: () async { final res = await sdk.lnurlPay(request: LnurlPayRequest(prepareResponse: prepareResponse)); @@ -256,27 +283,26 @@ class LightningWallet { final feeQuote = paymentMethod.feeQuote; OnchainConfirmationSpeed onchainConfirmationSpeed; - int fee; + BigInt fee; switch (priority) { case BitcoinTransactionPriority.fast: - fee = (feeQuote.speedFast.userFeeSat + feeQuote.speedFast.l1BroadcastFeeSat).toInt(); + fee = feeQuote.speedFast.userFeeSat + feeQuote.speedFast.l1BroadcastFeeSat; onchainConfirmationSpeed = OnchainConfirmationSpeed.fast; break; case BitcoinTransactionPriority.medium: - fee = - (feeQuote.speedMedium.userFeeSat + feeQuote.speedMedium.l1BroadcastFeeSat).toInt(); + fee = feeQuote.speedMedium.userFeeSat + feeQuote.speedMedium.l1BroadcastFeeSat; onchainConfirmationSpeed = OnchainConfirmationSpeed.medium; break; case BitcoinTransactionPriority.slow: default: - fee = (feeQuote.speedSlow.userFeeSat + feeQuote.speedSlow.l1BroadcastFeeSat).toInt(); + fee = feeQuote.speedSlow.userFeeSat + feeQuote.speedSlow.l1BroadcastFeeSat; onchainConfirmationSpeed = OnchainConfirmationSpeed.slow; } return PendingLightningTransaction( id: "", // ToDo: Find out where to get it - amount: prepareResponse.amount.toInt(), - fee: fee, + amount: Money(prepareResponse.amount, currency), + fee: Money(fee, currency), commitOverride: () async { final options = SendPaymentOptions.bitcoinAddress(confirmationSpeed: onchainConfirmationSpeed); @@ -404,7 +430,7 @@ class LightningWallet { } ElectrumTransactionInfo _getElectrumTransactionInfoFromPayment(Payment payment) { - TransactionDirection direction = TransactionDirection.outgoing; + var direction = TransactionDirection.outgoing; if (payment.paymentType == PaymentType.receive) { direction = TransactionDirection.incoming; @@ -416,10 +442,10 @@ class LightningWallet { return ElectrumTransactionInfo( WalletType.bitcoin, id: payment.id, - amount: payment.amount.toInt(), + amount: Money(payment.amount, currency), direction: direction, isPending: payment.status == PaymentStatus.pending, - fee: payment.fees.toInt(), + fee: Money(payment.fees, currency), date: DateTime.fromMillisecondsSinceEpoch(payment.timestamp.toInt() * 1000), confirmations: payment.status == PaymentStatus.pending ? 0 : 10, additionalInfo: {"isLightning": true}, @@ -430,10 +456,10 @@ class LightningWallet { return ElectrumTransactionInfo( WalletType.bitcoin, id: deposit.txid, - amount: deposit.amountSats.toInt(), + amount: Money(deposit.amountSats, currency), direction: TransactionDirection.incoming, isPending: true, - fee: 0, + fee: Money.zero(currency), date: DateTime.now(), confirmations: 0, additionalInfo: {"isLightning": true, "isSparkDeposit": true}, diff --git a/cw_bitcoin/lib/lightning/pending_lightning_transaction.dart b/cw_bitcoin/lib/lightning/pending_lightning_transaction.dart index d389949f9..d2bcabb1f 100644 --- a/cw_bitcoin/lib/lightning/pending_lightning_transaction.dart +++ b/cw_bitcoin/lib/lightning/pending_lightning_transaction.dart @@ -1,4 +1,4 @@ -import 'package:cw_bitcoin/bitcoin_amount_format.dart'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/pending_transaction.dart'; class PendingLightningTransaction with PendingTransaction { @@ -10,8 +10,7 @@ class PendingLightningTransaction with PendingTransaction { required this.commitOverride, }); - final int amount; - final int fee; + final bool isSendAll; Future Function() commitOverride; final List _listeners =[]; @@ -19,17 +18,17 @@ class PendingLightningTransaction with PendingTransaction { @override String id; + @override + final Money amount; + + @override + final Money fee; + @override String get hex => ""; @override - String get amountFormatted => bitcoinAmountToString(amount: amount); - - @override - String get feeFormatted => "$feeFormattedValue BTC"; - - @override - String get feeFormattedValue => bitcoinAmountToString(amount: fee); + String get amountFormatted => amount.toString(); @override int? get outputCount => 1; diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 3a363aa4a..51cc8a4d8 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -5,6 +5,7 @@ import 'package:collection/collection.dart'; import 'package:crypto/crypto.dart'; import 'package:cw_bitcoin/address_from_output.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/mweb_utxo.dart'; import 'package:cw_core/unspent_coin_type.dart'; @@ -17,12 +18,9 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/src/crypto/keypair/sign_utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:blockchain_utils/signer/ecdsa_signing_key.dart'; -import 'package:collection/collection.dart'; import 'package:convert/convert.dart' as convert; -import 'package:crypto/crypto.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; -import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart'; import 'package:cw_bitcoin/electrum_balance.dart'; @@ -35,25 +33,18 @@ import 'package:cw_bitcoin/litecoin_wallet_addresses.dart'; import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/psbt/transaction_builder.dart'; import 'package:cw_bitcoin/utils.dart'; -import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/encryption_file_utils.dart'; -import 'package:cw_core/mweb_utxo.dart'; -import 'package:cw_core/node.dart'; import 'package:cw_core/output_info.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_priority.dart'; -import 'package:cw_core/unspent_coin_type.dart'; import 'package:cw_core/unspent_coins_info.dart'; -import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_mweb/cw_mweb.dart'; -import 'package:cw_mweb/mwebd.pbgrpc.dart'; -import 'package:fixnum/fixnum.dart'; import 'package:flutter/foundation.dart'; import 'package:grpc/grpc.dart'; import 'package:hive/hive.dart'; @@ -587,8 +578,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { WalletType.litecoin, id: utxo.outputId, height: utxo.height, - amount: utxo.value.toInt(), - fee: 0, + amount: Money.fromInt(utxo.value, currency), + fee: Money.zero(currency), direction: TransactionDirection.incoming, isPending: utxo.height == 0, date: date, @@ -734,9 +725,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { Future checkMwebUtxosSpent() async { printV("checkMwebUtxosSpent() called!"); - if (!mwebEnabled) { - return; - } + if (!mwebEnabled) return; final pendingOutgoingTransactions = transactionHistory.transactions.values .where((tx) => tx.direction == TransactionDirection.outgoing && tx.isPending); @@ -763,8 +752,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { final height = await electrumClient.getCurrentBlockChainTip(); if (height == null || status.blockHeaderHeight != height) return; if (status.mwebUtxosHeight != height) return; // we aren't synced - int amount = 0; - Set inputAddresses = {}; + var amount = 0; + var inputAddresses = {}; var output = convert.AccumulatorSink(); var input = sha256.startChunkedConversion(output); @@ -777,8 +766,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { if (!inputAddresses.contains(utxo.address)) { addressRecord.txCount++; } - addressRecord.balance -= utxo.value.toInt(); - amount += utxo.value.toInt(); + addressRecord.balance -= utxo.value; + amount += utxo.value; inputAddresses.add(utxo.address); input.add(hex.decode(outputId)); } @@ -790,8 +779,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { WalletType.litecoin, id: digest.toString(), height: height, - amount: amount, - fee: 0, + amount: Money.fromInt(amount, currency), + fee: Money.zero(currency), direction: TransactionDirection.outgoing, isPending: false, date: DateTime.fromMillisecondsSinceEpoch(status.blockTime * 1000), @@ -921,10 +910,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { // update unspent balances: await updateUnspent(); - int confirmed = balance.confirmed; - int unconfirmed = balance.unconfirmed; - int confirmedMweb = 0; - int unconfirmedMweb = 0; + var confirmedMweb = 0; + var unconfirmedMweb = 0; try { mwebUtxosBox.values.forEach((utxo) { bool isConfirmed = utxo.height > 0; @@ -933,20 +920,20 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { "utxo: ${isConfirmed ? "confirmed" : "unconfirmed"} ${utxo.spent ? "spent" : "unspent"} ${utxo.outputId} ${utxo.height} ${utxo.value}"); if (isConfirmed) { - confirmedMweb += utxo.value.toInt(); + confirmedMweb += utxo.value; } if (isConfirmed && utxo.spent) { - unconfirmedMweb -= utxo.value.toInt(); + unconfirmedMweb -= utxo.value; } if (!isConfirmed && !utxo.spent) { - unconfirmedMweb += utxo.value.toInt(); + unconfirmedMweb += utxo.value; } }); } catch (_) {} - for (var addressRecord in walletAddresses.allAddresses) { + for (final addressRecord in walletAddresses.allAddresses) { addressRecord.balance = 0; addressRecord.txCount = 0; } @@ -990,11 +977,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { } return ElectrumBalance( - confirmed: confirmed, - unconfirmed: unconfirmed, - frozen: balance.frozen.toInt(), - secondConfirmed: confirmedMweb, - secondUnconfirmed: unconfirmedMweb, + confirmed: balance.confirmed, + unconfirmed: balance.unconfirmed, + frozen: balance.frozen, + secondConfirmed: Money.fromInt(confirmedMweb, currency), + secondUnconfirmed: Money.fromInt(unconfirmedMweb, currency), ); } @@ -1371,8 +1358,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { btcTx, type, electrumClient: electrumClient, - amount: 0, - fee: resp.fee.toInt(), + amount: Money.zero(currency), + fee: Money.fromInt(resp.fee.toInt(), currency), feeRate: "", network: network, hasChange: resp.recipient.length > 1, diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart index eee49591a..a4a4b7590 100644 --- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart @@ -228,13 +228,21 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with @override String get addressForExchange { // don't use mweb addresses for exchange refund address: - try { - final addresses = receiveAddresses - .where((element) => element.type == SegwitAddresType.p2wpkh && !element.isUsed); - return addresses.first.address; - } catch (_) { - return receiveAddresses.first.address; + + final current = getFreshAddress(); + final bool isMweb = receiveAddresses + .any((e) => e.address == current && e.type == SegwitAddresType.mweb); + + if (isMweb) { + final segwit = receiveAddresses + .where((e) => e.type == SegwitAddresType.p2wpkh && !e.isUsed && !e.isHidden && !hiddenAddresses.contains(e.address)) + .map((e) => e.address) + .toList(); + + return segwit.isNotEmpty ? segwit.first : current; } + + return current; } @override diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart index a9b741dc7..565234291 100644 --- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart +++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart @@ -1,7 +1,6 @@ -import 'dart:typed_data'; - import 'package:bbqrdart/bbqrdart.dart'; import 'package:cw_bitcoin/electrum_wallet.dart'; +import 'package:cw_core/amount/money.dart'; import 'package:flutter/foundation.dart'; import 'package:grpc/grpc.dart'; import 'package:cw_bitcoin/exceptions.dart'; @@ -9,7 +8,6 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_bitcoin/electrum.dart'; -import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/wallet_type.dart'; @@ -42,8 +40,6 @@ class PendingBitcoinTransaction with PendingTransaction { final WalletType type; final BtcTransaction _tx; final ElectrumClient electrumClient; - final int amount; - final int fee; final String feeRate; final BasedUtxoNetwork? network; final bool isSendAll; @@ -61,6 +57,12 @@ class PendingBitcoinTransaction with PendingTransaction { Uint8List? unsignedPsbt; + @override + final Money amount; + + @override + final Money fee; + @override String get id => idOverride ?? _tx.txId(); @@ -68,30 +70,7 @@ class PendingBitcoinTransaction with PendingTransaction { String get hex => hexOverride ?? _tx.serialize(); @override - String get amountFormatted => bitcoinAmountToString(amount: amount); - - @override - String get feeFormatted => "$feeFormattedValue ${_feeCurrency}"; - - String get _feeCurrency { - switch(type) { - case WalletType.bitcoin: - return "BTC"; - case WalletType.litecoin: - return "LTC"; - case WalletType.bitcoinCash: - return "BCH"; - case WalletType.dogecoin: - return "DOGE"; - default: - return type.name; - } - - } - - - @override - String get feeFormattedValue => bitcoinAmountToString(amount: fee); + String get amountFormatted => amount.toString(); @override int? get outputCount => _tx.outputs.length; @@ -155,14 +134,12 @@ class PendingBitcoinTransaction with PendingTransaction { Future _ltcCommit() async { try { - final resp = await CwMweb.broadcast( - BroadcastRequest(rawTx: BytesUtils.fromHexString(hex))); + final resp = await CwMweb.broadcast(BroadcastRequest(rawTx: BytesUtils.fromHexString(hex))); idOverride = resp.txid; } on GrpcError catch (e) { throw BitcoinTransactionCommitFailed(errorMessage: e.message); } catch (e) { - throw BitcoinTransactionCommitFailed( - errorMessage: "Unknown error: ${e.toString()}"); + throw BitcoinTransactionCommitFailed(errorMessage: "Unknown error: ${e.toString()}"); } } @@ -181,22 +158,23 @@ class PendingBitcoinTransaction with PendingTransaction { _listeners.forEach((listener) => listener(transactionInfo())); } - void addListener( - void Function(ElectrumTransactionInfo transaction) listener) => + void addListener(void Function(ElectrumTransactionInfo transaction) listener) => _listeners.add(listener); - ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type, - id: id, - height: 0, - amount: amount, - direction: TransactionDirection.outgoing, - date: DateTime.now(), - isPending: true, - isReplaced: false, - confirmations: 0, - inputAddresses: _tx.inputs.map((input) => input.txId).toList(), - outputAddresses: outputAddresses, - fee: fee); + ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo( + type, + id: id, + height: 0, + amount: amount, + direction: TransactionDirection.outgoing, + date: DateTime.now(), + isPending: true, + isReplaced: false, + confirmations: 0, + inputAddresses: _tx.inputs.map((input) => input.txId).toList(), + outputAddresses: outputAddresses, + fee: fee, + ); @override bool shouldCommitUR() => isViewOnly; @@ -207,8 +185,8 @@ class PendingBitcoinTransaction with PendingTransaction { var cborEncoder = CBOREncoder(); cborEncoder.encodeBytes(sourceBytes); var ur = UR("psbt", cborEncoder.getBytes()); - var urLegacy = UR("crypto-psbt", cborEncoder.getBytes()); - // var ur = UR("psbt", Uint8List.fromList(List.generate(64*1024, (int x) => x % 256))); + var urLegacy = UR("crypto-psbt", cborEncoder.getBytes()); + // var ur = UR("psbt", Uint8List.fromList(List.generate(64*1024, (int x) => x % 256))); var encoded = UREncoder(ur, 120); var encodedLegacy = UREncoder(urLegacy, 120); List values = []; @@ -230,8 +208,7 @@ class PendingBitcoinTransaction with PendingTransaction { return Future.value({ "Cupcake bcur": values.join("\n"), "ColdCard bbqr": bbqr.join("\n"), - if (kDebugMode) - "SeedSigner bcur legacy": valuesLegacy.join("\n"), + if (kDebugMode) "SeedSigner bcur legacy": valuesLegacy.join("\n"), }); } } diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index b7e893e90..b35b2bf09 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -5,23 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: "5b7468c326d2f8a4f630056404ca0d291ade42918f4a3c6233618e724f39da8e" url: "https://pub.dev" source: hosted - version: "76.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.3" + version: "92.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: "70e4b1ef8003c64793a9e268a551a82869a8a96f39deb73dea28084b0e8bf75e" url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "9.0.0" args: dependency: transitive description: @@ -151,10 +146,10 @@ packages: dependency: transitive description: name: build - sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 + sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "4.0.6" build_cli_annotations: dependency: transitive description: @@ -167,10 +162,10 @@ packages: dependency: transitive description: name: build_config - sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.3.0" build_daemon: dependency: transitive description: @@ -179,30 +174,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.4" - build_resolvers: - dependency: "direct dev" - description: - name: build_resolvers - sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 - url: "https://pub.dev" - source: hosted - version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" url: "https://pub.dev" source: hosted - version: "2.4.15" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" - url: "https://pub.dev" - source: hosted - version: "8.0.0" + version: "2.15.0" built_collection: dependency: transitive description: @@ -232,10 +211,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: @@ -326,10 +305,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" + sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b url: "https://pub.dev" source: hosted - version: "2.3.8" + version: "3.1.3" dart_varuint_bitcoin: dependency: transitive description: @@ -449,14 +428,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" glob: dependency: transitive description: @@ -506,18 +477,10 @@ packages: source: hosted version: "0.2.0" hive: - dependency: transitive - description: - name: hive - sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" - url: "https://pub.dev" - source: hosted - version: "2.2.3" - hive_generator: dependency: "direct dev" description: - name: hive_generator - sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" + name: hive + sha256: "5184b6ea455689754578267c31c3b0b329cccf20a5f2bfdab6941c29c3d3c09a" url: "https://pub.dev" source: hosted version: "2.0.1" @@ -589,26 +552,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" ledger_bitcoin: dependency: "direct main" description: @@ -651,38 +614,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.dev" - source: hosted - version: "0.1.3-main.0" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -695,26 +650,26 @@ packages: dependency: "direct main" description: name: mobx - sha256: bf1a90e5bcfd2851fc6984e20eef69557c65d9e4d0a88f5be4cf72c9819ce6b0 + sha256: "25ea9a3ee69243d578fb6288721d8eeb18550d0391c32f896d377d994154bff2" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" mobx_codegen: dependency: "direct dev" description: name: mobx_codegen - sha256: e0abbbc651a69550440f6b65c99ec222a1e2a4afd7baec8ba0f3088c7ca582a8 + sha256: "6529bcae074838004b58ad01fbe4966be182a176aacfb723686985986ebac19f" url: "https://pub.dev" source: hosted - version: "2.7.1" + version: "2.7.7" mockito: dependency: transitive description: name: mockito - sha256: f99d8d072e249f719a5531735d146d8cf04c580d93920b04de75bef6dfb2daf6 + sha256: eff30d002f0c8bf073b6f929df4483b543133fcafce056870163587b03f1d422 url: "https://pub.dev" source: hosted - version: "5.4.5" + version: "5.6.4" nested: dependency: transitive description: @@ -1000,18 +955,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 url: "https://pub.dev" source: hosted - version: "1.5.0" - source_helper: - dependency: transitive - description: - name: source_helper - sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" - url: "https://pub.dev" - source: hosted - version: "1.3.5" + version: "4.2.3" source_span: dependency: transitive description: @@ -1153,18 +1100,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.4" - timing: - dependency: transitive - description: - name: timing - sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" - url: "https://pub.dev" - source: hosted - version: "1.0.2" + version: "0.7.10" torch_dart: dependency: transitive description: @@ -1306,10 +1245,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -1383,5 +1322,5 @@ packages: source: hosted version: "2.2.2" sdks: - dart: ">=3.8.0 <4.0.0" + dart: ">=3.9.0 <4.0.0" flutter: ">=3.29.0" diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 28e99f483..b0752cf79 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -14,8 +14,8 @@ dependencies: sdk: flutter path_provider: ^2.0.11 http: ^1.1.0 - mobx: ^2.0.7+4 - flutter_mobx: ^2.0.6+1 + mobx: ^2.6.0 + flutter_mobx: ^2.3.0 intl: any shared_preferences: ^2.0.15 cw_core: @@ -77,10 +77,10 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - build_runner: ^2.4.15 - build_resolvers: ^2.4.4 - mobx_codegen: ^2.0.7 - hive_generator: ^2.0.1 + build_runner: ^2.15.0 + mobx_codegen: ^2.7.6 +# hive_generator: ^2.0.1 + hive: 2.0.1 dependency_overrides: watcher: ^1.1.0 diff --git a/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart b/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart deleted file mode 100644 index 24dc1970d..000000000 --- a/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'package:cw_bitcoin/exceptions.dart'; -import 'package:bitbox/bitbox.dart' as bitbox; -import 'package:cw_core/pending_transaction.dart'; -import 'package:cw_bitcoin/electrum.dart'; -import 'package:cw_bitcoin/bitcoin_amount_format.dart'; -import 'package:cw_bitcoin/electrum_transaction_info.dart'; -import 'package:cw_core/transaction_direction.dart'; -import 'package:cw_core/wallet_type.dart'; - -class PendingBitcoinCashTransaction with PendingTransaction { - PendingBitcoinCashTransaction(this._tx, this.type, - {required this.electrumClient, - required this.amount, - required this.fee, - required this.hasChange, - required this.isSendAll}) - : _listeners = []; - - final WalletType type; - final bitbox.Transaction _tx; - final ElectrumClient electrumClient; - final int amount; - final int fee; - final bool hasChange; - final bool isSendAll; - - @override - String get id => _tx.getId(); - - @override - String get hex => _tx.toHex(); - - @override - String get amountFormatted => bitcoinAmountToString(amount: amount); - - @override - String get feeFormatted => "$feeFormattedValue BCH"; - - @override - String get feeFormattedValue => bitcoinAmountToString(amount: fee); - - final List _listeners; - - @override - Future commit() async { - int? callId; - - final result = await electrumClient.broadcastTransaction( - transactionRaw: hex, idCallback: (id) => callId = id); - - if (result.isEmpty) { - if (callId != null) { - final error = electrumClient.getErrorMessage(callId!); - - if (error.contains("dust")) { - if (hasChange) { - throw BitcoinTransactionCommitFailedDustChange(); - } else if (!isSendAll) { - throw BitcoinTransactionCommitFailedDustOutput(); - } else { - throw BitcoinTransactionCommitFailedDustOutputSendAll(); - } - } - - if (error.contains("bad-txns-vout-negative")) { - throw BitcoinTransactionCommitFailedVoutNegative(); - } - throw BitcoinTransactionCommitFailed(errorMessage: error); - } - - throw BitcoinTransactionCommitFailed(); - } - - _listeners.forEach((listener) => listener(transactionInfo())); - } - - void addListener(void Function(ElectrumTransactionInfo transaction) listener) => - _listeners.add(listener); - - ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type, - id: id, - height: 0, - amount: amount, - direction: TransactionDirection.outgoing, - date: DateTime.now(), - isPending: true, - confirmations: 0, - fee: fee, - isReplaced: false, - ); - @override - Future> commitUR() { - throw UnimplementedError(); - } -} diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index 7ddecdf94..ec6e6bebc 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -15,8 +15,8 @@ dependencies: bip39: ^1.0.6 bip32: ^2.0.0 path_provider: ^2.0.11 - mobx: ^2.0.7+4 - flutter_mobx: ^2.0.6+1 + mobx: ^2.6.0 + flutter_mobx: ^2.3.0 cw_core: path: ../cw_core cw_bitcoin: @@ -33,9 +33,10 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - build_runner: ^2.4.15 - mobx_codegen: ^2.0.7 - hive_generator: ^2.0.1 + build_runner: ^2.15.0 + mobx_codegen: ^2.7.6 +# hive_generator: ^2.0.1 + hive: 2.0.1 dependency_overrides: watcher: ^1.1.0 diff --git a/cw_core/lib/address_info.dart b/cw_core/lib/address_info.dart index 63dc023ab..8d2ec6ebf 100644 --- a/cw_core/lib/address_info.dart +++ b/cw_core/lib/address_info.dart @@ -1,21 +1,21 @@ import 'package:cw_core/hive_type_ids.dart'; import 'package:hive/hive.dart'; -part 'address_info.g.dart'; +part 'address_info.part.dart'; -@HiveType(typeId: ADDRESS_INFO_TYPE_ID) +// @HiveType(typeId: ADDRESS_INFO_TYPE_ID) class AddressInfo extends HiveObject { AddressInfo({required this.address, this.accountIndex, required this.label}); static const typeId = ADDRESS_INFO_TYPE_ID; static const boxName = 'AddressInfo'; - @HiveField(0) + // @HiveField(0) int? accountIndex; - @HiveField(1, defaultValue: '') + // @HiveField(1, defaultValue: '') String address; - @HiveField(2, defaultValue: '') + // @HiveField(2, defaultValue: '') String label; } diff --git a/cw_core/lib/address_info.part.dart b/cw_core/lib/address_info.part.dart new file mode 100644 index 000000000..75b2247fa --- /dev/null +++ b/cw_core/lib/address_info.part.dart @@ -0,0 +1,47 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'address_info.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class AddressInfoAdapter extends TypeAdapter { + @override + final int typeId = 11; + + @override + AddressInfo read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return AddressInfo( + address: fields[1] == null ? '' : fields[1] as String, + accountIndex: fields[0] as int?, + label: fields[2] == null ? '' : fields[2] as String, + ); + } + + @override + void write(BinaryWriter writer, AddressInfo obj) { + writer + ..writeByte(3) + ..writeByte(0) + ..write(obj.accountIndex) + ..writeByte(1) + ..write(obj.address) + ..writeByte(2) + ..write(obj.label); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is AddressInfoAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/cw_core/lib/amount/amount_sanitizer.dart b/cw_core/lib/amount/amount_sanitizer.dart new file mode 100644 index 000000000..6504d1bd9 --- /dev/null +++ b/cw_core/lib/amount/amount_sanitizer.dart @@ -0,0 +1,16 @@ +String _smartAmountSanitizer(String amount) { + if (amount.contains(RegExp(r'^(?=.*[.])(?=.*,).*$'))) { + if (amount.indexOf(".") > amount.indexOf(",")) { + amount = amount.replaceAll(",", ""); + } else { + amount = amount.replaceAll(".", ""); + } + } + + return amount.replaceAll(",", "."); +} + + +extension SanitizerUserAmounts on String { + String sanitized() => _smartAmountSanitizer(this); +} diff --git a/cw_core/lib/amount/money.dart b/cw_core/lib/amount/money.dart new file mode 100644 index 000000000..3ff5af928 --- /dev/null +++ b/cw_core/lib/amount/money.dart @@ -0,0 +1,211 @@ +import 'package:cw_core/crypto_amount_format.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/currency.dart'; +import 'package:cw_core/format_fixed.dart'; +import 'package:cw_core/parse_fixed.dart'; + +class Money implements Comparable { + final BigInt amount; + final Currency currency; + + const Money(this.amount, this.currency); + + factory Money.zero(Currency currency) => Money(BigInt.zero, currency); + + factory Money.fromInt(int amount, Currency currency) => Money(BigInt.from(amount), currency); + + /// Parse the [source] and turn it into [Money] + /// + /// Throws a [FormatException] if the [source] is not a valid decimal or + /// not in canonical representation or if it is a decimal when [isBaseUnit] + factory Money.parse(source, Currency currency, {bool isBaseUnit = false}) { + final amount = isBaseUnit + ? BigInt.parse(source.toString()) + : parseFixed(source.toString(), currency.decimals); + + return Money(amount, currency); + } + + /// Parse the [source] and turn it into [Money] if possible + /// + /// As [parse] except that this method returns `null` if the input is not + /// valid or if it is a decimal when [isBaseUnit] + static Money? tryParse(source, Currency currency, {bool isBaseUnit = false}) { + final amount = isBaseUnit + ? BigInt.tryParse(source.toString()) + : tryParseFixed(source.toString(), currency.decimals); + + return amount != null ? Money(amount, currency) : null; + } + + /// Returns the sign of this [BigInt] amount. + /// Returns 0 for zero, -1 for values less than zero and +1 for values + /// greater than zero. + int get sign => amount.sign; + + /// Returns `true` when amount of this money is zero. + bool get isZero => amount == BigInt.zero; + + /// Returns `true` when amount of this money is negative. + bool get isNegative => amount.isNegative; + + /// Compares this to [other]. + /// + /// [other] has to be in same currency, [ArgumentError] will be thrown + /// otherwise. + @override + int compareTo(Money other) { + _assertSameCurrency(other); + + return amount.compareTo(other.amount); + } + + /// Returns `true` if [other] is the same amount of money in + /// the same currency. + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Money && other.currency == currency && other.amount == amount); + + /// Returns `true` when this money is less than [other]. + /// + /// Both operands have to be in same currency, [ArgumentError] will be thrown + /// otherwise. + bool operator <(Money other) { + _assertSameCurrency(other, "Cannot compare money in different currencies."); + + return amount < other.amount; + } + + /// Returns `true` when this money is less than or equal to [other]. + /// + /// Both operands have to be in same currency, [ArgumentError] will be thrown + /// otherwise. + bool operator <=(Money other) { + _assertSameCurrency(other, "Cannot compare money in different currencies."); + + return amount <= other.amount; + } + + /// Returns `true` when this money is greater than [other]. + /// + /// Both operands have to be in same currency, [ArgumentError] will be thrown + /// otherwise. + bool operator >(Money other) { + _assertSameCurrency(other, "Cannot compare money in different currencies."); + + return amount > other.amount; + } + + /// Returns `true` when this money is greater than or equal to [other]. + /// + /// Both operands have to be in same currency, [ArgumentError] will be thrown + /// otherwise. + bool operator >=(Money other) { + _assertSameCurrency(other, "Cannot compare money in different currencies."); + + return amount >= other.amount; + } + + /// Adds the amount of [other] to this amount. + /// + /// Both operands must be in same currency, [ArgumentError] will be thrown + /// otherwise. + Money operator +(Money other) { + _assertSameCurrency(other); + + return _withAmount(amount + other.amount); + } + + /// unary minus operator. + Money operator -() => _withAmount(-amount); + + /// Subtracts the amount of [other] from this amount. + /// + /// Both operands must be in same currency, [ArgumentError] will be thrown + /// otherwise. + Money operator -(Money other) { + _assertSameCurrency(other); + + return _withAmount(amount - other.amount); + } + + /// Returns [Money] multiplied by [other]. + /// + /// The result is again [Money]. + Money operator *(BigInt other) => _withAmount(amount * other); + + /// Returns [Money] divided by [other]. + /// + /// The result is again [Money]. + Money operator /(BigInt other) { + if (other == BigInt.zero) throw Exception('Division by zero.'); + + final neg = (amount.isNegative) ^ (other.isNegative); + final A = amount.abs(); + final B = other.abs(); + + final q = A ~/ B; + final r = A % B; + + // Compare 2*r with B to detect half/up/down + final twiceR = r << 1; + final mag = (twiceR >= B) ? q + BigInt.one : q; + + return _withAmount(neg ? -mag : mag); + } + + /// Creates a copy of this [Money] object with optional new values + /// for [amount], [currency]. + /// + /// If just [currency] is provided and the decimals missmatch + /// the amount will be transformed to keep its canonical representation + Money copyWith({BigInt? amount, Currency? currency}) { + if (currency != null && amount == null && currency.decimals != this.currency.decimals) { + return Money( + _transformAmount(this.amount, this.currency.decimals, currency.decimals), currency); + } + + return Money(amount ?? this.amount, currency ?? this.currency); + } + + /// Creates new instance with the same currency and given [amount]. + Money _withAmount(BigInt amount) => Money(amount, currency); + + void _assertSameCurrency(Money other, [String? message]) { + if (currency != other.currency) + throw ArgumentError(message ?? "Cannot operate with money values in different currencies."); + } + + BigInt _transformAmount(BigInt source, int sourceDecimals, int targetDecimals) { + if (sourceDecimals == targetDecimals) return source; + + if (sourceDecimals > targetDecimals) { + return parseFixed( + formatFixed(source, sourceDecimals).withMaxDecimals(targetDecimals), targetDecimals); + } else { + return parseFixed(formatFixed(source, sourceDecimals), targetDecimals); + } + } + + @override + int get hashCode => amount.hashCode ^ currency.hashCode; + + @override + String toString() => formatFixed(amount, currency.decimals); + + String toStringWithSymbol( + {int? fractionalDigits, bool trimZeros = true, bool useBaseUnit = false}) => + "${toStringWithPrecision(fractionalDigits: fractionalDigits, trimZeros: trimZeros, useBaseUnit: useBaseUnit)} ${_getSymbol(useBaseUnit)}"; + + String toStringWithPrecision( + {int? fractionalDigits, bool trimZeros = true, bool useBaseUnit = false}) => + formatFixed(amount, useBaseUnit ? 0 : currency.decimals, + fractionalDigits: fractionalDigits, trimZeros: trimZeros); + + // To Override the symbol with the ticker of the base unit + String _getSymbol(bool useBaseUnit) { + if (useBaseUnit && [CryptoCurrency.btc, CryptoCurrency.btcln].contains(currency)) return "sats"; + return currency.symbol; + } +} diff --git a/cw_core/lib/amount_converter.dart b/cw_core/lib/amount_converter.dart index 3c48cde09..850dd9b10 100644 --- a/cw_core/lib/amount_converter.dart +++ b/cw_core/lib/amount_converter.dart @@ -1,78 +1,4 @@ -import 'package:decimal/decimal.dart'; -import 'package:decimal/intl.dart'; -import 'package:intl/intl.dart'; -import 'package:cw_core/crypto_currency.dart'; - class AmountConverter { - static const _moneroAmountLength = 12; - static const _moneroAmountDivider = 1000000000000; - static const _wowneroAmountLength = 11; - static const _wowneroAmountDivider = 100000000000; - static const _bitcoinAmountDivider = 100000000; - static const _bitcoinAmountLength = 8; - static final _bitcoinAmountFormat = NumberFormat() - ..maximumFractionDigits = _bitcoinAmountLength - ..minimumFractionDigits = 1; - static final _moneroAmountFormat = NumberFormat() - ..maximumFractionDigits = _moneroAmountLength - ..minimumFractionDigits = 1; - static final _wowneroAmountFormat = NumberFormat() - ..maximumFractionDigits = _wowneroAmountLength - ..minimumFractionDigits = 1; - - static String amountIntToString(CryptoCurrency cryptoCurrency, int amount) { - switch (cryptoCurrency) { - case CryptoCurrency.xmr: - return _moneroAmountToString(amount); - case CryptoCurrency.wow: - return _wowneroAmountToString(amount); - case CryptoCurrency.btc: - case CryptoCurrency.bch: - case CryptoCurrency.ltc: - case CryptoCurrency.dcr: - case CryptoCurrency.doge: - return _bitcoinAmountToString(amount); - case CryptoCurrency.xhv: - case CryptoCurrency.xag: - case CryptoCurrency.xau: - case CryptoCurrency.xaud: - case CryptoCurrency.xbtc: - case CryptoCurrency.xcad: - case CryptoCurrency.xchf: - case CryptoCurrency.xcny: - case CryptoCurrency.xeur: - case CryptoCurrency.xgbp: - case CryptoCurrency.xjpy: - case CryptoCurrency.xnok: - case CryptoCurrency.xnzd: - case CryptoCurrency.xusd: - return _moneroAmountToString(amount); - case CryptoCurrency.zano: - return _moneroAmountToStringUsingDecimals(amount); - default: - return ''; - } - } - - static double cryptoAmountToDouble({required num amount, required num divider}) => - amount / divider; - - static String _moneroAmountToString(int amount) => _moneroAmountFormat - .format(cryptoAmountToDouble(amount: amount, divider: _moneroAmountDivider)); - - static String _bitcoinAmountToString(int amount) => _bitcoinAmountFormat - .format(cryptoAmountToDouble(amount: amount, divider: _bitcoinAmountDivider)); - - static String _wowneroAmountToString(int amount) => _wowneroAmountFormat - .format(cryptoAmountToDouble(amount: amount, divider: _wowneroAmountDivider)); - - static Decimal cryptoAmountToDecimal({required int amount, required int divider}) => - (Decimal.fromInt(amount) / Decimal.fromInt(divider)).toDecimal(); - - static String _moneroAmountToStringUsingDecimals(int amount) => _moneroAmountFormat.format( - DecimalIntl(cryptoAmountToDecimal(amount: amount, divider: _moneroAmountDivider))); - - static String toBaseUnits(String amount, int decimals) { amount = amount.trim(); if (amount.isEmpty) return '0'; diff --git a/cw_core/lib/balance.dart b/cw_core/lib/balance.dart index 8c7820dba..4e400b93e 100644 --- a/cw_core/lib/balance.dart +++ b/cw_core/lib/balance.dart @@ -1,26 +1,20 @@ +import 'package:cw_core/amount/money.dart'; + /// Balance Data class with all amounts in the lowest possible currency (e.g. satoshis or wei) abstract class Balance { - const Balance(this.available, this.additional, {this.secondAvailable, this.secondAdditional, this.frozen}); + const Balance( + this.available, + this.unavailable, { + this.secondAvailable, + this.secondUnavailable, + this.frozen, + }); - Balance.fromInt(int available, int additional, {int? secondAvailable, int? secondAdditional, int? frozen}) : - available = BigInt.from(available), - additional = BigInt.from(additional), - secondAvailable = secondAvailable == null ? null : BigInt.from(secondAvailable), - secondAdditional = secondAdditional == null ? null : BigInt.from(secondAdditional), - frozen = frozen == null ? null : BigInt.from(frozen) {} + final Money available; + final Money unavailable; + final Money? secondAvailable; + final Money? secondUnavailable; - - final BigInt available; - final BigInt additional; - - final BigInt? secondAvailable; - final BigInt? secondAdditional; - - final BigInt? frozen; - - BigInt get fullAvailableBalance => available; - - @deprecated - String get formattedUnAvailableBalance => ''; + final Money? frozen; } diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 0baeabcec..cdf8283f4 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -1,9 +1,9 @@ +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/currency.dart'; import 'package:cw_core/currency_groups.dart'; import 'package:cw_core/enumerable_item.dart'; import 'package:collection/collection.dart'; import 'package:cw_core/format_fixed.dart'; -import 'package:cw_core/parse_fixed.dart'; class CryptoCurrency extends EnumerableItem with Serializable implements Currency { const CryptoCurrency({ @@ -21,18 +21,30 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen this.groups = const {}, }) : super(title: title, raw: raw); - final String name; - final String? tag; - final String? fullName; - final String? iconPath; final String? flatIconPath; final String? chainIconPath; - @override - final int decimals; final bool enabled; final bool isPotentialScam; final Set groups; + @override + final String name; + + @override + final String? fullName; + + @override + final String? tag; + + @override + final String? iconPath; + + @override + final int decimals; + + @override + String get symbol => title; + set enabled(bool value) => this.enabled = value; static const all = [ @@ -432,13 +444,13 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen bool titleAndTagEqual(CryptoCurrency other) => title == other.title && tag == other.tag; + @override + Money parseAmount(String value) => Money.parse(value, this); + + @override + Money? tryParseAmount(String value) => Money.tryParse(value, this); + /// Format the raw amount into its decimal representation eg. turn Sats into Bitcoin String formatAmount(BigInt amount, {int? fractionalDigits, bool trimZeros = true}) => formatFixed(amount, decimals, fractionalDigits: fractionalDigits, trimZeros: trimZeros); - - /// Parse the [value] and turn it into the smallest denomination eg. turn Bitcoin into Sats - BigInt parseAmount(String value) => parseFixed(value, decimals); - - /// Try parsing the [value] and turn it into the smallest denomination eg. turn Bitcoin into Sats - BigInt? tryParseAmount(String value) => tryParseFixed(value, decimals); } diff --git a/cw_core/lib/currency.dart b/cw_core/lib/currency.dart index f43455a3e..75fa854b7 100644 --- a/cw_core/lib/currency.dart +++ b/cw_core/lib/currency.dart @@ -1,7 +1,23 @@ +import 'package:cw_core/amount/money.dart'; + abstract class Currency { + String get symbol; String get name; - String? get tag; String? get fullName; String? get iconPath; int get decimals; + String? get tag; + + /// Parse the [value] and turn it into [Money] + Money parseAmount(String value); + + /// Try parsing the [value] and turn it into [Money] + Money? tryParseAmount(String value); + + @override + bool operator ==(Object other) => + other is Currency && other.decimals == decimals && other.symbol == symbol && other.tag == tag; + + @override + int get hashCode => decimals.hashCode ^ symbol.hashCode ^ tag.hashCode; } diff --git a/cw_core/lib/enumerable_item.dart b/cw_core/lib/enumerable_item.dart index e7a863909..3eac165be 100644 --- a/cw_core/lib/enumerable_item.dart +++ b/cw_core/lib/enumerable_item.dart @@ -1,5 +1,3 @@ -import 'package:flutter/foundation.dart'; - abstract class EnumerableItem { const EnumerableItem({required this.title, required this.raw}); diff --git a/cw_core/lib/enumerate.dart b/cw_core/lib/enumerate.dart index d92347e91..70fc2e7a6 100644 --- a/cw_core/lib/enumerate.dart +++ b/cw_core/lib/enumerate.dart @@ -2,7 +2,7 @@ abstract class Enumerate { String get value; @override - operator ==(other) { + bool operator ==(other) { if (identical(other, this)) return true; if (other is! Enumerate) return false; return other.runtimeType == runtimeType && value == other.value; diff --git a/cw_core/lib/erc20_token.dart b/cw_core/lib/erc20_token.dart index e19cbc63f..d5fde7aa2 100644 --- a/cw_core/lib/erc20_token.dart +++ b/cw_core/lib/erc20_token.dart @@ -2,25 +2,25 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/hive_type_ids.dart'; import 'package:hive/hive.dart'; -part 'erc20_token.g.dart'; +part 'erc20_token.part.dart'; -@HiveType(typeId: Erc20Token.typeId) +// @HiveType(typeId: Erc20Token.typeId) class Erc20Token extends CryptoCurrency with HiveObjectMixin { - @HiveField(0) + // @HiveField(0) final String name; - @HiveField(1) + // @HiveField(1) final String symbol; - @HiveField(2) + // @HiveField(2) final String contractAddress; - @HiveField(3) + // @HiveField(3) final int decimal; - @HiveField(4, defaultValue: true) + // @HiveField(4, defaultValue: true) bool _enabled; - @HiveField(5) + // @HiveField(5) String? iconPath; - @HiveField(6) + // @HiveField(6) final String? tag; - @HiveField(7, defaultValue: false) + // @HiveField(7, defaultValue: false) bool isPotentialScam; bool get enabled => _enabled; diff --git a/cw_core/lib/erc20_token.part.dart b/cw_core/lib/erc20_token.part.dart new file mode 100644 index 000000000..14c61b5b1 --- /dev/null +++ b/cw_core/lib/erc20_token.part.dart @@ -0,0 +1,61 @@ + // GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'erc20_token.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class Erc20TokenAdapter extends TypeAdapter { + @override + final int typeId = 12; + + @override + Erc20Token read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return Erc20Token( + name: fields[0] as String, + symbol: fields[1] as String, + contractAddress: fields[2] as String, + decimal: fields[3] as int, + iconPath: fields[5] as String?, + tag: fields[6] as String?, + isPotentialScam: fields[7] == null ? false : fields[7] as bool, + ).._enabled = fields[4] == null ? true : fields[4] as bool; + } + + @override + void write(BinaryWriter writer, Erc20Token obj) { + writer + ..writeByte(8) + ..writeByte(0) + ..write(obj.name) + ..writeByte(1) + ..write(obj.symbol) + ..writeByte(2) + ..write(obj.contractAddress) + ..writeByte(3) + ..write(obj.decimal) + ..writeByte(4) + ..write(obj._enabled) + ..writeByte(5) + ..write(obj.iconPath) + ..writeByte(6) + ..write(obj.tag) + ..writeByte(7) + ..write(obj.isPotentialScam); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Erc20TokenAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/cw_core/lib/exceptions.dart b/cw_core/lib/exceptions.dart index 35999b78b..b6155529c 100644 --- a/cw_core/lib/exceptions.dart +++ b/cw_core/lib/exceptions.dart @@ -26,9 +26,7 @@ class TransactionCommitFailed implements Exception { TransactionCommitFailed({this.errorMessage}); @override - String toString() { - return errorMessage ?? "unknown error"; - } + String toString() => errorMessage ?? "unknown error"; } class TransactionCommitFailedDustChange implements Exception {} @@ -57,10 +55,6 @@ class SignSPLTokenTransactionRentException implements Exception {} class NoAssociatedTokenAccountException implements Exception {} - -/// ============================================================================== -/// ============================================================================== - class RestoreFromSeedException implements Exception { final String message; diff --git a/cw_core/lib/lnurl.dart b/cw_core/lib/lnurl.dart index 03be284d5..7676daf7c 100644 --- a/cw_core/lib/lnurl.dart +++ b/cw_core/lib/lnurl.dart @@ -1,6 +1,8 @@ import 'dart:convert'; import 'package:bech32/bech32.dart'; +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; const _BOLT_PREFIXES = ["lnbcrt", "lntbs", "lnbc", "lntb"]; @@ -61,14 +63,14 @@ int? _getAmountBolt11Msat(String invoice) { } } -int? getBolt11Amount(String invoice) { +Money? getBolt11Amount(String invoice) { final msat = _getAmountBolt11Msat(invoice); if (msat == null || msat % 1000 != 0) return null; - return msat ~/ 1000; + return Money.fromInt(msat ~/ 1000, CryptoCurrency.btcln); } class LNURL { - static Future getPayRequestAmount(String lnurl) async { + static Future getPayRequestAmount(String lnurl) async { final url = decode(lnurl); final response = await ProxyWrapper().get(clearnetUri: url); @@ -88,7 +90,7 @@ class LNURL { return null; if (msat == null || msat % 1000 != 0) return null; - return msat ~/ 1000; + return Money.fromInt(msat ~/ 1000, CryptoCurrency.btcln); } catch (_) { return null; } diff --git a/cw_core/lib/monero_balance.dart b/cw_core/lib/monero_balance.dart deleted file mode 100644 index 3a85d3cb9..000000000 --- a/cw_core/lib/monero_balance.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:cw_core/balance.dart'; -import 'package:cw_core/monero_amount_format.dart'; - -class MoneroBalance extends Balance { - MoneroBalance({required this.fullBalance, required this.unlockedBalance, this.frozenBalance = 0}) - : formattedFrozenBalance = moneroAmountToString(amount: frozenBalance), - super.fromInt(unlockedBalance, fullBalance - unlockedBalance, frozen: frozenBalance); - - final int fullBalance; - final int unlockedBalance; - final int frozenBalance; - final String formattedFrozenBalance; - - @override - String get formattedUnAvailableBalance => - formattedFrozenBalance == '0.0' ? '' : formattedFrozenBalance; -} diff --git a/cw_core/lib/mweb_utxo.dart b/cw_core/lib/mweb_utxo.dart index 5eab11734..ec5679eb3 100644 --- a/cw_core/lib/mweb_utxo.dart +++ b/cw_core/lib/mweb_utxo.dart @@ -1,9 +1,9 @@ import 'package:cw_core/hive_type_ids.dart'; import 'package:hive/hive.dart'; -part 'mweb_utxo.g.dart'; +part 'mweb_utxo.part.dart'; -@HiveType(typeId: MWEB_UTXO_TYPE_ID) +// @HiveType(typeId: MWEB_UTXO_TYPE_ID) class MwebUtxo extends HiveObject { MwebUtxo({ required this.height, @@ -17,21 +17,21 @@ class MwebUtxo extends HiveObject { static const typeId = MWEB_UTXO_TYPE_ID; static const boxName = 'MwebUtxo'; - @HiveField(0) + // @HiveField(0) int height; - @HiveField(1) + // @HiveField(1) int value; - @HiveField(2) + // @HiveField(2) String address; - @HiveField(3) + // @HiveField(3) String outputId; - @HiveField(4) + // @HiveField(4) int blockTime; - @HiveField(5, defaultValue: false) + // @HiveField(5, defaultValue: false) bool spent; } diff --git a/cw_core/lib/mweb_utxo.part.dart b/cw_core/lib/mweb_utxo.part.dart new file mode 100644 index 000000000..fedc7196b --- /dev/null +++ b/cw_core/lib/mweb_utxo.part.dart @@ -0,0 +1,56 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'mweb_utxo.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class MwebUtxoAdapter extends TypeAdapter { + @override + final int typeId = 20; + + @override + MwebUtxo read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return MwebUtxo( + height: fields[0] as int, + value: fields[1] as int, + address: fields[2] as String, + outputId: fields[3] as String, + blockTime: fields[4] as int, + spent: fields[5] == null ? false : fields[5] as bool, + ); + } + + @override + void write(BinaryWriter writer, MwebUtxo obj) { + writer + ..writeByte(6) + ..writeByte(0) + ..write(obj.height) + ..writeByte(1) + ..write(obj.value) + ..writeByte(2) + ..write(obj.address) + ..writeByte(3) + ..write(obj.outputId) + ..writeByte(4) + ..write(obj.blockTime) + ..writeByte(5) + ..write(obj.spent); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is MwebUtxoAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/cw_core/lib/nano_account.dart b/cw_core/lib/nano_account.dart index 91df62e75..dcf2710da 100644 --- a/cw_core/lib/nano_account.dart +++ b/cw_core/lib/nano_account.dart @@ -1,23 +1,23 @@ import 'package:cw_core/hive_type_ids.dart'; import 'package:hive/hive.dart'; -part 'nano_account.g.dart'; +part 'nano_account.part.dart'; -@HiveType(typeId: NanoAccount.typeId) +// @HiveType(typeId: NanoAccount.typeId) class NanoAccount extends HiveObject { NanoAccount({required this.label, required this.id, this.balance, this.isSelected = false}); static const typeId = NANO_ACCOUNT_TYPE_ID; - @HiveField(0) + // @HiveField(0) String label; - @HiveField(1) + // @HiveField(1) final int id; - @HiveField(2) + // @HiveField(2) bool isSelected; - @HiveField(3) + // @HiveField(3) String? balance; } diff --git a/cw_core/lib/nano_account.part.dart b/cw_core/lib/nano_account.part.dart new file mode 100644 index 000000000..49b1e63a7 --- /dev/null +++ b/cw_core/lib/nano_account.part.dart @@ -0,0 +1,50 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'nano_account.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class NanoAccountAdapter extends TypeAdapter { + @override + final int typeId = 13; + + @override + NanoAccount read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return NanoAccount( + label: fields[0] as String, + id: fields[1] as int, + balance: fields[3] as String?, + isSelected: fields[2] as bool, + ); + } + + @override + void write(BinaryWriter writer, NanoAccount obj) { + writer + ..writeByte(4) + ..writeByte(0) + ..write(obj.label) + ..writeByte(1) + ..write(obj.id) + ..writeByte(2) + ..write(obj.isSelected) + ..writeByte(3) + ..write(obj.balance); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is NanoAccountAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/cw_core/lib/node_legacy.dart b/cw_core/lib/node_legacy.dart index 54d9340ac..7d36ddd2f 100644 --- a/cw_core/lib/node_legacy.dart +++ b/cw_core/lib/node_legacy.dart @@ -16,7 +16,7 @@ import 'package:crypto/crypto.dart'; import 'cake_hive.dart'; import 'node.dart' as node_new; -part "node_legacy.g.dart"; +part 'node_legacy.part.dart'; Future performNodeHiveMigration() async { if(!CakeHive.isAdapterRegistered(Node.typeId)) { diff --git a/cw_core/lib/node_legacy.part.dart b/cw_core/lib/node_legacy.part.dart new file mode 100644 index 000000000..9550c1d34 --- /dev/null +++ b/cw_core/lib/node_legacy.part.dart @@ -0,0 +1,78 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'node_legacy.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class NodeAdapter extends TypeAdapter { + @override + final int typeId = 1; + + @override + Node read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return Node( + label: fields[12] == null ? '' : fields[12] as String?, + login: fields[1] as String?, + password: fields[2] as String?, + useSSL: fields[4] as bool?, + trusted: fields[5] == null ? false : fields[5] as bool, + socksProxyAddress: fields[6] as String?, + path: fields[7] == null ? '' : fields[7] as String?, + isEnabledForAutoSwitching: + fields[11] == null ? false : fields[11] as bool, + ) + ..uriRaw = fields[0] == null ? '' : fields[0] as String + ..typeRaw = fields[3] == null ? 0 : fields[3] as int + ..isElectrs = fields[8] as bool? + ..supportsSilentPayments = fields[9] as bool? + ..supportsMweb = fields[10] as bool?; + } + + @override + void write(BinaryWriter writer, Node obj) { + writer + ..writeByte(13) + ..writeByte(0) + ..write(obj.uriRaw) + ..writeByte(1) + ..write(obj.login) + ..writeByte(2) + ..write(obj.password) + ..writeByte(3) + ..write(obj.typeRaw) + ..writeByte(4) + ..write(obj.useSSL) + ..writeByte(5) + ..write(obj.trusted) + ..writeByte(6) + ..write(obj.socksProxyAddress) + ..writeByte(7) + ..write(obj.path) + ..writeByte(8) + ..write(obj.isElectrs) + ..writeByte(9) + ..write(obj.supportsSilentPayments) + ..writeByte(10) + ..write(obj.supportsMweb) + ..writeByte(11) + ..write(obj.isEnabledForAutoSwitching) + ..writeByte(12) + ..write(obj.label); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is NodeAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/cw_core/lib/output_info.dart b/cw_core/lib/output_info.dart index 1db1c2ed6..ef53099f9 100644 --- a/cw_core/lib/output_info.dart +++ b/cw_core/lib/output_info.dart @@ -1,24 +1,25 @@ +import 'package:cw_core/amount/money.dart'; + class OutputInfo { - const OutputInfo( - {required this.address, - required this.sendAll, - required this.isParsedAddress, - this.cryptoAmount, - this.formattedCryptoAmount, + const OutputInfo({ + required this.address, + required this.sendAll, + required this.isParsedAddress, + required this.cryptoAmount, this.fiatAmount, this.note, this.extractedAddress, this.memo, - this.extra = const {}}); + this.extra = const {}, + }); - final String? fiatAmount; - final String? cryptoAmount; - final String address; - final String? note; - final String? extractedAddress; - final bool sendAll; - final bool isParsedAddress; - final int? formattedCryptoAmount; - final String? memo; - final Map extra; -} \ No newline at end of file + final String? fiatAmount; + final Money cryptoAmount; + final String address; + final String? note; + final String? extractedAddress; + final bool sendAll; + final bool isParsedAddress; + final String? memo; + final Map extra; +} diff --git a/cw_core/lib/parse_fixed.dart b/cw_core/lib/parse_fixed.dart index 695bad60f..fede4df09 100644 --- a/cw_core/lib/parse_fixed.dart +++ b/cw_core/lib/parse_fixed.dart @@ -45,7 +45,7 @@ BigInt parseFixed(String value, int decimals) { var fraction = (comps.length == 2 ? comps[1] : "0").padRight(decimals, "0"); if (fraction.length > multiplier.length - 1) { - throw FormatException("fractional component exceeds decimals, underflow, parseFixed"); + throw FormatException("fractional component(${fraction.length}) exceeds decimals(${decimals}), underflow, parseFixed"); } final wholeValue = BigInt.parse(whole); diff --git a/cw_core/lib/payjoin_session.dart b/cw_core/lib/payjoin_session.dart index 9133b15f0..6acb0cab9 100644 --- a/cw_core/lib/payjoin_session.dart +++ b/cw_core/lib/payjoin_session.dart @@ -1,9 +1,9 @@ import 'package:cw_core/hive_type_ids.dart'; import 'package:hive/hive.dart'; -part 'payjoin_session.g.dart'; +part 'payjoin_session.part.dart'; -@HiveType(typeId: PAYJOIN_SESSION_TYPE_ID) +// @HiveType(typeId: PAYJOIN_SESSION_TYPE_ID) class PayjoinSession extends HiveObject { PayjoinSession({ required this.walletId, @@ -25,31 +25,31 @@ class PayjoinSession extends HiveObject { static const typeId = PAYJOIN_SESSION_TYPE_ID; static const boxName = 'PayjoinSessions'; - @HiveField(0) + // @HiveField(0) final String walletId; - @HiveField(1) + // @HiveField(1) final String? sender; - @HiveField(2) + // @HiveField(2) final String? receiver; - @HiveField(3) + // @HiveField(3) final String? pjUri; - @HiveField(4) + // @HiveField(4) String status; - @HiveField(5) + // @HiveField(5) DateTime? inProgressSince; - @HiveField(6) + // @HiveField(6) String? txId; - @HiveField(7) + // @HiveField(7) String? rawAmount; - @HiveField(8) + // @HiveField(8) String? error; bool get isSenderSession => sender != null; diff --git a/cw_core/lib/payjoin_session.part.dart b/cw_core/lib/payjoin_session.part.dart new file mode 100644 index 000000000..09dc619e1 --- /dev/null +++ b/cw_core/lib/payjoin_session.part.dart @@ -0,0 +1,65 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'payjoin_session.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class PayjoinSessionAdapter extends TypeAdapter { + @override + final int typeId = 23; + + @override + PayjoinSession read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return PayjoinSession( + walletId: fields[0] as String, + receiver: fields[2] as String?, + sender: fields[1] as String?, + pjUri: fields[3] as String?, + status: fields[4] as String, + inProgressSince: fields[5] as DateTime?, + rawAmount: fields[7] as String?, + ) + ..txId = fields[6] as String? + ..error = fields[8] as String?; + } + + @override + void write(BinaryWriter writer, PayjoinSession obj) { + writer + ..writeByte(9) + ..writeByte(0) + ..write(obj.walletId) + ..writeByte(1) + ..write(obj.sender) + ..writeByte(2) + ..write(obj.receiver) + ..writeByte(3) + ..write(obj.pjUri) + ..writeByte(4) + ..write(obj.status) + ..writeByte(5) + ..write(obj.inProgressSince) + ..writeByte(6) + ..write(obj.txId) + ..writeByte(7) + ..write(obj.rawAmount) + ..writeByte(8) + ..write(obj.error); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PayjoinSessionAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/cw_core/lib/pending_transaction.dart b/cw_core/lib/pending_transaction.dart index 89bd75922..3d8912fa3 100644 --- a/cw_core/lib/pending_transaction.dart +++ b/cw_core/lib/pending_transaction.dart @@ -1,3 +1,5 @@ +import 'package:cw_core/amount/money.dart'; + class PendingChange { final String address; final BigInt amount; @@ -7,22 +9,19 @@ class PendingChange { mixin PendingTransaction { String get id; + + Money get amount; + Money get fee; + String get amountFormatted; - String get feeFormatted; - String get feeFormattedValue; + String get feeFormatted => fee.toStringWithSymbol(fractionalDigits: 8); + String get feeFormattedValue => fee.toStringWithPrecision(fractionalDigits: 8); String? feeRate; String get hex; String? get evmTxHashFromRawHex => null; int? get outputCount => null; PendingChange? change; - String get roundedAmountFormatted { - if (amountFormatted.split(".").last.length <= 8) { - return amountFormatted; - } - return double.parse(amountFormatted).toStringAsFixed(8); - } - bool shouldCommitUR() => false; Future commit(); diff --git a/cw_core/lib/spl_token.dart b/cw_core/lib/spl_token.dart index 7f6db08ce..500435319 100644 --- a/cw_core/lib/spl_token.dart +++ b/cw_core/lib/spl_token.dart @@ -2,39 +2,39 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/hive_type_ids.dart'; import 'package:hive/hive.dart'; -part 'spl_token.g.dart'; +part 'spl_token.part.dart'; -@HiveType(typeId: SPLToken.typeId) +// @HiveType(typeId: SPLToken.typeId) class SPLToken extends CryptoCurrency with HiveObjectMixin { @override - @HiveField(0) + // @HiveField(0) final String name; - @HiveField(1) + // @HiveField(1) final String symbol; - @HiveField(2) + // @HiveField(2) final String mintAddress; - @HiveField(3) + // @HiveField(3) final int decimal; - @HiveField(4, defaultValue: true) + // @HiveField(4, defaultValue: true) bool _enabled; - @HiveField(5) + // @HiveField(5) final String mint; @override - @HiveField(6) + // @HiveField(6) final String? iconPath; @override - @HiveField(7) + // @HiveField(7) final String? tag; @override - @HiveField(8, defaultValue: false) + // @HiveField(8, defaultValue: false) bool isPotentialScam; SPLToken({ diff --git a/cw_core/lib/spl_token.part.dart b/cw_core/lib/spl_token.part.dart new file mode 100644 index 000000000..d3da96cfd --- /dev/null +++ b/cw_core/lib/spl_token.part.dart @@ -0,0 +1,64 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'spl_token.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class SPLTokenAdapter extends TypeAdapter { + @override + final int typeId = 16; + + @override + SPLToken read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return SPLToken( + name: fields[0] as String, + symbol: fields[1] as String, + mintAddress: fields[2] as String, + decimal: fields[3] as int, + mint: fields[5] as String, + iconPath: fields[6] as String?, + tag: fields[7] as String?, + isPotentialScam: fields[8] == null ? false : fields[8] as bool, + ).._enabled = fields[4] == null ? true : fields[4] as bool; + } + + @override + void write(BinaryWriter writer, SPLToken obj) { + writer + ..writeByte(9) + ..writeByte(0) + ..write(obj.name) + ..writeByte(1) + ..write(obj.symbol) + ..writeByte(2) + ..write(obj.mintAddress) + ..writeByte(3) + ..write(obj.decimal) + ..writeByte(4) + ..write(obj._enabled) + ..writeByte(5) + ..write(obj.mint) + ..writeByte(6) + ..write(obj.iconPath) + ..writeByte(7) + ..write(obj.tag) + ..writeByte(8) + ..write(obj.isPotentialScam); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is SPLTokenAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/cw_core/lib/transaction_info.dart b/cw_core/lib/transaction_info.dart index e3453747a..a8743e0f8 100644 --- a/cw_core/lib/transaction_info.dart +++ b/cw_core/lib/transaction_info.dart @@ -1,20 +1,18 @@ +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/format_amount.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/keyable.dart'; abstract class TransactionInfo extends Object with Keyable { late String id; late String txHash = id; - late int amount; - int? fee; + late Money amount; + Money? fee; late TransactionDirection direction; late bool isPending; late DateTime date; int? height; int confirmations = 0; - String amountFormatted(); - String fiatAmount(); - String? feeFormatted(); - void changeFiatAmount(String amount); String? to; String? from; String? evmSignatureName; @@ -26,4 +24,8 @@ abstract class TransactionInfo extends Object with Keyable { dynamic get keyIndex => id; Map additionalInfo = {}; + + String? _fiatAmount; + String fiatAmount() => _fiatAmount ?? ''; + void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); } diff --git a/cw_core/lib/tron_token.dart b/cw_core/lib/tron_token.dart index 3d891bea7..c4b27ed21 100644 --- a/cw_core/lib/tron_token.dart +++ b/cw_core/lib/tron_token.dart @@ -4,32 +4,32 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/hive_type_ids.dart'; import 'package:hive/hive.dart'; -part 'tron_token.g.dart'; +part 'tron_token.part.dart'; -@HiveType(typeId: TronToken.typeId) +// @HiveType(typeId: TronToken.typeId) class TronToken extends CryptoCurrency with HiveObjectMixin { - @HiveField(0) + // @HiveField(0) final String name; - @HiveField(1) + // @HiveField(1) final String symbol; - @HiveField(2) + // @HiveField(2) final String contractAddress; - @HiveField(3) + // @HiveField(3) final int decimal; - @HiveField(4, defaultValue: true) + // @HiveField(4, defaultValue: true) bool _enabled; - @HiveField(5) + // @HiveField(5) final String? iconPath; - @HiveField(6) + // @HiveField(6) final String? tag; - @HiveField(7, defaultValue: false) + // @HiveField(7, defaultValue: false) final bool isPotentialScam; bool get enabled => _enabled; diff --git a/cw_core/lib/tron_token.part.dart b/cw_core/lib/tron_token.part.dart new file mode 100644 index 000000000..e719c18c4 --- /dev/null +++ b/cw_core/lib/tron_token.part.dart @@ -0,0 +1,61 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'tron_token.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class TronTokenAdapter extends TypeAdapter { + @override + final int typeId = 18; + + @override + TronToken read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return TronToken( + name: fields[0] as String, + symbol: fields[1] as String, + contractAddress: fields[2] as String, + decimal: fields[3] as int, + iconPath: fields[5] as String?, + tag: fields[6] as String?, + isPotentialScam: fields[7] == null ? false : fields[7] as bool, + ).._enabled = fields[4] == null ? true : fields[4] as bool; + } + + @override + void write(BinaryWriter writer, TronToken obj) { + writer + ..writeByte(8) + ..writeByte(0) + ..write(obj.name) + ..writeByte(1) + ..write(obj.symbol) + ..writeByte(2) + ..write(obj.contractAddress) + ..writeByte(3) + ..write(obj.decimal) + ..writeByte(4) + ..write(obj._enabled) + ..writeByte(5) + ..write(obj.iconPath) + ..writeByte(6) + ..write(obj.tag) + ..writeByte(7) + ..write(obj.isPotentialScam); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is TronTokenAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/cw_core/lib/unspent_coins_info.dart b/cw_core/lib/unspent_coins_info.dart index a60feb634..8e0ec7883 100644 --- a/cw_core/lib/unspent_coins_info.dart +++ b/cw_core/lib/unspent_coins_info.dart @@ -2,9 +2,9 @@ import 'package:cw_core/hive_type_ids.dart'; import 'package:cw_core/unspent_comparable_mixin.dart'; import 'package:hive/hive.dart'; -part 'unspent_coins_info.g.dart'; +part 'unspent_coins_info.part.dart'; -@HiveType(typeId: UnspentCoinsInfo.typeId) +// @HiveType(typeId: UnspentCoinsInfo.typeId) class UnspentCoinsInfo extends HiveObject with UnspentComparable { UnspentCoinsInfo({ required this.walletId, @@ -25,40 +25,40 @@ class UnspentCoinsInfo extends HiveObject with UnspentComparable { static const boxName = 'Unspent'; static const boxKey = 'unspentBoxKey'; - @HiveField(0, defaultValue: '') + // @HiveField(0, defaultValue: '') String walletId; - @HiveField(1, defaultValue: '') + // @HiveField(1, defaultValue: '') String hash; - @HiveField(2, defaultValue: false) + // @HiveField(2, defaultValue: false) bool isFrozen; - @HiveField(3, defaultValue: false) + // @HiveField(3, defaultValue: false) bool isSending; - @HiveField(4) + // @HiveField(4) String? noteRaw; - @HiveField(5, defaultValue: '') + // @HiveField(5, defaultValue: '') String address; - @HiveField(6, defaultValue: 0) + // @HiveField(6, defaultValue: 0) int value; - @HiveField(7, defaultValue: 0) + // @HiveField(7, defaultValue: 0) int vout; - @HiveField(8, defaultValue: null) + // @HiveField(8, defaultValue: null) String? keyImage; - @HiveField(9, defaultValue: false) + // @HiveField(9, defaultValue: false) bool isChange; - @HiveField(10, defaultValue: 0) + // @HiveField(10, defaultValue: 0) int accountIndex; - @HiveField(11, defaultValue: false) + // @HiveField(11, defaultValue: false) bool? isSilentPayment; String get note => noteRaw ?? ''; diff --git a/cw_core/lib/unspent_coins_info.part.dart b/cw_core/lib/unspent_coins_info.part.dart new file mode 100644 index 000000000..658bb1e6f --- /dev/null +++ b/cw_core/lib/unspent_coins_info.part.dart @@ -0,0 +1,74 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'unspent_coins_info.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class UnspentCoinsInfoAdapter extends TypeAdapter { + @override + final int typeId = 9; + + @override + UnspentCoinsInfo read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return UnspentCoinsInfo( + walletId: fields[0] == null ? '' : fields[0] as String, + hash: fields[1] == null ? '' : fields[1] as String, + isFrozen: fields[2] == null ? false : fields[2] as bool, + isSending: fields[3] == null ? false : fields[3] as bool, + noteRaw: fields[4] as String?, + address: fields[5] == null ? '' : fields[5] as String, + vout: fields[7] == null ? 0 : fields[7] as int, + value: fields[6] == null ? 0 : fields[6] as int, + keyImage: fields[8] as String?, + isChange: fields[9] == null ? false : fields[9] as bool, + accountIndex: fields[10] == null ? 0 : fields[10] as int, + isSilentPayment: fields[11] == null ? false : fields[11] as bool?, + ); + } + + @override + void write(BinaryWriter writer, UnspentCoinsInfo obj) { + writer + ..writeByte(12) + ..writeByte(0) + ..write(obj.walletId) + ..writeByte(1) + ..write(obj.hash) + ..writeByte(2) + ..write(obj.isFrozen) + ..writeByte(3) + ..write(obj.isSending) + ..writeByte(4) + ..write(obj.noteRaw) + ..writeByte(5) + ..write(obj.address) + ..writeByte(6) + ..write(obj.value) + ..writeByte(7) + ..write(obj.vout) + ..writeByte(8) + ..write(obj.keyImage) + ..writeByte(9) + ..write(obj.isChange) + ..writeByte(10) + ..write(obj.accountIndex) + ..writeByte(11) + ..write(obj.isSilentPayment); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is UnspentCoinsInfoAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index c7ed4eccb..917d86ef4 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -43,8 +43,6 @@ abstract class WalletBase get balance; - String formatCryptoAmount(String amount) => amount; - SyncStatus get syncStatus; set syncStatus(SyncStatus status); diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index 893891618..3f7de05bf 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -3,7 +3,7 @@ import 'package:cw_core/hive_type_ids.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:hive/hive.dart'; -part 'wallet_type.g.dart'; +part 'wallet_type.part.dart'; const walletTypes = [ WalletType.monero, @@ -41,66 +41,66 @@ const evmWalletTypes = [ WalletType.bsc ]; -@HiveType(typeId: WALLET_TYPE_TYPE_ID) +// @HiveType(typeId: WALLET_TYPE_TYPE_ID) enum WalletType { - @HiveField(0) + // @HiveField(0) monero, - @HiveField(1) + // @HiveField(1) none, - @HiveField(2) + // @HiveField(2) bitcoin, - @HiveField(3) + // @HiveField(3) litecoin, - @HiveField(4) + // @HiveField(4) haven, - @HiveField(5) + // @HiveField(5) ethereum, - @HiveField(6) + // @HiveField(6) nano, - @HiveField(7) + // @HiveField(7) banano, - @HiveField(8) + // @HiveField(8) bitcoinCash, - @HiveField(9) + // @HiveField(9) polygon, - @HiveField(10) + // @HiveField(10) solana, - @HiveField(11) + // @HiveField(11) tron, - @HiveField(12) + // @HiveField(12) wownero, - @HiveField(13) + // @HiveField(13) zano, - @HiveField(14) + // @HiveField(14) decred, - @HiveField(15) + // @HiveField(15) dogecoin, - @HiveField(16) + // @HiveField(16) base, - @HiveField(17) + // @HiveField(17) arbitrum, - @HiveField(18) + // @HiveField(18) zcash, - @HiveField(19) + // @HiveField(19) bsc, } diff --git a/cw_core/lib/wallet_type.part.dart b/cw_core/lib/wallet_type.part.dart new file mode 100644 index 000000000..60867341c --- /dev/null +++ b/cw_core/lib/wallet_type.part.dart @@ -0,0 +1,136 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'wallet_type.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class WalletTypeAdapter extends TypeAdapter { + @override + final int typeId = 5; + + @override + WalletType read(BinaryReader reader) { + switch (reader.readByte()) { + case 0: + return WalletType.monero; + case 1: + return WalletType.none; + case 2: + return WalletType.bitcoin; + case 3: + return WalletType.litecoin; + case 4: + return WalletType.haven; + case 5: + return WalletType.ethereum; + case 6: + return WalletType.nano; + case 7: + return WalletType.banano; + case 8: + return WalletType.bitcoinCash; + case 9: + return WalletType.polygon; + case 10: + return WalletType.solana; + case 11: + return WalletType.tron; + case 12: + return WalletType.wownero; + case 13: + return WalletType.zano; + case 14: + return WalletType.decred; + case 15: + return WalletType.dogecoin; + case 16: + return WalletType.base; + case 17: + return WalletType.arbitrum; + case 18: + return WalletType.zcash; + case 19: + return WalletType.bsc; + default: + return WalletType.monero; + } + } + + @override + void write(BinaryWriter writer, WalletType obj) { + switch (obj) { + case WalletType.monero: + writer.writeByte(0); + break; + case WalletType.none: + writer.writeByte(1); + break; + case WalletType.bitcoin: + writer.writeByte(2); + break; + case WalletType.litecoin: + writer.writeByte(3); + break; + case WalletType.haven: + writer.writeByte(4); + break; + case WalletType.ethereum: + writer.writeByte(5); + break; + case WalletType.nano: + writer.writeByte(6); + break; + case WalletType.banano: + writer.writeByte(7); + break; + case WalletType.bitcoinCash: + writer.writeByte(8); + break; + case WalletType.polygon: + writer.writeByte(9); + break; + case WalletType.solana: + writer.writeByte(10); + break; + case WalletType.tron: + writer.writeByte(11); + break; + case WalletType.wownero: + writer.writeByte(12); + break; + case WalletType.zano: + writer.writeByte(13); + break; + case WalletType.decred: + writer.writeByte(14); + break; + case WalletType.dogecoin: + writer.writeByte(15); + break; + case WalletType.base: + writer.writeByte(16); + break; + case WalletType.arbitrum: + writer.writeByte(17); + break; + case WalletType.zcash: + writer.writeByte(18); + break; + case WalletType.bsc: + writer.writeByte(19); + break; + } + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is WalletTypeAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/cw_core/lib/wownero_balance.dart b/cw_core/lib/wownero_balance.dart deleted file mode 100644 index de6518023..000000000 --- a/cw_core/lib/wownero_balance.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:cw_core/balance.dart'; -import 'package:cw_core/wownero_amount_format.dart'; - -class WowneroBalance extends Balance { - WowneroBalance({required this.fullBalance, required this.unlockedBalance, this.frozenBalance = 0}) - : formattedFrozenBalance = wowneroAmountToString(amount: frozenBalance), - super.fromInt(unlockedBalance, fullBalance - unlockedBalance, frozen: frozenBalance); - - final int fullBalance; - final int unlockedBalance; - final int frozenBalance; - final String formattedFrozenBalance; - - @override - String get formattedUnAvailableBalance => - formattedFrozenBalance == '0.0' ? '' : formattedFrozenBalance; -} diff --git a/cw_core/lib/zano_asset.dart b/cw_core/lib/zano_asset.dart index 0332f5972..d619e7ae5 100644 --- a/cw_core/lib/zano_asset.dart +++ b/cw_core/lib/zano_asset.dart @@ -4,38 +4,38 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/hive_type_ids.dart'; import 'package:hive/hive.dart'; -part 'zano_asset.g.dart'; +part 'zano_asset.part.dart'; -@HiveType(typeId: ZanoAsset.typeId) +// @HiveType(typeId: ZanoAsset.typeId) class ZanoAsset extends CryptoCurrency with HiveObjectMixin { - @HiveField(0) + // @HiveField(0) final String fullName; - @HiveField(1) + // @HiveField(1) final String ticker; - @HiveField(2) + // @HiveField(2) final String assetId; - @HiveField(3) + // @HiveField(3) final int decimalPoint; - @HiveField(4, defaultValue: true) + // @HiveField(4, defaultValue: true) bool _enabled; - @HiveField(5) + // @HiveField(5) final String? iconPath; // @HiveField(6) // final String? tag; - @HiveField(6) + // @HiveField(6) final String owner; - @HiveField(7) + // @HiveField(7) final String metaInfo; - @HiveField(8) + // @HiveField(8) final BigInt currentSupply; - @HiveField(9) + // @HiveField(9) final bool hiddenSupply; - @HiveField(10) + // @HiveField(10) final BigInt totalMaxSupply; - @HiveField(11) + // @HiveField(11) final bool isInGlobalWhitelist; - @HiveField(12, defaultValue: null) + // @HiveField(12, defaultValue: null) final Map? info; bool get enabled => _enabled; diff --git a/cw_core/lib/zano_asset.part.dart b/cw_core/lib/zano_asset.part.dart new file mode 100644 index 000000000..d23d4b853 --- /dev/null +++ b/cw_core/lib/zano_asset.part.dart @@ -0,0 +1,76 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'zano_asset.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class ZanoAssetAdapter extends TypeAdapter { + @override + final int typeId = 22; + + @override + ZanoAsset read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return ZanoAsset( + fullName: fields[0] as String, + ticker: fields[1] as String, + assetId: fields[2] as String, + decimalPoint: fields[3] as int, + iconPath: fields[5] as String?, + owner: fields[6] as String, + metaInfo: fields[7] as String, + currentSupply: fields[8] as BigInt, + hiddenSupply: fields[9] as bool, + totalMaxSupply: fields[10] as BigInt, + isInGlobalWhitelist: fields[11] as bool, + info: (fields[12] as Map?)?.cast(), + ).._enabled = fields[4] == null ? true : fields[4] as bool; + } + + @override + void write(BinaryWriter writer, ZanoAsset obj) { + writer + ..writeByte(13) + ..writeByte(0) + ..write(obj.fullName) + ..writeByte(1) + ..write(obj.ticker) + ..writeByte(2) + ..write(obj.assetId) + ..writeByte(3) + ..write(obj.decimalPoint) + ..writeByte(4) + ..write(obj._enabled) + ..writeByte(5) + ..write(obj.iconPath) + ..writeByte(6) + ..write(obj.owner) + ..writeByte(7) + ..write(obj.metaInfo) + ..writeByte(8) + ..write(obj.currentSupply) + ..writeByte(9) + ..write(obj.hiddenSupply) + ..writeByte(10) + ..write(obj.totalMaxSupply) + ..writeByte(11) + ..write(obj.isInGlobalWhitelist) + ..writeByte(12) + ..write(obj.info); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ZanoAssetAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/cw_core/pubspec.lock b/cw_core/pubspec.lock index 2d0919c33..8eff1d338 100644 --- a/cw_core/pubspec.lock +++ b/cw_core/pubspec.lock @@ -5,23 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: "5b7468c326d2f8a4f630056404ca0d291ade42918f4a3c6233618e724f39da8e" url: "https://pub.dev" source: hosted - version: "76.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.3" + version: "92.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: "70e4b1ef8003c64793a9e268a551a82869a8a96f39deb73dea28084b0e8bf75e" url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "9.0.0" args: dependency: transitive description: @@ -76,18 +71,18 @@ packages: dependency: transitive description: name: build - sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 + sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "4.0.6" build_config: dependency: transitive description: name: build_config - sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.3.0" build_daemon: dependency: transitive description: @@ -96,30 +91,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.4" - build_resolvers: - dependency: "direct dev" - description: - name: build_resolvers - sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 - url: "https://pub.dev" - source: hosted - version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" url: "https://pub.dev" source: hosted - version: "2.4.15" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" - url: "https://pub.dev" - source: hosted - version: "8.0.0" + version: "2.15.0" built_collection: dependency: transitive description: @@ -149,10 +128,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: @@ -169,14 +148,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" - code_builder: - dependency: transitive - description: - name: code_builder - sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" - url: "https://pub.dev" - source: hosted - version: "4.10.1" collection: dependency: transitive description: @@ -221,10 +192,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" + sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b url: "https://pub.dev" source: hosted - version: "2.3.8" + version: "3.1.3" decimal: dependency: "direct main" description: @@ -291,14 +262,6 @@ packages: description: flutter source: sdk version: "0.0.0" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" glob: dependency: transitive description: @@ -316,21 +279,13 @@ packages: source: hosted version: "2.3.2" hive: - dependency: transitive + dependency: "direct dev" description: name: hive sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" url: "https://pub.dev" source: hosted version: "2.2.3" - hive_generator: - dependency: "direct dev" - description: - name: hive_generator - sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" - url: "https://pub.dev" - source: hosted - version: "2.0.1" http: dependency: "direct main" description: @@ -391,26 +346,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" logging: dependency: transitive description: @@ -419,38 +374,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.dev" - source: hosted - version: "0.1.3-main.0" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -463,18 +410,18 @@ packages: dependency: "direct main" description: name: mobx - sha256: bf1a90e5bcfd2851fc6984e20eef69557c65d9e4d0a88f5be4cf72c9819ce6b0 + sha256: "25ea9a3ee69243d578fb6288721d8eeb18550d0391c32f896d377d994154bff2" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" mobx_codegen: dependency: "direct dev" description: name: mobx_codegen - sha256: e0abbbc651a69550440f6b65c99ec222a1e2a4afd7baec8ba0f3088c7ca582a8 + sha256: "6529bcae074838004b58ad01fbe4966be182a176aacfb723686985986ebac19f" url: "https://pub.dev" source: hosted - version: "2.7.1" + version: "2.7.7" nested: dependency: transitive description: @@ -663,18 +610,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 url: "https://pub.dev" source: hosted - version: "1.5.0" - source_helper: - dependency: transitive - description: - name: source_helper - sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" - url: "https://pub.dev" - source: hosted - version: "1.3.5" + version: "4.2.3" source_span: dependency: transitive description: @@ -799,18 +738,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.4" - timing: - dependency: transitive - description: - name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" - url: "https://pub.dev" - source: hosted - version: "1.0.1" + version: "0.7.10" torch_dart: dependency: "direct main" description: @@ -846,10 +777,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -907,5 +838,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.0-0 <4.0.0" + dart: ">=3.9.0 <4.0.0" flutter: ">=3.27.0" diff --git a/cw_core/pubspec.yaml b/cw_core/pubspec.yaml index 3f9a6e521..ea642d82e 100644 --- a/cw_core/pubspec.yaml +++ b/cw_core/pubspec.yaml @@ -14,8 +14,8 @@ dependencies: http: ^1.1.0 file: ^7.0.0 path_provider: ^2.1.5 - mobx: ^2.0.7+4 - flutter_mobx: ^2.0.6+1 + mobx: ^2.6.0 + flutter_mobx: ^2.3.0 intl: any encrypt: ^5.0.1 decimal: ^2.3.3 @@ -53,10 +53,10 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - build_runner: ^2.4.15 - build_resolvers: ^2.4.4 - mobx_codegen: ^2.0.7 - hive_generator: ^2.0.1 + build_runner: ^2.15.0 + mobx_codegen: ^2.7.7 + hive: ^2.2.3 +# hive_generator: ^2.0.1 dependency_overrides: watcher: ^1.1.0 diff --git a/cw_core/test/amount/amount_sanitizer_test.dart b/cw_core/test/amount/amount_sanitizer_test.dart new file mode 100644 index 000000000..f6b5b5bed --- /dev/null +++ b/cw_core/test/amount/amount_sanitizer_test.dart @@ -0,0 +1,31 @@ +import 'package:cw_core/amount/amount_sanitizer.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('smartAmountSanitizer', () { + test('sanitize decimal dot', (){ + final amount = "1,000.00".sanitized(); + expect(amount, "1000.00"); + }); + + test('sanitize decimal comma', (){ + final amount = "1.000,00".sanitized(); + expect(amount, "1000.00"); + }); + + test('general case', (){ + final amount = "1000.00".sanitized(); + expect(amount, "1000.00"); + }); + + test('no decimals', (){ + final amount = "1,000".sanitized(); + expect(amount, "1.000"); + }); + + test('no decimals', (){ + final amount = "1.000".sanitized(); + expect(amount, "1.000"); + }); + }); +} diff --git a/cw_core/test/amount/money_test.dart b/cw_core/test/amount/money_test.dart new file mode 100644 index 000000000..ddb13bc3b --- /dev/null +++ b/cw_core/test/amount/money_test.dart @@ -0,0 +1,351 @@ +import "package:cw_core/amount/money.dart"; +import "package:cw_core/crypto_currency.dart"; +import "package:flutter_test/flutter_test.dart"; + +void main() { + group("Money", () { + test("parse", () { + var money = Money.parse("1", CryptoCurrency.btc); + expect(money.amount, BigInt.from(100000000)); + + money = Money.parse("-1", CryptoCurrency.btc); + expect(money.amount, BigInt.from(-100000000)); + + money = Money.parse("1.11", CryptoCurrency.btc); + expect(money.amount, BigInt.from(111000000)); + + money = Money.parse("-1.11", CryptoCurrency.btc); + expect(money.amount, BigInt.from(-111000000)); + + money = Money.parse("-.11", CryptoCurrency.btc); + expect(money.amount, BigInt.from(-11000000)); + + money = Money.parse(".11", CryptoCurrency.btc); + expect(money.amount, BigInt.from(11000000)); + + // Invalid representation + expect(() => Money.parse("1,11", CryptoCurrency.btc), throwsFormatException); + + // To many decimals + expect(() => Money.parse("-1.000000000000000", CryptoCurrency.btc), throwsFormatException); + + money = Money.parse("1", CryptoCurrency.btc, isBaseUnit: true); + expect(money.amount, BigInt.from(1)); + + money = Money.parse("0", CryptoCurrency.btc, isBaseUnit: true); + expect(money.amount, BigInt.from(0)); + + money = Money.parse("-1", CryptoCurrency.btc, isBaseUnit: true); + expect(money.amount, BigInt.from(-1)); + + // canonical representation when expecting base units + expect(() => Money.parse("1.0", CryptoCurrency.btc, isBaseUnit: true), throwsFormatException); + }); + + test("tryParse", () { + var money = Money.tryParse("1", CryptoCurrency.btc); + expect(money?.amount, BigInt.from(100000000)); + + money = Money.tryParse("-1", CryptoCurrency.btc); + expect(money?.amount, BigInt.from(-100000000)); + + money = Money.tryParse("1.11", CryptoCurrency.btc); + expect(money?.amount, BigInt.from(111000000)); + + money = Money.tryParse("-1.11", CryptoCurrency.btc); + expect(money?.amount, BigInt.from(-111000000)); + + money = Money.tryParse("-.11", CryptoCurrency.btc); + expect(money?.amount, BigInt.from(-11000000)); + + money = Money.tryParse(".11", CryptoCurrency.btc); + expect(money?.amount, BigInt.from(11000000)); + + // Invalid representation + money = Money.tryParse("1,11", CryptoCurrency.btc); + expect(money?.amount, isNull); + + // To many decimals + money = Money.tryParse("-1.000000000000000", CryptoCurrency.btc); + expect(money?.amount, isNull); + + money = Money.tryParse("1", CryptoCurrency.btc, isBaseUnit: true); + expect(money?.amount, BigInt.from(1)); + + money = Money.tryParse("0", CryptoCurrency.btc, isBaseUnit: true); + expect(money?.amount, BigInt.from(0)); + + money = Money.tryParse("-1", CryptoCurrency.btc, isBaseUnit: true); + expect(money?.amount, BigInt.from(-1)); + + // canonical representation when expecting base units + money = Money.tryParse("1.0", CryptoCurrency.btc, isBaseUnit: true); + expect(money?.amount, isNull); + }); + + test("sign", () { + expect(Money(BigInt.from(0), CryptoCurrency.btc).sign, 0); + + expect(Money(BigInt.from(1), CryptoCurrency.btc).sign, 1); + + expect(Money(BigInt.from(10), CryptoCurrency.btc).sign, 1); + + expect(Money(BigInt.from(-1), CryptoCurrency.btc).sign, -1); + + expect(Money(BigInt.from(-10), CryptoCurrency.btc).sign, -1); + }); + + test("isZero", () { + expect(Money(BigInt.from(0), CryptoCurrency.btc).isZero, isTrue); + + expect(Money(BigInt.from(1), CryptoCurrency.btc).isZero, isFalse); + + expect(Money(BigInt.from(-1), CryptoCurrency.btc).isZero, isFalse); + }); + + test("isNegative", () { + expect(Money(BigInt.from(0), CryptoCurrency.btc).isNegative, isFalse); + + expect(Money(BigInt.from(1), CryptoCurrency.btc).isNegative, isFalse); + + expect(Money(BigInt.from(-1), CryptoCurrency.btc).isNegative, isTrue); + }); + + group("Comparison", () { + final fourBitcoin = Money(BigInt.from(400000000), CryptoCurrency.btc); + final fiveBitcoin = Money(BigInt.from(500000000), CryptoCurrency.btc); + final sixBitcoin = Money(BigInt.from(600000000), CryptoCurrency.btc); + + final fiveMonero = Money.parse("5", CryptoCurrency.xmr); + + test("==", () { + expect(fiveBitcoin, equals(Money(BigInt.from(500000000), CryptoCurrency.btc))); + expect(fiveBitcoin, isNot(equals(fourBitcoin))); + expect(fiveBitcoin, isNot(equals(sixBitcoin))); + expect(fiveBitcoin, isNot(equals(fiveMonero))); + // intentional check of incompatible types. + // ignore: unrelated_type_equality_checks + expect(fiveBitcoin == "Not money", equals(false)); + }); + + test("<", () { + expect(fiveBitcoin < sixBitcoin, isTrue); + expect(fiveBitcoin < fiveBitcoin, isFalse); + expect(fiveBitcoin < fourBitcoin, isFalse); + + // Cannot compare money in different currencies: + expect(() => fiveBitcoin < fiveMonero, throwsArgumentError); + }); + + test("<=", () { + expect(fiveBitcoin <= sixBitcoin, isTrue); + expect(fiveBitcoin <= fiveBitcoin, isTrue); + expect(fiveBitcoin <= fourBitcoin, isFalse); + + // Cannot compare money in different currencies: + expect(() => fiveBitcoin <= fiveMonero, throwsArgumentError); + }); + + test(">", () { + expect(fiveBitcoin > fourBitcoin, isTrue); + expect(fiveBitcoin > fiveBitcoin, isFalse); + expect(fiveBitcoin > sixBitcoin, isFalse); + + // Cannot compare money in different currencies: + expect(() => fiveBitcoin > fiveMonero, throwsArgumentError); + }); + + test(">=", () { + expect(fiveBitcoin >= fourBitcoin, isTrue); + expect(fiveBitcoin >= fiveBitcoin, isTrue); + expect(fiveBitcoin >= sixBitcoin, isFalse); + + // Cannot compare money in different currencies: + expect(() => fiveBitcoin >= fiveMonero, throwsArgumentError); + }); + + test("conformance to Comparable", () { + expect(fiveBitcoin, isA>()); + + expect(fiveBitcoin.compareTo(fiveBitcoin), isZero); + expect(fiveBitcoin.compareTo(fourBitcoin), isPositive); + expect(fiveBitcoin.compareTo(sixBitcoin), isNegative); + expect(() => fiveBitcoin.compareTo(fiveMonero), throwsArgumentError); + }); + }); + + group("Math", () { + test("+", () { + final oneBitcoin = Money.parse("1", CryptoCurrency.btc); + final twoBitcoin = Money.parse("2", CryptoCurrency.btc); + final threeBitcoin = Money.parse("3", CryptoCurrency.btc); + + expect(oneBitcoin + twoBitcoin, equals(threeBitcoin)); + }); + + test("cannot add different currencies", () { + final oneBitcoin = Money(BigInt.one, CryptoCurrency.btc); + final oneMonero = Money(BigInt.one, CryptoCurrency.xmr); + + expect(() => oneBitcoin + oneMonero, throwsArgumentError); + }); + + test("invert money", () { + final oneSatoshi = Money(BigInt.one, CryptoCurrency.btc); + final negativeOneSatoshi = Money(BigInt.from(-1), CryptoCurrency.btc); + + expect(-oneSatoshi, equals(negativeOneSatoshi)); + expect(-negativeOneSatoshi, equals(oneSatoshi)); + }); + + test("-", () { + final oneSatoshi = Money(BigInt.one, CryptoCurrency.btc); + final twoSatoshi = Money(BigInt.two, CryptoCurrency.btc); + final threeSatoshi = Money(BigInt.from(3), CryptoCurrency.btc); + + expect(threeSatoshi - oneSatoshi, equals(twoSatoshi)); + }); + + test("cannot subtract different currencies", () { + final oneSatoshi = Money(BigInt.one, CryptoCurrency.btc); + final oneMonero = Money(BigInt.one, CryptoCurrency.xmr); + + expect(() => oneSatoshi - oneMonero, throwsArgumentError); + }); + + test("*", () { + final zeroSatoshi = Money(BigInt.zero, CryptoCurrency.btc); + final oneSatoshi = Money(BigInt.one, CryptoCurrency.btc); + final twoSatoshi = Money(BigInt.two, CryptoCurrency.btc); + + expect(oneSatoshi * BigInt.zero, equals(zeroSatoshi)); + expect(oneSatoshi * BigInt.two, equals(twoSatoshi)); + expect(oneSatoshi * BigInt.from(-2), equals(-twoSatoshi)); + }); + + group("/", () { + final threeBI = BigInt.from(3); + final fourBI = BigInt.from(4); + + final negThreeBI = BigInt.from(-3); + final negFourBI = BigInt.from(-4); + + test("rounds down when fractional part is < 0.5", () { + expect((Money(BigInt.from(10), CryptoCurrency.btc) / threeBI).amount, threeBI); + expect((Money(BigInt.from(-10), CryptoCurrency.btc) / threeBI).amount, negThreeBI); + expect((Money(BigInt.from(10), CryptoCurrency.btc) / negThreeBI).amount, negThreeBI); + expect((Money(BigInt.from(-10), CryptoCurrency.btc) / negThreeBI).amount, threeBI); + }); + + test("rounds up when fractional part is > 0.5", () { + expect((Money(BigInt.from(11), CryptoCurrency.btc) / threeBI).amount, fourBI); // 3.66... + expect((Money(BigInt.from(-11), CryptoCurrency.btc) / threeBI).amount, negFourBI); + expect((Money(BigInt.from(11), CryptoCurrency.btc) / negThreeBI).amount, negFourBI); + expect((Money(BigInt.from(-11), CryptoCurrency.btc) / negThreeBI).amount, fourBI); + }); + + test("rounds AWAY from zero when fractional part is exactly 0.5 (Ties)", () { + // 5 / 2 = 2.5 -> 3 + expect((Money(BigInt.from(5), CryptoCurrency.btc) / BigInt.two).amount, threeBI); + // -5 / 2 = -2.5 -> -3 + expect((Money(BigInt.from(-5), CryptoCurrency.btc) / BigInt.two).amount, negThreeBI); + // 5 / -2 = -2.5 -> -3 + expect((Money(BigInt.from(5), CryptoCurrency.btc) / -BigInt.two).amount, negThreeBI); + // -5 / -2 = 2.5 -> 3 + expect((Money(BigInt.from(-5), CryptoCurrency.btc) / -BigInt.two).amount, threeBI); + }); + + test("handles zero numerator correctly", () { + expect((Money(BigInt.zero, CryptoCurrency.btc) / BigInt.from(5)).amount, BigInt.zero); + expect((Money(BigInt.zero, CryptoCurrency.btc) / BigInt.from(-5)).amount, BigInt.zero); + }); + + test("throws Exception on division by zero", () { + expect( + () => Money(BigInt.from(5), CryptoCurrency.btc) / BigInt.zero, + throwsA(isA()), + ); + }); + + test("handles very large BigInts without precision loss", () { + // A number that would lose precision if converted to a standard double + final hugeA = BigInt.parse("10000000000000000000000000000000000005"); + final hugeB = BigInt.parse("10"); + // 1000...005 / 10 = 1000...000.5 -> Should round up to 1000...001 + final expected = BigInt.parse("1000000000000000000000000000000000001"); + + expect((Money(hugeA, CryptoCurrency.btc) / hugeB).amount, expected); + }); + + test("handles exact division perfectly", () { + expect((Money(BigInt.from(100), CryptoCurrency.btc) / BigInt.from(20)).amount, + BigInt.from(5)); + expect((Money(BigInt.from(-100), CryptoCurrency.btc) / BigInt.from(20)).amount, + BigInt.from(-5)); + }); + }); + }); + + group("copyWith", () { + final money = Money.parse("1", CryptoCurrency.btc); + + test("currency", () { + final copy = money.copyWith(currency: CryptoCurrency.eth); + expect(copy.amount, equals(BigInt.parse("1000000000000000000"))); + expect(copy.currency.decimals, equals(18)); + expect(copy.toString(), "1"); + }); + + test("currency nano", () { + final copy = money.copyWith(currency: CryptoCurrency.nano); + expect(copy.amount, equals(BigInt.parse("1000000000000000000000000000000"))); + expect(copy.currency.decimals, equals(30)); + expect(copy.toString(), "1"); + }); + + test("amount", () { + final copy = money.copyWith(amount: BigInt.one); + expect(copy.amount, BigInt.one); + expect(copy.currency.decimals, 8); + }); + + test("amount and currency", () { + final copy = money.copyWith(amount: BigInt.one); + expect(copy.amount, BigInt.one); + expect(copy.currency.decimals, 8); + }); + }); + + group("toStringWithPrecision", (){ + test("with fractionalDigits and padded with zeros", () { + final money = Money.parse("1", CryptoCurrency.btc); + expect(money.amount, equals(BigInt.parse("100000000"))); + expect(money.currency.decimals, equals(8)); + expect(money.toStringWithPrecision(fractionalDigits: 5, trimZeros: false), "1.00000"); + }); + + test("using base unit sats", () { + final money = Money.parse("1", CryptoCurrency.btc); + expect(money.amount, equals(BigInt.parse("100000000"))); + expect(money.currency.decimals, equals(8)); + expect(money.toStringWithPrecision(useBaseUnit: true), "100000000"); + }); + }); + + group("toStringWithSymbol", (){ + test("with fractionalDigits and padded with zeros", () { + final money = Money.parse("1", CryptoCurrency.btc); + expect(money.amount, equals(BigInt.parse("100000000"))); + expect(money.currency.decimals, equals(8)); + expect(money.toStringWithSymbol(fractionalDigits: 5, trimZeros: false), "1.00000 BTC"); + }); + + test("using base unit sats", () { + final money = Money.parse("1", CryptoCurrency.btc); + expect(money.amount, equals(BigInt.parse("100000000"))); + expect(money.currency.decimals, equals(8)); + expect(money.toStringWithSymbol(useBaseUnit: true), "100000000 sats"); + }); + }); + }); +} diff --git a/cw_core/test/lnurl_test.dart b/cw_core/test/lnurl_test.dart index 1df503b9e..f9120d881 100644 --- a/cw_core/test/lnurl_test.dart +++ b/cw_core/test/lnurl_test.dart @@ -1,3 +1,5 @@ +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/lnurl.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/utils/tor/abstract.dart'; @@ -11,13 +13,14 @@ void main() { expect(content, Uri.parse("https://cake.cash/.well-known/lnurlp/konsti")); }); - test('get amount', () async { - CakeTor.instance = await CakeTorInstance.getInstance(); - - final content = await LNURL.getPayRequestAmount( - "LNURL1DP68GURN8GHJ7CTSWQHXY6T5WFJHZAT9WD6ZU6T09AUZ7URJDAU8JTMKXYHKCM308A5N6MNHVVNXJEPAXGMNWWPEX3JRGDTPXVNXZ0FCX5CRQVPSXQNX602VFE24YNP9XGC8W6T5DQJNYVRPD4HH2MN5GTRNLZ"); - expect(content, 8500); - }); + // This tests includes a rest request to an URL that expired and due to the lack of an alternative URL we ignore this test + // test('get amount', () async { + // CakeTor.instance = await CakeTorInstance.getInstance(); + // + // final content = await LNURL.getPayRequestAmount( + // "LNURL1DP68GURN8GHJ7CTSWQHXY6T5WFJHZAT9WD6ZU6T09AUZ7URJDAU8JTMKXYHKCM308A5N6MNHVVNXJEPAXGMNWWPEX3JRGDTPXVNXZ0FCX5CRQVPSXQNX602VFE24YNP9XGC8W6T5DQJNYVRPD4HH2MN5GTRNLZ"); + // expect(content, Money.fromInt(8500, CryptoCurrency.btcln)); + // }); test('get no amount', () async { CakeTor.instance = await CakeTorInstance.getInstance(); @@ -64,22 +67,22 @@ void main() { test("should get the amount 1 BTC encoded as bitcoin", () { final invoice = "lnbc11p5exaxspp5u6e6lx90rw2vqr2e5axt8ur3v98qnmqfsmh8r0l0mcm7ry70vyvsdqqcqzzsxqrrsssp5eqydmrufk39dwhuxzajwx4xyffn0mlcu3qa8vjwry6qev5gsepkq9qxpqysgqt0zldve9uxt3txjt8wp0gev52lquljgugy26nneue85pjwk5yak4hs2d8533tus07pq5ka6zrnd0ruaeepwres8p7ws5mx63xlwff5sqlxcy8a"; - expect(getBolt11Amount(invoice), 100000000); + expect(getBolt11Amount(invoice), Money.fromInt(100000000, CryptoCurrency.btcln)); }); test("should get the amount 0,1 BTC encoded as millibitcoin", () { final invoice = "lnbc100m1p5exagmpp5n4c5tml3h8h7k4gthc9papkyeymceqqvcldkng4nvxwzj7nu9jnsdqqcqzzsxqrrsssp58kgzrh7y3pevantduc4yg33aa6r79ed6xgmhr0dy40zc8pxrz5ms9qxpqysgqcq27km3v3wcz6pntmsfnz259zy6tff0uwjz9kamdrvmxmdv09gp44mh0ygn7gweqf4zdev4x8e67p5kcvahethchdkkjexhtk7emuqcpasz0lp"; - expect(getBolt11Amount(invoice), 10000000); + expect(getBolt11Amount(invoice), Money.fromInt(10000000, CryptoCurrency.btcln)); }); test("should get the amount 1000 SATS encoded as microbitcoin", () { final invoice = "lnbc10u1p5exfgvpp5n3s9dsw9ddax5c3h8437ya7y4g582uyufyna626yr9vqdfjrhwzqdqqcqzzsxqrrsssp5mmkyp5h35fpaxm4063f3kc7d4nmgd4gd8fv8086wxd784m76kmpq9qxpqysgqye9r0vahpjy9l2je6vakxzk3cjnsyx76r29c4amkz4cmapq5tqt5l7rl0emkd003jjvz2d8jqrw6wc4cduvapuyeseh7a855l82wutcpeq30kg"; - expect(getBolt11Amount(invoice), 1000); + expect(getBolt11Amount(invoice), Money.fromInt(1000, CryptoCurrency.btcln)); }); test("should get the amount 1 SAT encoded as nanobitcoin", () { final invoice = "lnbc10n1p5exazppp5szh273r6nzvwydzsdlg0ws0vhxwnc6nqtqt4sjt0vu6x6l8t5csqdqqcqzzsxqrrsssp5nkup6uht7xlfqaj8lxpe5kx0ejum9p4pvv3nlnndr9ykzge3393s9qxpqysgqufh05a7qs0ezeckvmh7tjkr9ngng96nq274twg20z45ysp7afpdrttm2ky73cpjwpjqsmtzzd79w6lyvszgmkanwn8mj0jcfunrwv3qprg3z3j"; - expect(getBolt11Amount(invoice), 1); + expect(getBolt11Amount(invoice), Money.fromInt(1, CryptoCurrency.btcln)); }); }); } diff --git a/cw_decred/lib/amount_format.dart b/cw_decred/lib/amount_format.dart deleted file mode 100644 index c09f76b3b..000000000 --- a/cw_decred/lib/amount_format.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:intl/intl.dart'; -import 'package:cw_core/crypto_amount_format.dart'; - -const decredAmountLength = 8; -const decredAmountDivider = 100000000; -final decredAmountFormat = NumberFormat() - ..maximumFractionDigits = decredAmountLength - ..minimumFractionDigits = 1; - -String decredAmountToString({required int amount}) => - decredAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: decredAmountDivider)); - -double decredAmountToDouble({required int amount}) => - cryptoAmountToDouble(amount: amount, divider: decredAmountDivider); - -int stringDoubleToDecredAmount(String amount) { - int result = 0; - - try { - result = (double.parse(amount) * decredAmountDivider).round(); - } catch (e) { - result = 0; - } - - return result; -} diff --git a/cw_decred/lib/balance.dart b/cw_decred/lib/balance.dart index 97889c9af..b283cb4ba 100644 --- a/cw_decred/lib/balance.dart +++ b/cw_decred/lib/balance.dart @@ -1,26 +1,16 @@ -import 'package:cw_decred/amount_format.dart'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/balance.dart'; +import 'package:cw_core/crypto_currency.dart'; class DecredBalance extends Balance { - DecredBalance({required this.confirmed, required this.unconfirmed, required int frozen}) - : _frozen = frozen, super.fromInt(confirmed, unconfirmed); + DecredBalance({required Money confirmed, required Money unconfirmed, required Money frozen}) + : super(confirmed, unconfirmed, frozen: frozen); - factory DecredBalance.zero() => DecredBalance(confirmed: 0, unconfirmed: 0, frozen: 0); - - final int confirmed; - final int unconfirmed; - final int _frozen; - BigInt get frozen => BigInt.from(_frozen); + factory DecredBalance.zero() => DecredBalance( + confirmed: Money.zero(CryptoCurrency.dcr), + unconfirmed: Money.zero(CryptoCurrency.dcr), + frozen: Money.zero(CryptoCurrency.dcr)); @override - String get formattedAvailableBalance => decredAmountToString(amount: confirmed - _frozen); - - @override - String get formattedAdditionalBalance => decredAmountToString(amount: unconfirmed); - - @override - String get formattedUnAvailableBalance { - final frozenFormatted = decredAmountToString(amount: _frozen); - return frozenFormatted == '0.0' ? '' : frozenFormatted; - } + Money get available => super.available - (frozen ?? Money.zero(CryptoCurrency.dcr)); } diff --git a/cw_decred/lib/pending_transaction.dart b/cw_decred/lib/pending_transaction.dart index 23c76920c..3f58ee8b2 100644 --- a/cw_decred/lib/pending_transaction.dart +++ b/cw_decred/lib/pending_transaction.dart @@ -1,42 +1,33 @@ +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/pending_transaction.dart'; -import 'package:cw_decred/amount_format.dart'; class DecredPendingTransaction with PendingTransaction { - DecredPendingTransaction( - {required this.txid, - required this.amount, - required this.fee, - required this.rawHex, - required this.send}); + DecredPendingTransaction({ + required this.txId, + required this.amount, + required this.fee, + required this.rawHex, + required this.send, + }); - final int amount; - final int fee; - final String txid; + final Money amount; + final Money fee; + final String txId; final String rawHex; final Future Function() send; @override - String get id => txid; + String get id => txId; @override - String get amountFormatted => decredAmountToString(amount: amount); - - @override - String get feeFormatted => "$feeFormattedValue DCR"; - - @override - String get feeFormattedValue => decredAmountToString(amount: fee); + String get amountFormatted => amount.toString(); @override String get hex => rawHex; @override - Future commit() async { - return send(); - } + Future commit() => send(); @override - Future> commitUR() { - throw UnimplementedError(); - } + Future> commitUR() => throw UnimplementedError(); } diff --git a/cw_decred/lib/transaction_info.dart b/cw_decred/lib/transaction_info.dart index 18913175a..6a31c04d9 100644 --- a/cw_decred/lib/transaction_info.dart +++ b/cw_decred/lib/transaction_info.dart @@ -1,15 +1,12 @@ -import 'package:cw_core/currency_for_wallet_type.dart'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; -import 'package:cw_core/format_amount.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:cw_decred/amount_format.dart'; class DecredTransactionInfo extends TransactionInfo { DecredTransactionInfo({ required String id, - required int amount, - required int fee, + required Money amount, + required Money fee, required TransactionDirection direction, required bool isPending, required DateTime date, @@ -27,20 +24,4 @@ class DecredTransactionInfo extends TransactionInfo { this.confirmations = confirmations; this.to = to; } - - String? _fiatAmount; - - @override - String amountFormatted() => - '${formatAmount(decredAmountToString(amount: amount))} ${walletTypeToCryptoCurrency(WalletType.decred).title}'; - - @override - String? feeFormatted() => - '${formatAmount(decredAmountToString(amount: fee ?? 0))} ${walletTypeToCryptoCurrency(WalletType.decred).title}'; - - @override - String fiatAmount() => _fiatAmount ?? ''; - - @override - void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); } diff --git a/cw_decred/lib/wallet.dart b/cw_decred/lib/wallet.dart index 97c118331..a41dcb8f6 100644 --- a/cw_decred/lib/wallet.dart +++ b/cw_decred/lib/wallet.dart @@ -1,13 +1,13 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:cw_core/amount/money.dart'; import 'package:path/path.dart' as p; import 'package:cw_core/exceptions.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cw_decred/amount_format.dart'; import 'package:cw_decred/pending_transaction.dart'; import 'package:cw_decred/transaction_credentials.dart'; import 'package:flutter/foundation.dart'; @@ -121,12 +121,7 @@ abstract class DecredWalletBase @override bool isTestnet; - String get pubkey { - return _pubkey; - } - - @override - String formatCryptoAmount(String amount) => decredAmountToString(amount: int.parse(amount)); + String get pubkey => _pubkey; Future init() async { final getSeed = () async { @@ -354,13 +349,12 @@ abstract class DecredWalletBase Future createTransaction(Object credentials) async { if (watchingOnly) { return DecredPendingTransaction( - txid: "", - amount: 0, - fee: 0, - rawHex: "", - send: () async { - throw "unable to send with watching only wallet"; - }); + txId: "", + amount: Money.zero(currency), + fee: Money.zero(currency), + rawHex: "", + send: () async => throw "unable to send with watching only wallet", + ); } var totalIn = 0; final ignoreInputs = []; @@ -380,14 +374,12 @@ abstract class DecredWalletBase for (final out in creds.outputs) { var amt = 0; if (out.sendAll) { - if (creds.outputs.length != 1) { - throw "can only send all to one output"; - } + if (creds.outputs.length != 1) throw "can only send all to one output"; + sendAll = true; totalAmt = totalIn; - } else if (out.cryptoAmount != null) { - final coins = double.parse(out.cryptoAmount!); - amt = (coins * 1e8).round(); + } else { + amt = out.cryptoAmount.amount.toInt(); } totalAmt += amt; final o = { @@ -416,6 +408,7 @@ abstract class DecredWalletBase final res = await _libwallet.createSignedTransaction(walletInfo.name, jsonEncode(signReq)); final decoded = json.decode(res); final signedHex = decoded["signedhex"]; + final send = () async { await _libwallet.sendRawTransaction(walletInfo.name, signedHex); await updateBalance(); @@ -424,8 +417,14 @@ abstract class DecredWalletBase if (sendAll) { totalAmt = (totalAmt - fee).round(); } + return DecredPendingTransaction( - txid: decoded["txid"] ?? "", amount: totalAmt, fee: fee, rawHex: signedHex, send: send); + txId: decoded["txid"] ?? "", + amount: Money.fromInt(totalAmt, currency), + fee: Money.fromInt(fee, currency), + rawHex: signedHex, + send: send, + ); } int feeRate(TransactionPriority priority) { @@ -477,15 +476,14 @@ abstract class DecredWalletBase } @override - Future> fetchTransactions() async { - return this.fetchFiveTransactions(0); - } + Future> fetchTransactions() => fetchFiveTransactions(0); Future> fetchFiveTransactions(int from) async { try { final res = await _libwallet.listTransactions(walletInfo.name, from.toString(), "5"); final decoded = json.decode(res); - var txs = {}; + final txs = {}; + for (final d in decoded) { final txid = uniqueTxID(d["txid"] ?? "", d["vout"] ?? 0); var direction = TransactionDirection.outgoing; @@ -498,19 +496,18 @@ abstract class DecredWalletBase final fee = (feeDouble * 1e8).round().abs(); final confs = d["confirmations"] ?? 0; final sendTime = d["time"] ?? 0; - final height = d["height"] ?? 0; - final txInfo = DecredTransactionInfo( + + txs[txid] = DecredTransactionInfo( id: txid, - amount: amount, - fee: fee, + amount: Money.fromInt(amount, currency), + fee: Money.fromInt(fee, currency), direction: direction, isPending: confs == 0, date: DateTime.fromMillisecondsSinceEpoch(sendTime * 1000, isUtc: false), - height: height, + height: d["height"] ?? 0, confirmations: confs, to: d["address"] ?? "", ); - txs[txid] = txInfo; } return txs; } catch (e) { @@ -520,9 +517,7 @@ abstract class DecredWalletBase } // uniqueTxID combines the tx id and vout to create a unique id. - String uniqueTxID(String id, int vout) { - return id + ":" + vout.toString(); - } + String uniqueTxID(String id, int vout) => "$id:$vout"; @override Future save() async {} @@ -587,9 +582,9 @@ abstract class DecredWalletBase }); balance[CryptoCurrency.dcr] = DecredBalance( - confirmed: balanceMap["confirmed"] ?? 0, - unconfirmed: balanceMap["unconfirmed"] ?? 0, - frozen: totalFrozen, + confirmed: Money.fromInt(balanceMap["confirmed"] ?? 0, currency), + unconfirmed: Money.fromInt(balanceMap["unconfirmed"] ?? 0, currency), + frozen: Money.fromInt(totalFrozen, currency), ); } @@ -648,7 +643,7 @@ abstract class DecredWalletBase try { final res = await _libwallet.listUnspents(walletInfo.name); final decoded = json.decode(res); - var unspents = []; + final unspents = []; for (final d in decoded) { final spendable = d["spendable"] ?? false; if (!spendable) { diff --git a/cw_decred/lib/wallet_service.dart b/cw_decred/lib/wallet_service.dart index afcf6d5db..44e6d6886 100644 --- a/cw_decred/lib/wallet_service.dart +++ b/cw_decred/lib/wallet_service.dart @@ -10,7 +10,6 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:path/path.dart'; import 'package:hive/hive.dart'; -import 'package:collection/collection.dart'; import 'package:cw_core/unspent_coins_info.dart'; class DecredWalletService extends WalletService< diff --git a/cw_decred/pubspec.lock b/cw_decred/pubspec.lock index 6600652b8..9a568daa7 100644 --- a/cw_decred/pubspec.lock +++ b/cw_decred/pubspec.lock @@ -5,23 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" url: "https://pub.dev" source: hosted - version: "76.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.3" + version: "93.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "10.0.1" args: dependency: transitive description: @@ -76,18 +71,18 @@ packages: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "4.0.6" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.3.0" build_daemon: dependency: transitive description: @@ -96,30 +91,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" - build_resolvers: - dependency: "direct dev" - description: - name: build_resolvers - sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 - url: "https://pub.dev" - source: hosted - version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" url: "https://pub.dev" source: hosted - version: "2.4.15" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" - url: "https://pub.dev" - source: hosted - version: "8.0.0" + version: "2.15.0" built_collection: dependency: transitive description: @@ -132,10 +111,10 @@ packages: dependency: transitive description: name: built_value - sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" url: "https://pub.dev" source: hosted - version: "8.9.5" + version: "8.12.6" cake_backup: dependency: transitive description: @@ -149,10 +128,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: @@ -177,14 +156,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" - code_builder: - dependency: transitive - description: - name: code_builder - sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" - url: "https://pub.dev" - source: hosted - version: "4.10.1" collection: dependency: transitive description: @@ -236,10 +207,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" + sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2" url: "https://pub.dev" source: hosted - version: "2.3.7" + version: "3.1.7" decimal: dependency: transitive description: @@ -276,10 +247,10 @@ packages: dependency: "direct dev" description: name: ffigen - sha256: "2119b4fe3aad0db94dc9531b90283c4640a6231070e613c400b426a4da08c704" + sha256: b7803707faeec4ce3c1b0c2274906504b796e3b70ad573577e72333bd1c9b3ba url: "https://pub.dev" source: hosted - version: "16.1.0" + version: "20.1.1" file: dependency: transitive description: @@ -314,14 +285,6 @@ packages: description: flutter source: sdk version: "0.0.0" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" glob: dependency: transitive description: @@ -339,21 +302,13 @@ packages: source: hosted version: "2.3.2" hive: - dependency: transitive + dependency: "direct dev" description: name: hive sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" url: "https://pub.dev" source: hosted version: "2.2.3" - hive_generator: - dependency: "direct dev" - description: - name: hive_generator - sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" - url: "https://pub.dev" - source: hosted - version: "2.0.1" http: dependency: transitive description: @@ -414,26 +369,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" logging: dependency: transitive description: @@ -442,38 +397,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.dev" - source: hosted - version: "0.1.3-main.0" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -486,18 +433,18 @@ packages: dependency: transitive description: name: mobx - sha256: bf1a90e5bcfd2851fc6984e20eef69557c65d9e4d0a88f5be4cf72c9819ce6b0 + sha256: "25ea9a3ee69243d578fb6288721d8eeb18550d0391c32f896d377d994154bff2" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" mobx_codegen: dependency: "direct dev" description: name: mobx_codegen - sha256: "990da80722f7d7c0017dec92040b31545d625b15d40204c36a1e63d167c73cdc" + sha256: "6529bcae074838004b58ad01fbe4966be182a176aacfb723686985986ebac19f" url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "2.7.7" nested: dependency: transitive description: @@ -694,18 +641,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 url: "https://pub.dev" source: hosted - version: "1.5.0" - source_helper: - dependency: transitive - description: - name: source_helper - sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" - url: "https://pub.dev" - source: hosted - version: "1.3.5" + version: "4.2.3" source_span: dependency: transitive description: @@ -830,18 +769,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.4" - timing: - dependency: transitive - description: - name: timing - sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" - url: "https://pub.dev" - source: hosted - version: "1.0.2" + version: "0.7.10" torch_dart: dependency: transitive description: @@ -877,10 +808,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -946,5 +877,5 @@ packages: source: hosted version: "2.2.2" sdks: - dart: ">=3.7.0-0 <4.0.0" + dart: ">=3.10.0 <4.0.0" flutter: ">=3.24.0" diff --git a/cw_decred/pubspec.yaml b/cw_decred/pubspec.yaml index 781b38cc9..1b808e293 100644 --- a/cw_decred/pubspec.yaml +++ b/cw_decred/pubspec.yaml @@ -18,11 +18,10 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - build_runner: ^2.4.15 - build_resolvers: ^2.4.4 - mobx_codegen: ^2.0.7 - hive_generator: ^2.0.1 - ffigen: ^16.1.0 + build_runner: ^2.15.0 + mobx_codegen: ^2.7.7 + ffigen: ^20.1.1 + hive: ^2.2.3 ffigen: name: libdcrwallet diff --git a/cw_dogecoin/lib/src/dogecoin_wallet.dart b/cw_dogecoin/lib/src/dogecoin_wallet.dart index c9db30fa7..610cd592e 100644 --- a/cw_dogecoin/lib/src/dogecoin_wallet.dart +++ b/cw_dogecoin/lib/src/dogecoin_wallet.dart @@ -68,7 +68,7 @@ abstract class DogeCoinWalletBase extends ElectrumWallet with Store { } @override - int get networkDustAmount => 100000000; // 1 DOGE = 1e8 koinu + BigInt get networkDustAmount => BigInt.from(100000000); // 1 DOGE = 1e8 koinu static int estimatedDogeCoinTransactionSize(int inputsCount, int outputsCounts) => inputsCount * 180 + outputsCounts * 34 + 10; diff --git a/cw_dogecoin/pubspec.yaml b/cw_dogecoin/pubspec.yaml index 97b58a285..910d2b63e 100644 --- a/cw_dogecoin/pubspec.yaml +++ b/cw_dogecoin/pubspec.yaml @@ -15,8 +15,8 @@ dependencies: bip39: ^1.0.6 bip32: ^2.0.0 path_provider: ^2.0.11 - mobx: ^2.0.7+4 - flutter_mobx: ^2.0.6+1 + mobx: ^2.6.0 + flutter_mobx: ^2.3.0 cw_core: path: ../cw_core cw_bitcoin: @@ -30,9 +30,10 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - build_runner: ^2.4.15 - mobx_codegen: ^2.0.7 - hive_generator: ^2.0.1 + build_runner: ^2.15.0 + mobx_codegen: ^2.7.7 +# hive_generator: ^2.0.1 + hive: 2.0.1 dependency_overrides: watcher: ^1.1.0 diff --git a/cw_evm/lib/clients/evm_chain_client.dart b/cw_evm/lib/clients/evm_chain_client.dart index 0d51e8f17..2751e94bb 100644 --- a/cw_evm/lib/clients/evm_chain_client.dart +++ b/cw_evm/lib/clients/evm_chain_client.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:developer'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/node.dart'; @@ -144,7 +145,8 @@ class EVMChainClient { final jsonResponse = json.decode(response.body) as Map; - if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) { + if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0 && + jsonResponse['result'] is List) { final symbol = EVMChainUtils.getFeeCurrency(chainId); return (jsonResponse['result'] as List) @@ -297,14 +299,13 @@ class EVMChainClient { Future signTransaction({ required Credentials privateKey, required String toAddress, - required BigInt amount, - required BigInt gasFee, + required Money amount, + required Money gasFee, required int estimatedGasUnits, required int maxFeePerGas, required EVMChainTransactionPriority? priority, required CryptoCurrency currency, required String feeCurrency, - required int exponent, String? contractAddress, String? data, int? gasPrice, @@ -317,11 +318,13 @@ class EVMChainClient { currency == CryptoCurrency.bnb || contractAddress != null); - bool isNativeToken = currency == CryptoCurrency.eth || - currency == CryptoCurrency.maticpoly || - currency == CryptoCurrency.baseEth || - currency == CryptoCurrency.arbEth || - currency == CryptoCurrency.bnb; + final isNativeToken = [ + CryptoCurrency.eth, + CryptoCurrency.maticpoly, + CryptoCurrency.baseEth, + CryptoCurrency.arbEth, + CryptoCurrency.bnb + ].contains(currency); // Get nonce with "pending" block tag to include pending transactions // This prevents "Nonce too low" errors when sending multiple transactions quickly @@ -335,7 +338,7 @@ class EVMChainClient { to: EthereumAddress.fromHex(toAddress), maxPriorityFeePerGas: priority != null ? EtherAmount.fromInt(EtherUnit.gwei, priority.tip) : null, - amount: isNativeToken ? EtherAmount.inWei(amount) : EtherAmount.zero(), + amount: isNativeToken ? EtherAmount.inWei(amount.amount) : EtherAmount.zero(), data: data != null ? hexToBytes(data) : null, maxGas: estimatedGasUnits, maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas), @@ -358,7 +361,7 @@ class EVMChainClient { signedTransaction = await erc20.transfer( EthereumAddress.fromHex(toAddress), - amount, + amount.amount, credentials: privateKey, transaction: transaction, ); @@ -369,24 +372,20 @@ class EVMChainClient { return PendingEVMChainTransaction( signedTransaction: prepareSignedTransactionForSending(signedTransaction), - amount: amount.toString(), + amount: amount, fee: gasFee, - feeCurrency: feeCurrency, sendTransaction: _sendTransaction, - exponent: exponent, ); } Future signApprovalTransaction({ required Credentials privateKey, required String spender, - required BigInt amount, - required BigInt gasFee, - required String feeCurrency, + required Money amount, + required Money gasFee, required int estimatedGasUnits, required int maxFeePerGas, required EVMChainTransactionPriority? priority, - required int exponent, required String contractAddress, int? gasPrice, bool useBlinkProtection = true, @@ -416,20 +415,18 @@ class EVMChainClient { final signedTransaction = await erc20.approve( EthereumAddress.fromHex(spender), - amount, + amount.amount, credentials: privateKey, transaction: transaction, ); return PendingEVMChainTransaction( signedTransaction: prepareSignedTransactionForSending(signedTransaction), - amount: amount.toString(), + amount: amount, fee: gasFee, - feeCurrency: feeCurrency, sendTransaction: () => sendTransaction(signedTransaction, useBlinkProtection: useBlinkProtection), - exponent: exponent, - isInfiniteApproval: amount.toRadixString(16) == + isInfiniteApproval: amount.amount.toRadixString(16) == 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', ); } @@ -523,20 +520,17 @@ class EVMChainClient { */ } - Future fetchERC20Balances( - EthereumAddress userAddress, String contractAddress) async { + Future fetchERC20Balances(EthereumAddress userAddress, Erc20Token token) async { try { - final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!); + final erc20 = ERC20(address: EthereumAddress.fromHex(token.contractAddress), client: _client!); final balance = await erc20.balanceOf(userAddress); - int exponent = (await erc20.decimals()).toInt(); - - return EVMChainERC20Balance(balance, exponent: exponent); + return EVMChainERC20Balance(Money(balance, token)); } on RangeError catch (_) { throw Exception('Invalid token contract for this network.'); } catch (e) { if (e.toString().contains("hostUnreachable")) { - return EVMChainERC20Balance(BigInt.zero); + return EVMChainERC20Balance(Money.zero(token)); } throw Exception('Could not fetch balances: ${e.toString()}'); } diff --git a/cw_evm/lib/deuro/deuro_savings.dart b/cw_evm/lib/deuro/deuro_savings.dart index e35020c72..478439f43 100644 --- a/cw_evm/lib/deuro/deuro_savings.dart +++ b/cw_evm/lib/deuro/deuro_savings.dart @@ -1,5 +1,7 @@ +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_evm/deuro/deuro_savings_contract.dart'; +import 'package:cw_evm/deuro/deuro_savings_gateway_contract.dart' as v1; +import 'package:cw_evm/deuro/deuro_savings_contract.dart' as v2; import 'package:cw_evm/evm_chain_wallet.dart'; import 'package:cw_evm/contract/erc20.dart'; import 'package:cw_evm/evm_chain_exceptions.dart'; @@ -9,21 +11,27 @@ import 'package:web3dart/crypto.dart'; import 'package:web3dart/web3dart.dart'; const String savingsGatewayAddress = "0x073493d73258C4BEb6542e8dd3e1b2891C972303"; +const String savingsV2Address = "0x760233b90e45d186A9A98E911B115F7F4B90d3D9"; const String dEuroAddress = "0xbA3f535bbCcCcA2A154b573Ca6c5A49BAAE0a3ea"; const String frontendCode = "0x00000000000000000000000000000000000000000043616b652057616c6c6574"; class DEuro { - final SavingsGateway _savingsGateway; + final v2.Savings _savings; final ERC20 _dEuro; final EVMChainWallet _wallet; DEuro(EVMChainWallet wallet) : _wallet = wallet, - _savingsGateway = _getSavingsGateway(wallet.getWeb3Client()!), + _savings = _getSavings(wallet.getWeb3Client()!), _dEuro = _getDEuroToken(wallet.getWeb3Client()!); - static SavingsGateway _getSavingsGateway(Web3Client client) => SavingsGateway( + static v2.Savings _getSavings(Web3Client client) => v2.Savings( + address: EthereumAddress.fromHex(savingsV2Address), + client: client, + ); + + static v1.SavingsGateway _getSavingsGateway(Web3Client client) => v1.SavingsGateway( address: EthereumAddress.fromHex(savingsGatewayAddress), client: client, ); @@ -35,26 +43,31 @@ class DEuro { EthereumAddress get _address => EthereumAddress.fromHex(_wallet.walletAddresses.primaryAddress); - Future get savingsBalance async => - (await _savingsGateway.savings(accountOwner: _address)).saved; + Future get savingsBalance async => + Money((await _savings.savings(accountOwner: _address)).saved, CryptoCurrency.deuro); - Future get accruedInterest => _savingsGateway.accruedInterest(accountOwner: _address); + Future get savingsBalanceV1 async => Money( + (await _getSavingsGateway(_wallet.getWeb3Client()!).savings(accountOwner: _address)).saved, + CryptoCurrency.deuro); - Future get interestRate => _savingsGateway.currentRatePPM(); + Future get accruedInterest async => + Money(await _savings.accruedInterest(accountOwner: _address), CryptoCurrency.deuro); - Future get approvedBalance => _dEuro.allowance(_address, _savingsGateway.self.address); + Future get interestRate => _savings.currentRatePPM(); + + Future get approvedBalance => _dEuro.allowance(_address, _savings.self.address); Future _checkEthBalanceForGasFees(EVMChainTransactionPriority priority) async { final ethBalance = await _wallet.getWeb3Client()!.getBalance(_address); final currentBalance = ethBalance.getInWei; + final savings = _getSavingsGateway(_wallet.getWeb3Client()!); final gasFeesModel = await _wallet.calculateActualEstimatedFeeForCreateTransaction( - amount: BigInt.zero, - contractAddress: _savingsGateway.self.address.hexEip55, - receivingAddressHex: _savingsGateway.self.address.hexEip55, + amount: Money.zero(_wallet.currency), + contractAddress: savings.self.address.hexEip55, + receivingAddressHex: savings.self.address.hexEip55, priority: priority, - data: _savingsGateway.self.abi.functions[17] - .encodeCall([BigInt.zero, hexToBytes(frontendCode)]), + data: savings.self.abi.functions[17].encodeCall([BigInt.zero, hexToBytes(frontendCode)]), ); final estimatedGasFee = BigInt.from(gasFeesModel.estimatedGasFee); @@ -73,28 +86,26 @@ class DEuro { try { await _checkEthBalanceForGasFees(priority); - final signedTransaction = await _savingsGateway.save( - (amount: amount, frontendCode: hexToBytes(frontendCode)), + final signedTransaction = await _savings.save( + (amount: amount, compound: true), credentials: _wallet.evmChainPrivateKey, ); final fee = await _wallet.calculateActualEstimatedFeeForCreateTransaction( - amount: amount, - contractAddress: _savingsGateway.self.address.hexEip55, - receivingAddressHex: _savingsGateway.self.address.hexEip55, + amount: Money.zero(_wallet.currency), + contractAddress: _savings.self.address.hexEip55, + receivingAddressHex: _savings.self.address.hexEip55, priority: priority, - data: _savingsGateway.self.abi.functions[17].encodeCall([amount, hexToBytes(frontendCode)]), + data: _savings.self.abi.functions[18].encodeCall([amount, true]), ); - final sendTransaction = () => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction); + sendTransaction() => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction); return PendingEVMChainTransaction( - feeCurrency: "ETH", sendTransaction: sendTransaction, signedTransaction: signedTransaction, - fee: BigInt.from(fee.estimatedGasFee), - amount: amount.toString(), - exponent: 18, + fee: Money(BigInt.from(fee.estimatedGasFee), CryptoCurrency.eth), + amount: Money(amount, CryptoCurrency.deuro), ); } catch (e) { if (e.toString().contains('insufficient funds for gas')) { @@ -110,28 +121,65 @@ class DEuro { try { await _checkEthBalanceForGasFees(priority); - final signedTransaction = await _savingsGateway.withdraw( + final signedTransaction = await _savings.withdraw( + (target: _address, amount: amount), + credentials: _wallet.evmChainPrivateKey, + ); + + final fee = await _wallet.calculateActualEstimatedFeeForCreateTransaction( + amount: Money.zero(_wallet.currency), + contractAddress: _savings.self.address.hexEip55, + receivingAddressHex: _savings.self.address.hexEip55, + priority: priority, + data: _savings.self.abi.functions[23].encodeCall([_address, amount]), + ); + + sendTransaction() => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction); + + return PendingEVMChainTransaction( + sendTransaction: sendTransaction, + signedTransaction: signedTransaction, + fee: Money(BigInt.from(fee.estimatedGasFee), CryptoCurrency.eth), + amount: Money(amount, CryptoCurrency.deuro), + ); + } catch (e) { + if (e.toString().contains('insufficient funds for gas')) { + final ethBalance = await _wallet.getWeb3Client()!.getBalance(_address); + throw InsufficientGasFeeException(currentBalance: ethBalance.getInWei); + } + rethrow; + } + } + + Future withdrawSavingsV1(EVMChainTransactionPriority priority) async { + try { + await _checkEthBalanceForGasFees(priority); + final withdrawAmount = await savingsBalanceV1; + + // Withdraw at least a million to overflow and close the savings position + final amount = BigInt.parse("1000000000000000000000000"); + final savings = _getSavingsGateway(_wallet.getWeb3Client()!); + final signedTransaction = await savings.withdraw( (target: _address, amount: amount, frontendCode: hexToBytes(frontendCode)), credentials: _wallet.evmChainPrivateKey, ); final fee = await _wallet.calculateActualEstimatedFeeForCreateTransaction( - amount: amount, - contractAddress: _savingsGateway.self.address.hexEip55, - receivingAddressHex: _savingsGateway.self.address.hexEip55, + amount: Money.zero(_wallet.currency), + contractAddress: savings.self.address.hexEip55, + receivingAddressHex: savings.self.address.hexEip55, priority: priority, - data: _savingsGateway.self.abi.functions[24].encodeCall([_address, amount, hexToBytes(frontendCode)]), + data: savings.self.abi.functions[24].encodeCall([_address, amount, hexToBytes(frontendCode)]), ); - final sendTransaction = () => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction); + sendTransaction() => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction); return PendingEVMChainTransaction( - sendTransaction: sendTransaction, - signedTransaction: signedTransaction, - fee: BigInt.from(fee.estimatedGasFee), - amount: amount.toString(), - feeCurrency: "ETH", - exponent: 18); + sendTransaction: sendTransaction, + signedTransaction: signedTransaction, + fee: Money(BigInt.from(fee.estimatedGasFee), CryptoCurrency.eth), + amount: withdrawAmount, + ); } catch (e) { if (e.toString().contains('insufficient funds for gas')) { final ethBalance = await _wallet.getWeb3Client()!.getBalance(_address); @@ -145,27 +193,25 @@ class DEuro { try { await _checkEthBalanceForGasFees(priority); - final signedTransaction = await _savingsGateway.refreshBalance((owner: _address), + final signedTransaction = await _savings.refreshBalance((owner: _address), credentials: _wallet.evmChainPrivateKey, ); final fee = await _wallet.calculateActualEstimatedFeeForCreateTransaction( - amount: BigInt.zero, - contractAddress: _savingsGateway.self.address.hexEip55, - receivingAddressHex: _savingsGateway.self.address.hexEip55, + amount: Money.zero(_wallet.currency), + contractAddress: _savings.self.address.hexEip55, + receivingAddressHex: _savings.self.address.hexEip55, priority: priority, - data: _savingsGateway.self.abi.functions[15].encodeCall([_address]), + data: _savings.self.abi.functions[16].encodeCall([_address]), ); - final sendTransaction = () => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction); + sendTransaction() => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction); return PendingEVMChainTransaction( - feeCurrency: "ETH", sendTransaction: sendTransaction, signedTransaction: signedTransaction, - fee: BigInt.from(fee.estimatedGasFee), - amount: 0.toString(), - exponent: 18, + fee: Money(BigInt.from(fee.estimatedGasFee), CryptoCurrency.eth), + amount: Money.zero(CryptoCurrency.deuro), ); } catch (e) { if (e.toString().contains('insufficient funds for gas')) { @@ -182,14 +228,12 @@ class DEuro { await _checkEthBalanceForGasFees(priority); return (await _wallet.createApprovalTransaction( - BigInt.parse( + Money(BigInt.parse( 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', radix: 16, - ), - _savingsGateway.self.address.hexEip55, - CryptoCurrency.deuro, + ), CryptoCurrency.deuro), + _savings.self.address.hexEip55, priority, - "ETH", )) as PendingEVMChainTransaction; } catch (e) { if (e.toString().contains('insufficient funds for gas')) { diff --git a/cw_evm/lib/deuro/deuro_savings_contract.dart b/cw_evm/lib/deuro/deuro_savings_contract.dart index 77316f456..9a8ca5c1b 100644 --- a/cw_evm/lib/deuro/deuro_savings_contract.dart +++ b/cw_evm/lib/deuro/deuro_savings_contract.dart @@ -1,51 +1,38 @@ +// @dart=3.0 +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint // ignore_for_file: unused_local_variable, unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:web3dart/web3dart.dart' as _i1; import 'dart:typed_data' as _i2; +import 'package:web3dart/web3dart.dart' as _i1; + final _contractAbi = _i1.ContractAbi.fromJson( - '[{"inputs":[{"internalType":"contract IDecentralizedEURO","name":"deuro_","type":"address"},{"internalType":"uint24","name":"initialRatePPM","type":"uint24"},{"internalType":"address","name":"gateway_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ChangeNotReady","type":"error"},{"inputs":[],"name":"ModuleDisabled","type":"error"},{"inputs":[],"name":"NoPendingChange","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"interest","type":"uint256"}],"name":"InterestCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint24","name":"newRate","type":"uint24"}],"name":"RateChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"who","type":"address"},{"indexed":false,"internalType":"uint24","name":"nextRate","type":"uint24"},{"indexed":false,"internalType":"uint40","name":"nextChange","type":"uint40"}],"name":"RateProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint192","name":"amount","type":"uint192"}],"name":"Saved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint192","name":"amount","type":"uint192"}],"name":"Withdrawn","type":"event"},{"inputs":[],"name":"GATEWAY","outputs":[{"internalType":"contract IFrontendGateway","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"accountOwner","type":"address"}],"name":"accruedInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"accountOwner","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"accruedInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint192","name":"targetAmount","type":"uint192"},{"internalType":"bytes32","name":"frontendCode","type":"bytes32"}],"name":"adjust","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint192","name":"targetAmount","type":"uint192"}],"name":"adjust","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"applyChange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint192","name":"saved","type":"uint192"},{"internalType":"uint64","name":"ticks","type":"uint64"}],"internalType":"struct Savings.Account","name":"account","type":"tuple"},{"internalType":"uint64","name":"ticks","type":"uint64"}],"name":"calculateInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentRatePPM","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentTicks","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deuro","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"equity","outputs":[{"internalType":"contract IReserve","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextChange","outputs":[{"internalType":"uint40","name":"","type":"uint40"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextRatePPM","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint24","name":"newRatePPM_","type":"uint24"},{"internalType":"address[]","name":"helpers","type":"address[]"}],"name":"proposeChange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"refreshBalance","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"refreshMyBalance","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint192","name":"amount","type":"uint192"},{"internalType":"bytes32","name":"frontendCode","type":"bytes32"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint192","name":"amount","type":"uint192"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint192","name":"amount","type":"uint192"},{"internalType":"bytes32","name":"frontendCode","type":"bytes32"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint192","name":"amount","type":"uint192"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"savings","outputs":[{"internalType":"uint192","name":"saved","type":"uint192"},{"internalType":"uint64","name":"ticks","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"ticks","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint192","name":"amount","type":"uint192"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint192","name":"amount","type":"uint192"},{"internalType":"bytes32","name":"frontendCode","type":"bytes32"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]', - 'SavingsGateway', + '[{"inputs":[{"internalType":"contract IDecentralizedEURO","name":"deuro_","type":"address"},{"internalType":"uint24","name":"initialRatePPM","type":"uint24"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ChangeNotReady","type":"error"},{"inputs":[],"name":"ModuleDisabled","type":"error"},{"inputs":[],"name":"NoPendingChange","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint192","name":"amount","type":"uint192"}],"name":"InterestClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"interest","type":"uint256"},{"indexed":false,"internalType":"bool","name":"compounded","type":"bool"}],"name":"InterestCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint24","name":"newRate","type":"uint24"}],"name":"RateChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"who","type":"address"},{"indexed":false,"internalType":"uint24","name":"nextRate","type":"uint24"},{"indexed":false,"internalType":"uint40","name":"nextChange","type":"uint40"}],"name":"RateProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint192","name":"amount","type":"uint192"}],"name":"Saved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint192","name":"amount","type":"uint192"}],"name":"Withdrawn","type":"event"},{"inputs":[{"internalType":"address","name":"accountOwner","type":"address"}],"name":"accruedInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"accountOwner","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"accruedInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint192","name":"targetAmount","type":"uint192"}],"name":"adjust","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"applyChange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint192","name":"saved","type":"uint192"},{"internalType":"uint64","name":"ticks","type":"uint64"}],"internalType":"struct Savings.Account","name":"account","type":"tuple"},{"internalType":"uint64","name":"ticks","type":"uint64"}],"name":"calculateInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"claimInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"claimableInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentRatePPM","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentTicks","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deuro","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"equity","outputs":[{"internalType":"contract IReserve","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextChange","outputs":[{"internalType":"uint40","name":"","type":"uint40"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextRatePPM","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonCompounding","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint24","name":"newRatePPM_","type":"uint24"},{"internalType":"address[]","name":"helpers","type":"address[]"}],"name":"proposeChange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"refreshBalance","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"refreshMyBalance","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint192","name":"amount","type":"uint192"},{"internalType":"bool","name":"compound","type":"bool"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint192","name":"amount","type":"uint192"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint192","name":"amount","type":"uint192"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"savings","outputs":[{"internalType":"uint192","name":"saved","type":"uint192"},{"internalType":"uint64","name":"ticks","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"ticks","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint192","name":"amount","type":"uint192"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]', + 'Savings', ); -class SavingsGateway extends _i1.GeneratedContract { - SavingsGateway({ +class Savings extends _i1.GeneratedContract { + Savings({ required _i1.EthereumAddress address, required _i1.Web3Client client, int? chainId, }) : super( - _i1.DeployedContract( - _contractAbi, - address, - ), - client, - chainId, - ); + _i1.DeployedContract( + _contractAbi, + address, + ), + client, + chainId, + ); /// The optional [atBlock] parameter can be used to view historical data. When /// set, the function will be evaluated in the specified block. By default, the /// latest on-chain block will be used. - Future<_i1.EthereumAddress> GATEWAY({_i1.BlockNum? atBlock}) async { + Future accruedInterest( + {required _i1.EthereumAddress accountOwner, _i1.BlockNum? atBlock}) async { final function = self.abi.functions[1]; - assert(checkSignature(function, '338c5371')); - final params = []; - final response = await read( - function, - params, - atBlock, - ); - return (response[0] as _i1.EthereumAddress); - } - - /// The optional [atBlock] parameter can be used to view historical data. When - /// set, the function will be evaluated in the specified block. By default, the - /// latest on-chain block will be used. - Future accruedInterest({ - required _i1.EthereumAddress accountOwner, - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[2]; assert(checkSignature(function, '77267ec3')); final params = [accountOwner]; final response = await read( @@ -60,10 +47,10 @@ class SavingsGateway extends _i1.GeneratedContract { /// set, the function will be evaluated in the specified block. By default, the /// latest on-chain block will be used. Future accruedInterest$2( - ({_i1.EthereumAddress accountOwner, BigInt timestamp}) args, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[3]; + ({_i1.EthereumAddress accountOwner, BigInt timestamp}) args, { + _i1.BlockNum? atBlock, + }) async { + final function = self.abi.functions[2]; assert(checkSignature(function, 'a696399d')); final params = [ args.accountOwner, @@ -81,16 +68,31 @@ class SavingsGateway extends _i1.GeneratedContract { /// like the gas price, nonce and max gas. The `data` and `to` fields will be /// set by the contract. Future adjust( - ({BigInt targetAmount, _i2.Uint8List frontendCode}) args, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { + ({BigInt targetAmount}) args, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[3]; + assert(checkSignature(function, 'bdc65152')); + final params = [args.targetAmount]; + return write( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future applyChange({ + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { final function = self.abi.functions[4]; - assert(checkSignature(function, '753ef93c')); - final params = [ - args.targetAmount, - args.frontendCode, - ]; + assert(checkSignature(function, '4e65037e')); + final params = []; return write( credentials, transaction, @@ -103,10 +105,10 @@ class SavingsGateway extends _i1.GeneratedContract { /// set, the function will be evaluated in the specified block. By default, the /// latest on-chain block will be used. Future calculateInterest( - ({dynamic account, BigInt ticks}) args, { - _i1.BlockNum? atBlock, - }) async { - final function = self.abi.functions[7]; + ({dynamic account, BigInt ticks}) args, { + _i1.BlockNum? atBlock, + }) async { + final function = self.abi.functions[5]; assert(checkSignature(function, '7915ce20')); final params = [ args.account, @@ -120,6 +122,43 @@ class SavingsGateway extends _i1.GeneratedContract { return (response[0] as BigInt); } + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future claimInterest( + ({_i1.EthereumAddress target}) args, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[6]; + assert(checkSignature(function, '88d742c2')); + final params = [args.target]; + return write( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future claimableInterest( + ({_i1.EthereumAddress $param7}) args, { + _i1.BlockNum? atBlock, + }) async { + final function = self.abi.functions[7]; + assert(checkSignature(function, '4f24d20a')); + final params = [args.$param7]; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as BigInt); + } + /// The optional [atBlock] parameter can be used to view historical data. When /// set, the function will be evaluated in the specified block. By default, the /// latest on-chain block will be used. @@ -210,15 +249,55 @@ class SavingsGateway extends _i1.GeneratedContract { return (response[0] as BigInt); } + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future nonCompounding( + ({_i1.EthereumAddress $param8}) args, { + _i1.BlockNum? atBlock, + }) async { + final function = self.abi.functions[14]; + assert(checkSignature(function, 'cdb95c3b')); + final params = [args.$param8]; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as bool); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future proposeChange( + ({BigInt newRatePPM_, List<_i1.EthereumAddress> helpers}) args, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[15]; + assert(checkSignature(function, '1de252e4')); + final params = [ + args.newRatePPM_, + args.helpers, + ]; + return write( + credentials, + transaction, + function, + params, + ); + } + /// The optional [transaction] parameter can be used to override parameters /// like the gas price, nonce and max gas. The `data` and `to` fields will be /// set by the contract. Future<_i2.Uint8List> refreshBalance( - ({_i1.EthereumAddress owner}) args, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[15]; + ({_i1.EthereumAddress owner}) args, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[16]; assert(checkSignature(function, 'b77cd1c7')); final params = [args.owner]; return writeRaw( @@ -236,7 +315,7 @@ class SavingsGateway extends _i1.GeneratedContract { required _i1.Credentials credentials, _i1.Transaction? transaction, }) async { - final function = self.abi.functions[16]; + final function = self.abi.functions[17]; assert(checkSignature(function, '85bd12d1')); final params = []; return writeRaw( @@ -251,42 +330,15 @@ class SavingsGateway extends _i1.GeneratedContract { /// like the gas price, nonce and max gas. The `data` and `to` fields will be /// set by the contract. Future<_i2.Uint8List> save( - ({BigInt amount, _i2.Uint8List frontendCode}) args, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[17]; - assert(checkSignature(function, '9e2363dc')); + ({BigInt amount, bool compound}) args, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[18]; + assert(checkSignature(function, 'a776f78c')); final params = [ args.amount, - args.frontendCode, - ]; - return writeRaw( - credentials, - transaction, - function, - params, - ); - } - - /// The optional [transaction] parameter can be used to override parameters - /// like the gas price, nonce and max gas. The `data` and `to` fields will be - /// set by the contract. - Future<_i2.Uint8List> saveTo( - ({ - _i1.EthereumAddress owner, - BigInt amount, - _i2.Uint8List frontendCode - }) args, { - required _i1.Credentials credentials, - _i1.Transaction? transaction, - }) async { - final function = self.abi.functions[19]; - assert(checkSignature(function, 'cbcf9676')); - final params = [ - args.owner, - args.amount, - args.frontendCode, + args.compound, ]; return writeRaw( credentials, @@ -299,7 +351,7 @@ class SavingsGateway extends _i1.GeneratedContract { /// The optional [atBlock] parameter can be used to view historical data. When /// set, the function will be evaluated in the specified block. By default, the /// latest on-chain block will be used. - Future savings({ + Future savings({ required _i1.EthereumAddress accountOwner, _i1.BlockNum? atBlock, }) async { @@ -311,27 +363,40 @@ class SavingsGateway extends _i1.GeneratedContract { params, atBlock, ); - return Savings(response); + return SavingsData(response); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future ticks( + ({BigInt timestamp}) args, { + _i1.BlockNum? atBlock, + }) async { + final function = self.abi.functions[22]; + assert(checkSignature(function, '534cb30d')); + final params = [args.timestamp]; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as BigInt); } /// The optional [transaction] parameter can be used to override parameters /// like the gas price, nonce and max gas. The `data` and `to` fields will be /// set by the contract. Future<_i2.Uint8List> withdraw( - ({ - _i1.EthereumAddress target, - BigInt amount, - _i2.Uint8List frontendCode - }) args, { + ({_i1.EthereumAddress target, BigInt amount}) args, { required _i1.Credentials credentials, _i1.Transaction? transaction, }) async { - final function = self.abi.functions[24]; - assert(checkSignature(function, '829a0476')); + final function = self.abi.functions[23]; + assert(checkSignature(function, '1791dc5e')); final params = [ args.target, args.amount, - args.frontendCode, ]; return writeRaw( credentials, @@ -341,6 +406,30 @@ class SavingsGateway extends _i1.GeneratedContract { ); } + /// Returns a live stream of all InterestClaimed events emitted by this contract. + Stream interestClaimedEvents({ + _i1.BlockNum? fromBlock, + _i1.BlockNum? toBlock, + }) { + final event = self.event('InterestClaimed'); + final filter = _i1.FilterOptions.events( + contract: self, + event: event, + fromBlock: fromBlock, + toBlock: toBlock, + ); + return client.events(filter).map((_i1.FilterEvent result) { + final decoded = event.decodeResults( + result.topics!, + result.data!, + ); + return InterestClaimed( + decoded, + result, + ); + }); + } + /// Returns a live stream of all InterestCollected events emitted by this contract. Stream interestCollectedEvents({ _i1.BlockNum? fromBlock, @@ -462,8 +551,8 @@ class SavingsGateway extends _i1.GeneratedContract { } } -class Savings { - Savings(List response) +class SavingsData { + SavingsData(List response) : saved = (response[0] as BigInt), ticks = (response[1] as BigInt); @@ -472,25 +561,42 @@ class Savings { final BigInt ticks; } +class InterestClaimed { + InterestClaimed( + List response, + this.event, + ) : account = (response[0] as _i1.EthereumAddress), + amount = (response[1] as BigInt); + + final _i1.EthereumAddress account; + + final BigInt amount; + + final _i1.FilterEvent event; +} + class InterestCollected { InterestCollected( - List response, - this.event, - ) : account = (response[0] as _i1.EthereumAddress), - interest = (response[1] as BigInt); + List response, + this.event, + ) : account = (response[0] as _i1.EthereumAddress), + interest = (response[1] as BigInt), + compounded = (response[2] as bool); final _i1.EthereumAddress account; final BigInt interest; + final bool compounded; + final _i1.FilterEvent event; } class RateChanged { RateChanged( - List response, - this.event, - ) : newRate = (response[0] as BigInt); + List response, + this.event, + ) : newRate = (response[0] as BigInt); final BigInt newRate; @@ -499,9 +605,9 @@ class RateChanged { class RateProposed { RateProposed( - List response, - this.event, - ) : who = (response[0] as _i1.EthereumAddress), + List response, + this.event, + ) : who = (response[0] as _i1.EthereumAddress), nextRate = (response[1] as BigInt), nextChange = (response[2] as BigInt); @@ -516,9 +622,9 @@ class RateProposed { class Saved { Saved( - List response, - this.event, - ) : account = (response[0] as _i1.EthereumAddress), + List response, + this.event, + ) : account = (response[0] as _i1.EthereumAddress), amount = (response[1] as BigInt); final _i1.EthereumAddress account; @@ -530,9 +636,9 @@ class Saved { class Withdrawn { Withdrawn( - List response, - this.event, - ) : account = (response[0] as _i1.EthereumAddress), + List response, + this.event, + ) : account = (response[0] as _i1.EthereumAddress), amount = (response[1] as BigInt); final _i1.EthereumAddress account; diff --git a/cw_evm/lib/deuro/deuro_savings_gateway_contract.dart b/cw_evm/lib/deuro/deuro_savings_gateway_contract.dart new file mode 100644 index 000000000..77316f456 --- /dev/null +++ b/cw_evm/lib/deuro/deuro_savings_gateway_contract.dart @@ -0,0 +1,543 @@ +// ignore_for_file: type=lint +// ignore_for_file: unused_local_variable, unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:web3dart/web3dart.dart' as _i1; +import 'dart:typed_data' as _i2; + +final _contractAbi = _i1.ContractAbi.fromJson( + '[{"inputs":[{"internalType":"contract IDecentralizedEURO","name":"deuro_","type":"address"},{"internalType":"uint24","name":"initialRatePPM","type":"uint24"},{"internalType":"address","name":"gateway_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ChangeNotReady","type":"error"},{"inputs":[],"name":"ModuleDisabled","type":"error"},{"inputs":[],"name":"NoPendingChange","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"interest","type":"uint256"}],"name":"InterestCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint24","name":"newRate","type":"uint24"}],"name":"RateChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"who","type":"address"},{"indexed":false,"internalType":"uint24","name":"nextRate","type":"uint24"},{"indexed":false,"internalType":"uint40","name":"nextChange","type":"uint40"}],"name":"RateProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint192","name":"amount","type":"uint192"}],"name":"Saved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint192","name":"amount","type":"uint192"}],"name":"Withdrawn","type":"event"},{"inputs":[],"name":"GATEWAY","outputs":[{"internalType":"contract IFrontendGateway","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"accountOwner","type":"address"}],"name":"accruedInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"accountOwner","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"accruedInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint192","name":"targetAmount","type":"uint192"},{"internalType":"bytes32","name":"frontendCode","type":"bytes32"}],"name":"adjust","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint192","name":"targetAmount","type":"uint192"}],"name":"adjust","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"applyChange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint192","name":"saved","type":"uint192"},{"internalType":"uint64","name":"ticks","type":"uint64"}],"internalType":"struct Savings.Account","name":"account","type":"tuple"},{"internalType":"uint64","name":"ticks","type":"uint64"}],"name":"calculateInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentRatePPM","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentTicks","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deuro","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"equity","outputs":[{"internalType":"contract IReserve","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextChange","outputs":[{"internalType":"uint40","name":"","type":"uint40"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextRatePPM","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint24","name":"newRatePPM_","type":"uint24"},{"internalType":"address[]","name":"helpers","type":"address[]"}],"name":"proposeChange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"refreshBalance","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"refreshMyBalance","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint192","name":"amount","type":"uint192"},{"internalType":"bytes32","name":"frontendCode","type":"bytes32"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint192","name":"amount","type":"uint192"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint192","name":"amount","type":"uint192"},{"internalType":"bytes32","name":"frontendCode","type":"bytes32"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint192","name":"amount","type":"uint192"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"savings","outputs":[{"internalType":"uint192","name":"saved","type":"uint192"},{"internalType":"uint64","name":"ticks","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"ticks","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint192","name":"amount","type":"uint192"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint192","name":"amount","type":"uint192"},{"internalType":"bytes32","name":"frontendCode","type":"bytes32"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]', + 'SavingsGateway', +); + +class SavingsGateway extends _i1.GeneratedContract { + SavingsGateway({ + required _i1.EthereumAddress address, + required _i1.Web3Client client, + int? chainId, + }) : super( + _i1.DeployedContract( + _contractAbi, + address, + ), + client, + chainId, + ); + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future<_i1.EthereumAddress> GATEWAY({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[1]; + assert(checkSignature(function, '338c5371')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as _i1.EthereumAddress); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future accruedInterest({ + required _i1.EthereumAddress accountOwner, + _i1.BlockNum? atBlock, + }) async { + final function = self.abi.functions[2]; + assert(checkSignature(function, '77267ec3')); + final params = [accountOwner]; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as BigInt); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future accruedInterest$2( + ({_i1.EthereumAddress accountOwner, BigInt timestamp}) args, { + _i1.BlockNum? atBlock, + }) async { + final function = self.abi.functions[3]; + assert(checkSignature(function, 'a696399d')); + final params = [ + args.accountOwner, + args.timestamp, + ]; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as BigInt); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future adjust( + ({BigInt targetAmount, _i2.Uint8List frontendCode}) args, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[4]; + assert(checkSignature(function, '753ef93c')); + final params = [ + args.targetAmount, + args.frontendCode, + ]; + return write( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future calculateInterest( + ({dynamic account, BigInt ticks}) args, { + _i1.BlockNum? atBlock, + }) async { + final function = self.abi.functions[7]; + assert(checkSignature(function, '7915ce20')); + final params = [ + args.account, + args.ticks, + ]; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as BigInt); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future currentRatePPM({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[8]; + assert(checkSignature(function, '06a7b376')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as BigInt); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future currentTicks({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[9]; + assert(checkSignature(function, 'b079f163')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as BigInt); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future<_i1.EthereumAddress> deuro({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[10]; + assert(checkSignature(function, '82b8eaf5')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as _i1.EthereumAddress); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future<_i1.EthereumAddress> equity({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[11]; + assert(checkSignature(function, '91a0ac6a')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as _i1.EthereumAddress); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future nextChange({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[12]; + assert(checkSignature(function, 'b6f83c17')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as BigInt); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future nextRatePPM({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[13]; + assert(checkSignature(function, '2e4b20ab')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as BigInt); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future<_i2.Uint8List> refreshBalance( + ({_i1.EthereumAddress owner}) args, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[15]; + assert(checkSignature(function, 'b77cd1c7')); + final params = [args.owner]; + return writeRaw( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future<_i2.Uint8List> refreshMyBalance({ + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[16]; + assert(checkSignature(function, '85bd12d1')); + final params = []; + return writeRaw( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future<_i2.Uint8List> save( + ({BigInt amount, _i2.Uint8List frontendCode}) args, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[17]; + assert(checkSignature(function, '9e2363dc')); + final params = [ + args.amount, + args.frontendCode, + ]; + return writeRaw( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future<_i2.Uint8List> saveTo( + ({ + _i1.EthereumAddress owner, + BigInt amount, + _i2.Uint8List frontendCode + }) args, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[19]; + assert(checkSignature(function, 'cbcf9676')); + final params = [ + args.owner, + args.amount, + args.frontendCode, + ]; + return writeRaw( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future savings({ + required _i1.EthereumAddress accountOwner, + _i1.BlockNum? atBlock, + }) async { + final function = self.abi.functions[21]; + assert(checkSignature(function, '1f7cdd5f')); + final params = [accountOwner]; + final response = await read( + function, + params, + atBlock, + ); + return Savings(response); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future<_i2.Uint8List> withdraw( + ({ + _i1.EthereumAddress target, + BigInt amount, + _i2.Uint8List frontendCode + }) args, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[24]; + assert(checkSignature(function, '829a0476')); + final params = [ + args.target, + args.amount, + args.frontendCode, + ]; + return writeRaw( + credentials, + transaction, + function, + params, + ); + } + + /// Returns a live stream of all InterestCollected events emitted by this contract. + Stream interestCollectedEvents({ + _i1.BlockNum? fromBlock, + _i1.BlockNum? toBlock, + }) { + final event = self.event('InterestCollected'); + final filter = _i1.FilterOptions.events( + contract: self, + event: event, + fromBlock: fromBlock, + toBlock: toBlock, + ); + return client.events(filter).map((_i1.FilterEvent result) { + final decoded = event.decodeResults( + result.topics!, + result.data!, + ); + return InterestCollected( + decoded, + result, + ); + }); + } + + /// Returns a live stream of all RateChanged events emitted by this contract. + Stream rateChangedEvents({ + _i1.BlockNum? fromBlock, + _i1.BlockNum? toBlock, + }) { + final event = self.event('RateChanged'); + final filter = _i1.FilterOptions.events( + contract: self, + event: event, + fromBlock: fromBlock, + toBlock: toBlock, + ); + return client.events(filter).map((_i1.FilterEvent result) { + final decoded = event.decodeResults( + result.topics!, + result.data!, + ); + return RateChanged( + decoded, + result, + ); + }); + } + + /// Returns a live stream of all RateProposed events emitted by this contract. + Stream rateProposedEvents({ + _i1.BlockNum? fromBlock, + _i1.BlockNum? toBlock, + }) { + final event = self.event('RateProposed'); + final filter = _i1.FilterOptions.events( + contract: self, + event: event, + fromBlock: fromBlock, + toBlock: toBlock, + ); + return client.events(filter).map((_i1.FilterEvent result) { + final decoded = event.decodeResults( + result.topics!, + result.data!, + ); + return RateProposed( + decoded, + result, + ); + }); + } + + /// Returns a live stream of all Saved events emitted by this contract. + Stream savedEvents({ + _i1.BlockNum? fromBlock, + _i1.BlockNum? toBlock, + }) { + final event = self.event('Saved'); + final filter = _i1.FilterOptions.events( + contract: self, + event: event, + fromBlock: fromBlock, + toBlock: toBlock, + ); + return client.events(filter).map((_i1.FilterEvent result) { + final decoded = event.decodeResults( + result.topics!, + result.data!, + ); + return Saved( + decoded, + result, + ); + }); + } + + /// Returns a live stream of all Withdrawn events emitted by this contract. + Stream withdrawnEvents({ + _i1.BlockNum? fromBlock, + _i1.BlockNum? toBlock, + }) { + final event = self.event('Withdrawn'); + final filter = _i1.FilterOptions.events( + contract: self, + event: event, + fromBlock: fromBlock, + toBlock: toBlock, + ); + return client.events(filter).map((_i1.FilterEvent result) { + final decoded = event.decodeResults( + result.topics!, + result.data!, + ); + return Withdrawn( + decoded, + result, + ); + }); + } +} + +class Savings { + Savings(List response) + : saved = (response[0] as BigInt), + ticks = (response[1] as BigInt); + + final BigInt saved; + + final BigInt ticks; +} + +class InterestCollected { + InterestCollected( + List response, + this.event, + ) : account = (response[0] as _i1.EthereumAddress), + interest = (response[1] as BigInt); + + final _i1.EthereumAddress account; + + final BigInt interest; + + final _i1.FilterEvent event; +} + +class RateChanged { + RateChanged( + List response, + this.event, + ) : newRate = (response[0] as BigInt); + + final BigInt newRate; + + final _i1.FilterEvent event; +} + +class RateProposed { + RateProposed( + List response, + this.event, + ) : who = (response[0] as _i1.EthereumAddress), + nextRate = (response[1] as BigInt), + nextChange = (response[2] as BigInt); + + final _i1.EthereumAddress who; + + final BigInt nextRate; + + final BigInt nextChange; + + final _i1.FilterEvent event; +} + +class Saved { + Saved( + List response, + this.event, + ) : account = (response[0] as _i1.EthereumAddress), + amount = (response[1] as BigInt); + + final _i1.EthereumAddress account; + + final BigInt amount; + + final _i1.FilterEvent event; +} + +class Withdrawn { + Withdrawn( + List response, + this.event, + ) : account = (response[0] as _i1.EthereumAddress), + amount = (response[1] as BigInt); + + final _i1.EthereumAddress account; + + final BigInt amount; + + final _i1.FilterEvent event; +} diff --git a/cw_evm/lib/evm_chain_transaction_info.dart b/cw_evm/lib/evm_chain_transaction_info.dart index 128c00eab..6ce7a716c 100644 --- a/cw_evm/lib/evm_chain_transaction_info.dart +++ b/cw_evm/lib/evm_chain_transaction_info.dart @@ -1,18 +1,19 @@ // ignore_for_file: overridden_fields, annotate_overrides -import 'dart:math'; - +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/format_amount.dart'; -import 'package:cw_core/format_fixed.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; +import 'package:cw_evm/evm_chain_registry.dart'; import 'package:cw_evm/utils/evm_chain_utils.dart'; class EVMChainTransactionInfo extends TransactionInfo { EVMChainTransactionInfo({ required this.id, required this.height, - required this.ethAmount, - required this.ethFee, + required this.amount, + required this.fee, required this.tokenSymbol, this.exponent = 18, required this.direction, @@ -24,19 +25,16 @@ class EVMChainTransactionInfo extends TransactionInfo { this.evmSignatureName, this.contractAddress, required this.chainId, - }) : amount = ethAmount.toInt(), - fee = ethFee.toInt(); + }); final String id; final int height; - final int amount; - final BigInt ethAmount; + final Money amount; final int exponent; final TransactionDirection direction; final DateTime date; final bool isPending; - final int fee; - final BigInt ethFee; + final Money fee; final int confirmations; final String tokenSymbol; String? _fiatAmount; @@ -49,39 +47,32 @@ class EVMChainTransactionInfo extends TransactionInfo { /// Get fee currency symbol based on wallet type String get feeCurrency => EVMChainUtils.getFeeCurrency(chainId); - String get _displayTokenSymbol { - if (chainId == 56 && tokenSymbol.toUpperCase() == 'BSC-USD') return 'USDT'; - - return tokenSymbol; - } - - @override - String amountFormatted() { - final amount = formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString()); - return '${amount.substring(0, min(10, amount.length))} $_displayTokenSymbol'; - } - @override String fiatAmount() => _fiatAmount ?? ''; @override void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); - @override - String feeFormatted() => '${formatFixed(ethFee, 18)} $feeCurrency'; - factory EVMChainTransactionInfo.fromJson(Map data, int chainId) { + final decimals = data['exponent'] as int? ?? 18; + final tokenSymbol = data['tokenSymbol'] as String; + final currency = + Erc20Token(name: '', symbol: tokenSymbol, contractAddress: '', decimal: decimals); + + final feeCurrency = + EvmChainRegistry().getChainConfig(chainId)?.nativeCurrency ?? CryptoCurrency.eth; + return EVMChainTransactionInfo( id: data['id'] as String, height: data['height'] as int, - ethAmount: BigInt.parse(data['amount'] as String), - exponent: data['exponent'] as int? ?? 18, - ethFee: BigInt.parse(data['fee'] as String), + amount: Money(BigInt.parse(data['amount'] as String), currency), + exponent: decimals, + fee: Money(BigInt.parse(data['fee'] as String), feeCurrency), direction: TransactionDirection.values[data['direction'] as int], date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), isPending: data['isPending'] as bool? ?? false, confirmations: data['confirmations'] as int, - tokenSymbol: data['tokenSymbol'] as String, + tokenSymbol: tokenSymbol, to: data['to'] as String?, from: data['from'] as String?, evmSignatureName: data['evmSignatureName'] as String?, @@ -93,9 +84,9 @@ class EVMChainTransactionInfo extends TransactionInfo { Map toJson() => { 'id': id, 'height': height, - 'amount': ethAmount.toString(), + 'amount': amount.amount.toString(), 'exponent': exponent, - 'fee': ethFee.toString(), + 'fee': fee.amount.toString(), 'direction': direction.index, 'date': date.millisecondsSinceEpoch, 'isPending': isPending, diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index 66ec7577c..4b002e396 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -5,6 +5,7 @@ import 'dart:typed_data'; import 'package:bip32/bip32.dart' as bip32; import 'package:bip39/bip39.dart' as bip39; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/encryption_file_utils.dart'; @@ -28,7 +29,6 @@ import 'package:cw_evm/evm_chain_default_tokens.dart'; import 'package:cw_evm/evm_chain_exceptions.dart'; import 'package:cw_evm/evm_chain_registry.dart'; import 'package:cw_evm/evm_chain_transaction_credentials.dart'; -import 'package:cw_evm/utils/evm_chain_formatter.dart'; import 'package:cw_evm/evm_chain_transaction_history.dart'; import 'package:cw_evm/evm_chain_transaction_model.dart'; import 'package:cw_evm/evm_chain_transaction_priority.dart'; @@ -103,7 +103,7 @@ abstract class EVMChainWalletBase walletInfo, initialChainId ?? _getInitialChainId(walletInfo.type)), balance = ObservableMap.of( { - nativeCurrency: initialBalance ?? EVMChainERC20Balance(BigInt.zero), + nativeCurrency: initialBalance ?? EVMChainERC20Balance(Money.zero(nativeCurrency)), }, ), super(walletInfo, derivationInfo) { @@ -411,20 +411,29 @@ abstract class EVMChainWalletBase EVMChainTransactionModel transactionModel, String address, ) { + final decimals = transactionModel.tokenDecimal ?? 18; + final tokenSymbol = transactionModel.tokenSymbol ?? + EVMChainUtils.getDefaultTokenSymbol(transactionModel.chainId); + + final amountCurrency = Erc20Token( + name: '', + contractAddress: transactionModel.contractAddress, + decimal: decimals, + symbol: tokenSymbol, + ); return EVMChainTransactionInfo( id: transactionModel.hash, height: transactionModel.blockNumber, - ethAmount: transactionModel.amount, + amount: Money(transactionModel.amount, amountCurrency), direction: transactionModel.from == address ? TransactionDirection.outgoing : TransactionDirection.incoming, isPending: false, date: transactionModel.date, confirmations: transactionModel.confirmations, - ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice, + fee: Money(BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice, currency), exponent: transactionModel.tokenDecimal ?? 18, - tokenSymbol: transactionModel.tokenSymbol ?? - EVMChainUtils.getDefaultTokenSymbol(transactionModel.chainId), + tokenSymbol: tokenSymbol, to: transactionModel.to, from: transactionModel.from, evmSignatureName: transactionModel.evmSignatureName, @@ -473,11 +482,10 @@ abstract class EVMChainWalletBase @override Future checkNodeHealth() async { try { - // Check native balance await _client.getBalance(_evmChainPrivateKey.address); - // Check USDC token balance - String usdcContractAddress = _getUSDCContractAddress(); + final usdcContractAddress = Erc20Token( + name: "USDC", symbol: "USDC", contractAddress: _getUSDCContractAddress(), decimal: 6); await _client.fetchERC20Balances(_evmChainPrivateKey.address, usdcContractAddress); @@ -531,7 +539,7 @@ abstract class EVMChainWalletBase // Ensure balance is initialized for current currency (in case currency changed) if (!balance.containsKey(currency)) { - balance[currency] = EVMChainERC20Balance(BigInt.zero); + balance[currency] = EVMChainERC20Balance(Money.zero(currency)); } await save(); @@ -757,7 +765,7 @@ abstract class EVMChainWalletBase /// - The addressHex for the receiving wallet, /// - A contract address which would be essential in determining if to calculate the estimate for ERC20 or native ETH Future calculateActualEstimatedFeeForCreateTransaction({ - required amount, + required Money amount, required String? contractAddress, required String receivingAddressHex, required TransactionPriority? priority, @@ -774,7 +782,6 @@ abstract class EVMChainWalletBase final gasBaseFee = await _client.getGasBaseFee(); final gasPrice = await _client.getGasUnitPrice(); - // Validate that we got valid gas price if (gasPrice <= 0) { printV('Invalid gas price received: $gasPrice'); throw EVMChainTransactionFeesException('Failed to retrieve gas price from node'); @@ -791,7 +798,7 @@ abstract class EVMChainWalletBase final estimatedGas = await _client.getEstimatedGasUnitsForTransaction( contractAddress: contractAddress, senderAddress: _evmChainPrivateKey.address, - value: EtherAmount.fromBigInt(EtherUnit.wei, amount!), + value: EtherAmount.fromBigInt(EtherUnit.wei, amount.amount), gasPrice: EtherAmount.fromInt(EtherUnit.wei, adjustedGasPrice), toAddress: EthereumAddress.fromHex(receivingAddressHex), maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas), @@ -932,15 +939,15 @@ abstract class EVMChainWalletBase 'Currency ${_credentials.currency.title} ${_credentials.currency.tag} is not accessible in the wallet, try to enable it first.')); final currencyBalance = balance[transactionCurrency]!; - BigInt totalAmount = BigInt.zero; - BigInt estimatedFeesForTransaction = BigInt.zero; - int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18; - String? contractAddress; - int estimatedGasUnitsForTransaction = 0; - int maxFeePerGasForTransaction = 0; - String toAddress = _credentials.outputs.first.isParsedAddress + final toAddress = _credentials.outputs.first.isParsedAddress ? _credentials.outputs.first.extractedAddress! : _credentials.outputs.first.address; + var totalAmount = Money.zero(transactionCurrency); + var estimatedFeesForTransaction = Money.zero(currency); + var estimatedGasUnitsForTransaction = 0; + var maxFeePerGasForTransaction = 0; + + String? contractAddress; if (transactionCurrency is Erc20Token) { contractAddress = transactionCurrency.contractAddress; @@ -948,23 +955,12 @@ abstract class EVMChainWalletBase // so far this can not be made with Ethereum as Ethereum does not support multiple recipients if (hasMultiDestination) { - if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) { + if (outputs.any((item) => item.sendAll || item.cryptoAmount.amount <= BigInt.zero)) { throw EVMChainTransactionCreationException(transactionCurrency); } - totalAmount = outputs.fold( - BigInt.zero, - (acc, output) { - if (output.cryptoAmount != null && output.cryptoAmount!.isNotEmpty) { - return acc + - EVMChainFormatter.parseEVMChainAmountToBigInt( - output.cryptoAmount!, - decimals: exponent, - ); - } - return acc; - }, - ); + totalAmount = outputs.fold(Money.zero(transactionCurrency), + (acc, output) => acc + output.cryptoAmount.copyWith(currency: transactionCurrency)); final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction( amount: totalAmount, @@ -973,28 +969,22 @@ abstract class EVMChainWalletBase contractAddress: contractAddress, ); - estimatedFeesForTransaction = BigInt.from(gasFeesModel.estimatedGasFee); + estimatedFeesForTransaction = + estimatedFeesForTransaction.copyWith(amount: BigInt.from(gasFeesModel.estimatedGasFee)); estimatedGasUnitsForTransaction = gasFeesModel.estimatedGasUnits; maxFeePerGasForTransaction = gasFeesModel.maxFeePerGas; - if (currencyBalance.balance < totalAmount) { + if (currencyBalance.available < totalAmount) { throw EVMChainTransactionCreationException(transactionCurrency); } } else { final output = outputs.first; if (!output.sendAll) { - if (output.cryptoAmount != null && output.cryptoAmount!.isNotEmpty) { - totalAmount = EVMChainFormatter.parseEVMChainAmountToBigInt( - output.cryptoAmount!, - decimals: exponent, - ); - } else { - totalAmount = BigInt.zero; - } + totalAmount = output.cryptoAmount.copyWith(currency: transactionCurrency); } if (output.sendAll && transactionCurrency is Erc20Token) { - totalAmount = currencyBalance.balance; + totalAmount = currencyBalance.available; } final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction( @@ -1004,7 +994,8 @@ abstract class EVMChainWalletBase contractAddress: contractAddress, ); - estimatedFeesForTransaction = BigInt.from(gasFeesModel.estimatedGasFee); + estimatedFeesForTransaction = + estimatedFeesForTransaction.copyWith(amount: BigInt.from(gasFeesModel.estimatedGasFee)); estimatedGasUnitsForTransaction = gasFeesModel.estimatedGasUnits; maxFeePerGasForTransaction = gasFeesModel.maxFeePerGas; @@ -1012,30 +1003,30 @@ abstract class EVMChainWalletBase if (selectedChainId == 8453) { // Applying a small buffer to account for gas price fluctuations // 10% or minimum 10,000 wei, whichever is higher - final refinedGasFee = estimatedFeesForTransaction; + final refinedGasFee = estimatedFeesForTransaction.amount; final gasBufferPercent = refinedGasFee * BigInt.from(110) ~/ BigInt.from(100); final gasBufferMin = refinedGasFee + BigInt.from(10000); final gasBuffer = gasBufferPercent > gasBufferMin ? gasBufferPercent : gasBufferMin; // Using the buffered fee for the final amount - totalAmount = (currencyBalance.balance - gasBuffer); - estimatedFeesForTransaction = gasBuffer; + totalAmount = totalAmount.copyWith(amount: currencyBalance.available.amount - gasBuffer); + estimatedFeesForTransaction = estimatedFeesForTransaction.copyWith(amount: gasBuffer); } else { // Calculating the final amount with the estimated gas fee - totalAmount = (currencyBalance.balance - estimatedFeesForTransaction); + totalAmount = currencyBalance.available - estimatedFeesForTransaction; } } // check the fees on the base currency - if (estimatedFeesForTransaction > balance[currency]!.balance) { + if (estimatedFeesForTransaction > balance[currency]!.available) { throw EVMChainTransactionFeesException.fromCurrency(currency.title); } - if (currencyBalance.balance < totalAmount) { + if (currencyBalance.available < totalAmount) { throw EVMChainTransactionCreationException(transactionCurrency); } if (transactionCurrency is! Erc20Token && - totalAmount + estimatedFeesForTransaction > currencyBalance.balance) { + totalAmount + estimatedFeesForTransaction > currencyBalance.available) { throw EVMChainTransactionFeesException.fromCurrency(currency.title); } } @@ -1056,7 +1047,6 @@ abstract class EVMChainWalletBase currency: transactionCurrency, feeCurrency: EVMChainUtils.getFeeCurrency(selectedChainId), maxFeePerGas: maxFeePerGasForTransaction, - exponent: exponent, contractAddress: transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null, data: hexOpReturnMemo, @@ -1070,7 +1060,7 @@ abstract class EVMChainWalletBase Future createCallDataTransaction( String to, String dataHex, - BigInt valueWei, + Money valueWei, EVMChainTransactionPriority? priority, String? sourceTokenAddress, BigInt? sourceTokenAmount, { @@ -1101,37 +1091,36 @@ abstract class EVMChainWalletBase gas = GasParamsHandler.zero(); } - // Validate NATIVE Balance (for Gas + Value) - final nativeBal = balance[nativeCurrency]?.balance ?? BigInt.zero; - final requiredNative = valueWei + BigInt.from(gas.estimatedGasFee); + final nativeBal = balance[nativeCurrency]?.available ?? Money.zero(nativeCurrency); + var requiredNative = Money.fromInt(gas.estimatedGasFee, nativeCurrency); + + if (valueWei.currency == nativeCurrency) { + requiredNative += valueWei; + } if (requiredNative > nativeBal) { throw Exception('Not enough ${nativeCurrency.title} to cover value and fees.'); } - // Validate ERC20 Balance (Only if source is NOT native) final cleanAddress = sourceTokenAddress?.toLowerCase() ?? ''; - // Check for both 0xeeee... AND 0x0000... (Zero Address) - bool isNativeSource = sourceTokenAddress == null || + final isNativeSource = sourceTokenAddress == null || cleanAddress == '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' || cleanAddress == '0x0000000000000000000000000000000000000000'; if (!isNativeSource && sourceTokenAmount != null && sourceTokenAmount > BigInt.zero) { - // Filter list to find match. final matchingTokens = balance.keys .where((k) => k is Erc20Token && k.contractAddress.toLowerCase() == cleanAddress); if (matchingTokens.isEmpty) { - // Token is not in the wallet balance map -> Balance is 0 throw Exception('Insufficient token balance (Token not found in wallet).'); } final tokenKey = matchingTokens.first; - final tokenBalance = balance[tokenKey]?.balance ?? BigInt.zero; + final tokenBalance = balance[tokenKey]?.available ?? Money.zero(tokenKey); - if (tokenBalance < sourceTokenAmount) { - throw Exception('Insufficient ${tokenKey.title} balance to cover the transaction amount.'); + if (tokenBalance < Money(sourceTokenAmount, tokenKey)) { + throw Exception('Insufficient ${tokenKey.symbol} balance to cover the transaction amount.'); } } @@ -1144,13 +1133,12 @@ abstract class EVMChainWalletBase privateKey: _evmChainPrivateKey, toAddress: to, amount: valueWei, - gasFee: BigInt.from(gas.estimatedGasFee), + gasFee: Money.fromInt(gas.estimatedGasFee, currency), estimatedGasUnits: gasUnits, maxFeePerGas: gas.maxFeePerGas, priority: priority, currency: nativeCurrency, feeCurrency: nativeCurrency.title, - exponent: 18, contractAddress: null, data: dataHex, gasPrice: gas.gasPrice, @@ -1161,23 +1149,23 @@ abstract class EVMChainWalletBase } } - Future createApprovalTransaction(BigInt amount, String spender, - CryptoCurrency token, EVMChainTransactionPriority? priority, String feeCurrency, + Future createApprovalTransaction( + Money amount, String spender, EVMChainTransactionPriority? priority, {bool useBlinkProtection = true}) async { - final CryptoCurrency transactionCurrency = - balance.keys.firstWhere((element) => element.title == token.title); + final transactionCurrency = + balance.keys.firstWhere((element) => element.symbol == amount.currency.symbol); assert(transactionCurrency is Erc20Token); final data = _client.getEncodedDataForApprovalTransaction( contractAddress: EthereumAddress.fromHex((transactionCurrency as Erc20Token).contractAddress), - value: EtherAmount.fromBigInt(EtherUnit.wei, amount), + value: EtherAmount.fromBigInt(EtherUnit.wei, amount.amount), toAddress: EthereumAddress.fromHex(spender), ); - final tokenContract = (transactionCurrency as Erc20Token).contractAddress; + final tokenContract = transactionCurrency.contractAddress; final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction( - amount: BigInt.zero, + amount: Money.zero(currency), receivingAddressHex: tokenContract, priority: priority, contractAddress: tokenContract, @@ -1193,11 +1181,9 @@ abstract class EVMChainWalletBase spender: spender, amount: amount, priority: priority, - gasFee: BigInt.from(gasFeesModel.estimatedGasFee), + gasFee: Money.fromInt(gasFeesModel.estimatedGasFee, currency), maxFeePerGas: gasFeesModel.maxFeePerGas, - feeCurrency: feeCurrency, estimatedGasUnits: safeGasUnits, - exponent: transactionCurrency.decimal, contractAddress: tokenContract, gasPrice: gasFeesModel.gasPrice, useBlinkProtection: useBlinkProtection, @@ -1354,7 +1340,8 @@ abstract class EVMChainWalletBase String toJSON() => json.encode({ 'mnemonic': _mnemonic, 'private_key': privateKey, - 'balance': balance[currency]?.toJSON() ?? EVMChainERC20Balance(BigInt.zero).toJSON(), + 'balance': + balance[currency]?.toJSON() ?? EVMChainERC20Balance(Money.zero(currency)).toJSON(), 'passphrase': passphrase, 'selected_chain_id': selectedChainId, }); @@ -1370,9 +1357,9 @@ abstract class EVMChainWalletBase try { final balance = await _client.getBalance(_evmChainPrivateKey.address); - return EVMChainERC20Balance(balance.getInWei); + return EVMChainERC20Balance(Money(balance.getInWei, currency)); } catch (_) { - return balance[currency] ?? EVMChainERC20Balance(BigInt.zero); + return balance[currency] ?? EVMChainERC20Balance(Money.zero(currency)); } } @@ -1429,10 +1416,7 @@ abstract class EVMChainWalletBase try { if (token.enabled) { - balance[token] = await _client.fetchERC20Balances( - _evmChainPrivateKey.address, - token.contractAddress, - ); + balance[token] = await _client.fetchERC20Balances(_evmChainPrivateKey.address, token); } else { balance.remove(token); } @@ -1536,12 +1520,7 @@ abstract class EVMChainWalletBase if (newToken.enabled) { try { - final erc20Balance = await _client.fetchERC20Balances( - _evmChainPrivateKey.address, - newToken.contractAddress, - ); - - balance[newToken] = erc20Balance; + balance[newToken] = await _client.fetchERC20Balances(_evmChainPrivateKey.address, newToken); await evmChainErc20TokensBox.put(newToken.contractAddress, newToken); } on Exception catch (_) { @@ -1611,9 +1590,6 @@ abstract class EVMChainWalletBase if (!hasKeysFile) rethrow; } - final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String?) ?? - EVMChainERC20Balance(BigInt.zero); - final WalletKeysData keysData; // Migrate wallet from the old scheme to the new .keys file scheme if (!hasKeysFile) { @@ -1658,6 +1634,10 @@ abstract class EVMChainWalletBase // Use saved chainId if available, otherwise use the computed chainId final initialChainIdForWallet = savedChainId ?? chainId; + final balance = + EVMChainERC20Balance.fromJSON(data?['balance'] as String?, chainConfig.nativeCurrency) ?? + EVMChainERC20Balance(Money.zero(chainConfig.nativeCurrency)); + return EVMChainWallet( walletInfo: walletInfo, derivationInfo: await walletInfo.getDerivationInfo(), diff --git a/cw_evm/lib/evm_erc20_balance.dart b/cw_evm/lib/evm_erc20_balance.dart index 13ae310c8..b0a59b15b 100644 --- a/cw_evm/lib/evm_erc20_balance.dart +++ b/cw_evm/lib/evm_erc20_balance.dart @@ -1,33 +1,23 @@ -import 'dart:convert'; +import "dart:convert"; -import 'package:cw_core/balance.dart'; +import "package:cw_core/amount/money.dart"; +import "package:cw_core/balance.dart"; +import "package:cw_core/currency.dart"; class EVMChainERC20Balance extends Balance { - EVMChainERC20Balance(this.balance, {this.exponent = 18}) - : super(balance, balance); + EVMChainERC20Balance(Money balance) : super(balance, Money.zero(balance.currency)); - final BigInt balance; - final int exponent; + String toJSON() => json.encode({ "balanceInWei": available.amount.toString() }); - String toJSON() => json.encode({ - 'balanceInWei': balance.toString(), - 'exponent': exponent, - }); - - static EVMChainERC20Balance? fromJSON(String? jsonSource) { - if (jsonSource == null) { - return null; - } + static EVMChainERC20Balance? fromJSON(String? jsonSource, Currency currency) { + if (jsonSource == null) return null; final decoded = json.decode(jsonSource) as Map; try { - return EVMChainERC20Balance( - BigInt.parse(decoded['balanceInWei']), - exponent: decoded['exponent'], - ); + return EVMChainERC20Balance(Money(BigInt.parse(decoded["balanceInWei"]), currency)); } catch (e) { - return EVMChainERC20Balance(BigInt.zero); + return EVMChainERC20Balance(Money.zero(currency)); } } } diff --git a/cw_evm/lib/pending_evm_chain_transaction.dart b/cw_evm/lib/pending_evm_chain_transaction.dart index 3e9ad39f2..15ad59752 100644 --- a/cw_evm/lib/pending_evm_chain_transaction.dart +++ b/cw_evm/lib/pending_evm_chain_transaction.dart @@ -1,6 +1,6 @@ import 'dart:typed_data'; -import 'package:cw_core/format_fixed.dart'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:web3dart/crypto.dart'; import 'package:hex/hex.dart' as Hex; @@ -8,36 +8,36 @@ import 'package:hex/hex.dart' as Hex; class PendingEVMChainTransaction with PendingTransaction { final Function sendTransaction; final Uint8List signedTransaction; - final BigInt fee; - final String feeCurrency; - final String amount; - final int exponent; final bool isInfiniteApproval; PendingEVMChainTransaction({ required this.sendTransaction, required this.signedTransaction, required this.fee, - required this.feeCurrency, required this.amount, - required this.exponent, this.isInfiniteApproval = false, }); @override String get amountFormatted { if (isInfiniteApproval) return "∞"; - return formatFixed(BigInt.parse(amount), exponent); + return amount.toString(); } + @override + final Money amount; + + @override + final Money fee; + @override Future commit() async => await sendTransaction(); @override - String get feeFormatted => "$feeFormattedValue $feeCurrency"; + String get feeFormatted => fee.toStringWithSymbol(fractionalDigits: 10); @override - String get feeFormattedValue => formatFixed(fee, 18, fractionalDigits: 10); + String get feeFormattedValue => fee.toStringWithPrecision(fractionalDigits: 10); @override String get hex => bytesToHex(signedTransaction, include0x: true); @@ -61,7 +61,5 @@ class PendingEVMChainTransaction with PendingTransaction { } @override - Future> commitUR() { - throw UnimplementedError(); - } + Future> commitUR() => throw UnimplementedError(); } diff --git a/cw_evm/lib/usdt0/usdt0_service.dart b/cw_evm/lib/usdt0/usdt0_service.dart index 963994b0c..56c82d14d 100644 --- a/cw_evm/lib/usdt0/usdt0_service.dart +++ b/cw_evm/lib/usdt0/usdt0_service.dart @@ -1,3 +1,4 @@ +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_evm/contract/erc20.dart'; @@ -80,11 +81,9 @@ class USDT0Service { ); final pendingApproval = await wallet.createApprovalTransaction( - maxUint, + Money(maxUint,token), adapter, - token, priority, - EVMChainUtils.getFeeCurrency(wallet.selectedChainId), useBlinkProtection: useBlinkProtection, ); await pendingApproval.commit(); @@ -116,7 +115,7 @@ class USDT0Service { return wallet.createCallDataTransaction( oftAddress, encoded.dataHex, - encoded.valueWei, + Money(encoded.valueWei, token), priority, token.contractAddress, amount, diff --git a/cw_evm/lib/utils/evm_chain_formatter.dart b/cw_evm/lib/utils/evm_chain_formatter.dart index f9f3a73bd..5a9c321c8 100644 --- a/cw_evm/lib/utils/evm_chain_formatter.dart +++ b/cw_evm/lib/utils/evm_chain_formatter.dart @@ -11,40 +11,6 @@ class EVMChainFormatter { } } - /// Parse EVM chain amount to BigInt to avoid integer overflow for large amounts - /// [decimals] defaults to 18 for native ETH/POL, but should be set to token decimals for ERC20 tokens - static BigInt parseEVMChainAmountToBigInt(String amount, {int decimals = evmDecimals}) { - try { - String cleanAmount = amount.replaceAll(',', '.'); - - bool isNegative = cleanAmount.startsWith('-'); - if (isNegative) { - cleanAmount = cleanAmount.substring(1); - } - - final parts = cleanAmount.split('.'); - String whole = parts[0].isEmpty ? '0' : parts[0]; - String fraction = parts.length > 1 ? parts[1] : ''; - - // 3. Strict Truncation Logic - // If fraction is longer than decimals, strictly cut it off. - // If shorter, pad it with zeros. - if (fraction.length > decimals) { - fraction = fraction.substring(0, decimals); - } else { - fraction = fraction.padRight(decimals, '0'); - } - - final wholeBigInt = BigInt.parse(whole); - final fractionBigInt = BigInt.parse(fraction); - final multiplier = BigInt.from(10).pow(decimals); - - return (wholeBigInt * multiplier) + fractionBigInt; - } catch (_) { - return BigInt.zero; - } - } - static String truncateDecimals(String amount, int decimals) { final parts = amount.split("."); diff --git a/cw_evm/pubspec.yaml b/cw_evm/pubspec.yaml index 4ba23a5ef..0186841c5 100644 --- a/cw_evm/pubspec.yaml +++ b/cw_evm/pubspec.yaml @@ -50,7 +50,7 @@ dev_dependencies: sdk: flutter build_runner: ^2.4.15 mobx_codegen: ^2.0.7 - hive_generator: ^2.0.1 +# hive_generator: ^2.0.1 flutter_lints: ^2.0.0 flutter: diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 9d115ea10..f12f65a3b 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -4,6 +4,8 @@ import 'dart:ffi'; import 'dart:isolate'; import 'dart:math'; +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart'; @@ -118,9 +120,7 @@ String getSeedLegacy(String? language) { return legacy; } -String getPassphrase() { - return currentWallet?.getCacheAttribute(key: "cakewallet.passphrase") ?? ""; -} +String getPassphrase() => currentWallet?.getCacheAttribute(key: "cakewallet.passphrase") ?? ""; Map>> addressCache = {}; @@ -146,12 +146,11 @@ String getAddress({int accountIndex = 0, int addressIndex = 0}) { return addressCache[currentWallet!.ffiAddress()]![accountIndex]![addressIndex]!; } -int getFullBalance({int accountIndex = 0}) { - return currentWallet?.balance(accountIndex: accountIndex) ?? 0; -} +Money getFullBalance({int accountIndex = 0}) => + Money.fromInt(currentWallet?.balance(accountIndex: accountIndex) ?? 0, CryptoCurrency.xmr); -int getUnlockedBalance({int accountIndex = 0}) => - currentWallet?.unlockedBalance(accountIndex: accountIndex) ?? 0; +Money getUnlockedBalance({int accountIndex = 0}) => Money.fromInt( + currentWallet?.unlockedBalance(accountIndex: accountIndex) ?? 0, CryptoCurrency.xmr); int getCurrentHeight() => currentWallet?.blockChainHeight() ?? 0; @@ -417,13 +416,11 @@ Future setTrustedDaemon(bool trusted) async => Future trustedDaemon() async => currentWallet!.trustedDaemon(); -String signMessage(String message, {String address = ""}) { - return currentWallet!.signMessage(message: message, address: address); -} +String signMessage(String message, {String address = ""}) => + currentWallet!.signMessage(message: message, address: address); -bool verifyMessage(String message, String address, String signature) { - return currentWallet!.verifySignedMessage(message: message, address: address, signature: signature); -} +bool verifyMessage(String message, String address, String signature) => + currentWallet!.verifySignedMessage(message: message, address: address, signature: signature); Map> debugCallLength() => monero.debugCallLength; @@ -438,4 +435,4 @@ Map getWalletCacheDebug() { "error": e.toString(), }; } -} \ No newline at end of file +} diff --git a/cw_monero/lib/monero_balance.dart b/cw_monero/lib/monero_balance.dart new file mode 100644 index 000000000..eec2ec769 --- /dev/null +++ b/cw_monero/lib/monero_balance.dart @@ -0,0 +1,17 @@ +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/balance.dart'; +import 'package:cw_core/crypto_currency.dart'; + +class MoneroBalance extends Balance { + MoneroBalance({ + required this.fullBalance, + required Money unlockedBalance, + Money? frozen, + }) : super( + unlockedBalance, + fullBalance - unlockedBalance, + frozen: frozen ?? Money.zero(CryptoCurrency.xmr), + ); + + final Money fullBalance; +} diff --git a/cw_monero/lib/monero_transaction_info.dart b/cw_monero/lib/monero_transaction_info.dart index b7ddd0ca6..3f1567122 100644 --- a/cw_monero/lib/monero_transaction_info.dart +++ b/cw_monero/lib/monero_transaction_info.dart @@ -1,5 +1,4 @@ -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/format_amount.dart'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; @@ -15,23 +14,10 @@ class MoneroTransactionInfo extends TransactionInfo { final DateTime date; final int accountIndex; final bool isPending; - final int amount; - final int fee; + final Money amount; + final Money fee; final int addressIndex; final int confirmations; String? recipientAddress; String? key; - String? _fiatAmount; - - @override - String amountFormatted() => '${CryptoCurrency.xmr.formatAmount(BigInt.from(amount))} XMR'; - - @override - String fiatAmount() => _fiatAmount ?? ''; - - @override - void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); - - @override - String feeFormatted() => '${CryptoCurrency.xmr.formatAmount(BigInt.from(fee))} XMR'; } diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 95e02f9e1..b06cd4625 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -3,12 +3,11 @@ import 'dart:ffi'; import 'dart:io'; import 'dart:isolate'; -import 'package:cw_core/monero_amount_format.dart'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/account.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/monero_balance.dart'; import 'package:cw_core/monero_transaction_priority.dart'; import 'package:cw_core/monero_wallet_keys.dart'; import 'package:cw_core/monero_wallet_utils.dart'; @@ -30,6 +29,7 @@ import 'package:cw_monero/api/wallet.dart' as monero_wallet; import 'package:cw_monero/api/wallet_manager.dart'; import 'package:cw_monero/exceptions/monero_transaction_creation_exception.dart'; import 'package:cw_monero/ledger.dart'; +import 'package:cw_monero/monero_balance.dart'; import 'package:cw_monero/monero_transaction_creation_credentials.dart'; import 'package:cw_monero/monero_transaction_history.dart'; import 'package:cw_monero/monero_transaction_info.dart'; @@ -375,15 +375,15 @@ abstract class MoneroWalletBase extends WalletBase item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) { - throw MoneroTransactionCreationException( - 'You do not have enough XMR to send this amount.'); + if (outputs.any((item) => item.sendAll || item.cryptoAmount.amount <= BigInt.zero)) { + throw MoneroTransactionCreationException('You do not have enough XMR to send this amount.'); } - final int totalAmount = outputs.fold( - 0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)); + final totalAmount = outputs.fold(0, (acc, value) => acc + value.cryptoAmount.amount.toInt()); - if (unlockedBalance < totalAmount) { - throw MoneroTransactionCreationException( - 'You do not have enough XMR to send this amount.'); + if (unlockedBalance < Money.fromInt(totalAmount, CryptoCurrency.xmr)) { + throw MoneroTransactionCreationException('You do not have enough XMR to send this amount.'); } - if (inputs.isEmpty) MoneroTransactionCreationException( - 'No inputs selected'); + if (inputs.isEmpty) MoneroTransactionCreationException('No inputs selected'); final moneroOutputs = outputs.map((output) { - final outputAddress = - output.isParsedAddress ? output.extractedAddress : output.address; + final outputAddress = output.isParsedAddress ? output.extractedAddress : output.address; - return MoneroOutput( - address: outputAddress!, - amount: output.cryptoAmount!.replaceAll(',', '.')); + return MoneroOutput(address: outputAddress!, amount: output.cryptoAmount.toString()); }).toList(); pendingTransactionDescription = @@ -481,8 +473,7 @@ abstract class MoneroWalletBase extends WalletBase{ 'key': row.key, @@ -893,8 +883,7 @@ abstract class MoneroWalletBase extends WalletBase monero_wallet.getUnlockedBalance( - accountIndex: walletAddresses.account!.id); + Money _getUnlockedBalance() => + monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id); - int _getFrozenBalance() { + Money _getFrozenBalance() { var frozenBalance = 0; - for (var coin in unspentCoinsInfo.values.where((element) => + for (final coin in unspentCoinsInfo.values.where((element) => element.walletId == id && element.accountIndex == walletAddresses.account!.id)) { if (coin.isFrozen && !coin.isSending) frozenBalance += coin.value; } - return frozenBalance; + return Money.fromInt(frozenBalance, CryptoCurrency.xmr); } void _onNewBlock(int height, int blocksLeft, double ptc) async { @@ -1013,9 +999,4 @@ abstract class MoneroWalletBase extends WalletBase setLedgerConnection(LedgerConnection connection) async { await enableLedgerExchange(connection); } - - @override - String formatCryptoAmount(String amount) { - return moneroAmountToString(amount: int.parse(amount)); - } } diff --git a/cw_monero/lib/pending_monero_transaction.dart b/cw_monero/lib/pending_monero_transaction.dart index c32a2cc9c..d9d1c434a 100644 --- a/cw_monero/lib/pending_monero_transaction.dart +++ b/cw_monero/lib/pending_monero_transaction.dart @@ -1,16 +1,14 @@ import 'dart:async'; import 'dart:ffi'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/pending_transaction.dart'; import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/structs/pending_transaction.dart'; -import 'package:cw_monero/api/transaction_history.dart' - as monero_transaction_history; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/amount_converter.dart'; - -import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_monero/api/transaction_history.dart' as monero_transaction_history; import 'package:cw_monero/api/wallet.dart'; import 'package:cw_monero/monero_wallet.dart'; import 'package:monero/monero.dart' as monero; @@ -29,6 +27,10 @@ class PendingMoneroTransaction with PendingTransaction { final PendingTransactionDescription pendingTransactionDescription; final MoneroWalletBase wallet; + Money get amount => Money.fromInt(pendingTransactionDescription.amount, CryptoCurrency.xmr); + + Money get fee => Money.fromInt(pendingTransactionDescription.fee, CryptoCurrency.xmr); + @override String get id => pendingTransactionDescription.hash; @@ -36,15 +38,7 @@ class PendingMoneroTransaction with PendingTransaction { String get hex => pendingTransactionDescription.hex; @override - String get amountFormatted => AmountConverter.amountIntToString( - CryptoCurrency.xmr, pendingTransactionDescription.amount); - - @override - String get feeFormatted => "$feeFormattedValue XMR"; - - @override - String get feeFormattedValue => AmountConverter.amountIntToString( - CryptoCurrency.xmr, pendingTransactionDescription.fee); + String get amountFormatted => amount.toString(); @override bool shouldCommitUR() => isViewOnly && wallet.hardwareWalletType != HardwareWalletType.trezor; diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 2fbb5d4e5..79da36944 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -5,23 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: "5b7468c326d2f8a4f630056404ca0d291ade42918f4a3c6233618e724f39da8e" url: "https://pub.dev" source: hosted - version: "76.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.3" + version: "92.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: "70e4b1ef8003c64793a9e268a551a82869a8a96f39deb73dea28084b0e8bf75e" url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "9.0.0" args: dependency: transitive description: @@ -108,18 +103,18 @@ packages: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "4.0.6" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.3.0" build_daemon: dependency: transitive description: @@ -128,30 +123,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" - build_resolvers: - dependency: "direct dev" - description: - name: build_resolvers - sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 - url: "https://pub.dev" - source: hosted - version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" url: "https://pub.dev" source: hosted - version: "2.4.15" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" - url: "https://pub.dev" - source: hosted - version: "8.0.0" + version: "2.15.0" built_collection: dependency: transitive description: @@ -164,10 +143,10 @@ packages: dependency: transitive description: name: built_value - sha256: "8b158ab94ec6913e480dc3f752418348b5ae099eb75868b5f4775f0572999c61" + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" url: "https://pub.dev" source: hosted - version: "8.9.4" + version: "8.12.6" cake_backup: dependency: transitive description: @@ -181,10 +160,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: @@ -260,10 +239,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" + sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b url: "https://pub.dev" source: hosted - version: "2.3.8" + version: "3.1.3" dbus: dependency: transitive description: @@ -354,14 +333,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" glob: dependency: transitive description: @@ -403,18 +374,10 @@ packages: source: hosted version: "0.2.0" hive: - dependency: transitive - description: - name: hive - sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" - url: "https://pub.dev" - source: hosted - version: "2.2.3" - hive_generator: dependency: "direct dev" description: - name: hive_generator - sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" + name: hive + sha256: "5184b6ea455689754578267c31c3b0b329cccf20a5f2bfdab6941c29c3d3c09a" url: "https://pub.dev" source: hosted version: "2.0.1" @@ -478,26 +441,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" ledger_flutter_plus: dependency: "direct main" description: @@ -522,38 +485,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.dev" - source: hosted - version: "0.1.3-main.0" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -566,26 +521,26 @@ packages: dependency: "direct main" description: name: mobx - sha256: bf1a90e5bcfd2851fc6984e20eef69557c65d9e4d0a88f5be4cf72c9819ce6b0 + sha256: "25ea9a3ee69243d578fb6288721d8eeb18550d0391c32f896d377d994154bff2" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" mobx_codegen: dependency: "direct dev" description: name: mobx_codegen - sha256: "990da80722f7d7c0017dec92040b31545d625b15d40204c36a1e63d167c73cdc" + sha256: "6529bcae074838004b58ad01fbe4966be182a176aacfb723686985986ebac19f" url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "2.7.7" mockito: dependency: "direct dev" description: name: mockito - sha256: f99d8d072e249f719a5531735d146d8cf04c580d93920b04de75bef6dfb2daf6 + sha256: eff30d002f0c8bf073b6f929df4483b543133fcafce056870163587b03f1d422 url: "https://pub.dev" source: hosted - version: "5.4.5" + version: "5.6.4" monero: dependency: "direct main" description: @@ -823,18 +778,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 url: "https://pub.dev" source: hosted - version: "1.5.0" - source_helper: - dependency: transitive - description: - name: source_helper - sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" - url: "https://pub.dev" - source: hosted - version: "1.3.5" + version: "4.2.3" source_span: dependency: transitive description: @@ -959,18 +906,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.4" - timing: - dependency: transitive - description: - name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" - url: "https://pub.dev" - source: hosted - version: "1.0.1" + version: "0.7.10" torch_dart: dependency: transitive description: @@ -1040,10 +979,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -1109,5 +1048,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.0-0 <4.0.0" + dart: ">=3.9.0 <4.0.0" flutter: ">=3.24.0" diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 516464d36..c84f08b04 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -16,8 +16,8 @@ dependencies: ffi: ^2.0.1 http: ^1.1.0 path_provider: ^2.0.11 - mobx: ^2.0.7+4 - flutter_mobx: ^2.0.6+1 + mobx: ^2.6.0 + flutter_mobx: ^2.3.0 intl: any encrypt: ^5.0.1 polyseed: ^0.0.7 @@ -39,11 +39,11 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - build_runner: ^2.4.15 - build_resolvers: ^2.4.4 - mobx_codegen: ^2.0.7 + build_runner: ^2.15.0 + mobx_codegen: ^2.7.7 mockito: ^5.4.5 - hive_generator: ^2.0.1 +# hive_generator: ^2.0.1 + hive: 2.0.1 sqflite_common_ffi: ^2.3.4+4 dependency_overrides: diff --git a/cw_nano/lib/banano_balance.dart b/cw_nano/lib/banano_balance.dart deleted file mode 100644 index 408b9dddb..000000000 --- a/cw_nano/lib/banano_balance.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:cw_core/balance.dart'; -import 'package:nanoutil/nanoutil.dart'; - -BigInt stringAmountToBigIntBanano(String amount) { - return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerBanano)); -} - -class BananoBalance extends Balance { - final BigInt currentBalance; - final BigInt receivableBalance; - - BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(BigInt.zero, BigInt.zero); - - BananoBalance.fromFormattedString( - {required String formattedCurrentBalance, required String formattedReceivableBalance}) - : currentBalance = stringAmountToBigIntBanano(formattedCurrentBalance), - receivableBalance = stringAmountToBigIntBanano(formattedReceivableBalance), - super(BigInt.zero, BigInt.zero); - - BananoBalance.fromRawString( - {required String currentBalance, required String receivableBalance}) - : currentBalance = BigInt.parse(currentBalance), - receivableBalance = BigInt.parse(receivableBalance), - super(BigInt.zero, BigInt.zero); - - @override - String get formattedAvailableBalance { - return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerBanano); - } - - @override - String get formattedAdditionalBalance { - return NanoAmounts.getRawAsUsableString(receivableBalance.toString(), NanoAmounts.rawPerBanano); - } -} diff --git a/cw_nano/lib/nano_balance.dart b/cw_nano/lib/nano_balance.dart index ce9464696..c5559e6d3 100644 --- a/cw_nano/lib/nano_balance.dart +++ b/cw_nano/lib/nano_balance.dart @@ -1,36 +1,22 @@ +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/balance.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:nanoutil/nanoutil.dart'; - -BigInt stringAmountToBigIntNano(String amount) { - return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerNano)); -} +import 'package:cw_core/crypto_currency.dart'; class NanoBalance extends Balance { - final BigInt currentBalance; - final BigInt receivableBalance; + final Money currentBalance; + final Money receivableBalance; - NanoBalance({required this.currentBalance, required this.receivableBalance}) : super(currentBalance, receivableBalance); + NanoBalance({required this.currentBalance, required this.receivableBalance}) + : super(currentBalance, receivableBalance); - NanoBalance.fromFormattedString( - {required String formattedCurrentBalance, required String formattedReceivableBalance}) - : currentBalance = stringAmountToBigIntNano(formattedCurrentBalance), - receivableBalance = stringAmountToBigIntNano(formattedReceivableBalance), - super(stringAmountToBigIntNano(formattedCurrentBalance), stringAmountToBigIntNano(formattedReceivableBalance)); - - NanoBalance.fromRawString( - {required String currentBalance, required String receivableBalance}) - : currentBalance = BigInt.parse(currentBalance), - receivableBalance = BigInt.parse(receivableBalance), - super(BigInt.parse(currentBalance), BigInt.parse(receivableBalance)); - - @override - String get formattedAvailableBalance { - return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerNano); - } - - @override - String get formattedAdditionalBalance { - return NanoAmounts.getRawAsUsableString(receivableBalance.toString(), NanoAmounts.rawPerNano); - } + factory NanoBalance.fromRawString({ + required String currentBalance, + required String receivableBalance, + }) => + NanoBalance( + currentBalance: + Money.tryParse(currentBalance, CryptoCurrency.nano) ?? Money.zero(CryptoCurrency.nano), + receivableBalance: Money.tryParse(receivableBalance, CryptoCurrency.nano) ?? + Money.zero(CryptoCurrency.nano), + ); } diff --git a/cw_nano/lib/nano_client.dart b/cw_nano/lib/nano_client.dart index 285469568..8cf1a66be 100644 --- a/cw_nano/lib/nano_client.dart +++ b/cw_nano/lib/nano_client.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'dart:convert'; +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/nano_account_info_response.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; @@ -85,10 +87,10 @@ class NanoClient { throw Exception( "Error while trying to get balance! ${data["error"] != null ? data["error"] : ""}"); } - final String currentBalance = data["balance"] as String; - final String receivableBalance = data["receivable"] as String; - final BigInt cur = BigInt.parse(currentBalance); - final BigInt rec = BigInt.parse(receivableBalance); + final currentBalance = data["balance"] as String; + final receivableBalance = data["receivable"] as String; + final cur = Money(BigInt.parse(currentBalance), CryptoCurrency.nano); + final rec = Money(BigInt.parse(receivableBalance), CryptoCurrency.nano); return NanoBalance(currentBalance: cur, receivableBalance: rec); } @@ -258,7 +260,7 @@ class NanoClient { // first get the current account balance: if (balanceAfterTx == null) { - final BigInt currentBalance = (await getBalance(publicAddress)).currentBalance; + final BigInt currentBalance = (await getBalance(publicAddress)).currentBalance.amount; final BigInt txAmount = BigInt.parse(amountRaw); balanceAfterTx = currentBalance - txAmount; } diff --git a/cw_nano/lib/nano_transaction_info.dart b/cw_nano/lib/nano_transaction_info.dart index 9195a06ef..66dd78344 100644 --- a/cw_nano/lib/nano_transaction_info.dart +++ b/cw_nano/lib/nano_transaction_info.dart @@ -1,13 +1,14 @@ +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/format_amount.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; -import 'package:nanoutil/nanoutil.dart'; class NanoTransactionInfo extends TransactionInfo { NanoTransactionInfo({ required this.id, required this.height, - required this.amountRaw, + required Money amountRaw, this.tokenSymbol = "XNO", required this.direction, required this.confirmed, @@ -15,12 +16,11 @@ class NanoTransactionInfo extends TransactionInfo { required this.confirmations, required this.to, required this.from, - }) : this.amount = amountRaw.toInt(); + }) : this.amount = amountRaw; final String id; final int height; - final int amount; - final BigInt amountRaw; + final Money amount; final TransactionDirection direction; final DateTime date; final bool confirmed; @@ -32,28 +32,17 @@ class NanoTransactionInfo extends TransactionInfo { bool get isPending => !this.confirmed; - @override - String amountFormatted() { - final String amt = - NanoAmounts.getRawAsUsableString(amountRaw.toString(), NanoAmounts.rawPerNano); - final String acc = NanoAmounts.getRawAccuracy(amountRaw.toString(), NanoAmounts.rawPerNano); - return "$acc$amt $tokenSymbol"; - } - @override String fiatAmount() => _fiatAmount ?? ''; @override void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); - @override - String feeFormatted() => "0 XNO"; - factory NanoTransactionInfo.fromJson(Map data) { return NanoTransactionInfo( id: data['id'] as String, height: data['height'] as int, - amountRaw: BigInt.parse(data['amountRaw'] as String), + amountRaw: Money(BigInt.parse(data['amountRaw'] as String), CryptoCurrency.nano), direction: parseTransactionDirectionFromInt(data['direction'] as int), date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), confirmed: data['confirmed'] as bool, @@ -67,7 +56,7 @@ class NanoTransactionInfo extends TransactionInfo { Map toJson() => { 'id': id, 'height': height, - 'amountRaw': amountRaw.toString(), + 'amountRaw': amount.amount.toString(), 'direction': direction.index, 'date': date.millisecondsSinceEpoch, 'confirmed': confirmed, diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index 44b4a3c8a..86b197818 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:bip39/bip39.dart' as bip39; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/encryption_file_utils.dart'; @@ -55,7 +56,10 @@ abstract class NanoWalletBase walletAddresses = NanoWalletAddresses(walletInfo), balance = ObservableMap.of({ CryptoCurrency.nano: initialBalance ?? - NanoBalance(currentBalance: BigInt.zero, receivableBalance: BigInt.zero) + NanoBalance( + currentBalance: Money.zero(CryptoCurrency.nano), + receivableBalance: Money.zero(CryptoCurrency.nano), + ) }), super(walletInfo, derivationInfo) { this.walletInfo = walletInfo; @@ -191,7 +195,7 @@ abstract class NanoWalletBase BigInt runningAmount = BigInt.zero; await _updateBalance(); - BigInt runningBalance = balance[currency]?.currentBalance ?? BigInt.zero; + BigInt runningBalance = balance[currency]?.currentBalance.amount ?? BigInt.zero; final List> blocks = []; String? previousHash; @@ -199,14 +203,12 @@ abstract class NanoWalletBase for (var txOut in credentials.outputs) { late BigInt amt; if (txOut.sendAll) { - amt = balance[currency]?.currentBalance ?? BigInt.zero; + amt = balance[currency]?.currentBalance.amount ?? BigInt.zero; } else { - amt = BigInt.tryParse(NanoAmounts.getAmountAsRaw( - txOut.cryptoAmount?.replaceAll(',', '.') ?? "0", NanoAmounts.rawPerNano)) ?? - BigInt.zero; + amt = txOut.cryptoAmount.amount; } - if (balance[currency]?.currentBalance != null && amt > balance[currency]!.currentBalance) { + if (balance[currency]?.currentBalance != null && amt > balance[currency]!.currentBalance.amount) { throw Exception("Trying to send more than entire balance!"); } @@ -233,7 +235,7 @@ abstract class NanoWalletBase } try { - if (runningAmount > balance[currency]!.currentBalance || runningBalance < BigInt.zero) { + if (runningAmount > balance[currency]!.currentBalance.amount || runningBalance < BigInt.zero) { throw Exception(("Trying to send more than entire balance!")); } } catch (e) { @@ -241,7 +243,7 @@ abstract class NanoWalletBase } return PendingNanoTransaction( - amount: runningAmount, + amount: Money(runningAmount, currency), id: "", nanoClient: _client, blocks: blocks, @@ -294,7 +296,7 @@ abstract class NanoWalletBase final bool isSend = transactionModel.type == "send"; result[transactionModel.hash] = NanoTransactionInfo( id: transactionModel.hash, - amountRaw: transactionModel.amount, + amountRaw: Money(transactionModel.amount, currency), height: transactionModel.height, direction: isSend ? TransactionDirection.outgoing : TransactionDirection.incoming, confirmed: transactionModel.confirmed, @@ -360,7 +362,7 @@ abstract class NanoWalletBase // get our balance: await _updateBalance(); // if we have anything to receive, process it: - if (balance[currency]!.receivableBalance > BigInt.zero) { + if (balance[currency]!.receivableBalance.amount > BigInt.zero) { await _receiveAll(); } }); @@ -465,8 +467,8 @@ abstract class NanoWalletBase // if we don't have a balance, we should at least create one, since it's a late binding // otherwise, it's better to just leave it as whatever it was before: if (balance[currency] == null) { - balance[currency] = - NanoBalance(currentBalance: BigInt.zero, receivableBalance: BigInt.zero); + balance[currency] = NanoBalance( + currentBalance: Money.zero(currency), receivableBalance: Money.zero(currency)); } } // don't save unnecessarily: diff --git a/cw_nano/lib/pending_nano_transaction.dart b/cw_nano/lib/pending_nano_transaction.dart index 8a759f55b..5922d2f93 100644 --- a/cw_nano/lib/pending_nano_transaction.dart +++ b/cw_nano/lib/pending_nano_transaction.dart @@ -1,6 +1,7 @@ +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_nano/nano_client.dart'; -import 'package:nanoutil/nanoutil.dart'; class PendingNanoTransaction with PendingTransaction { PendingNanoTransaction({ @@ -11,38 +12,26 @@ class PendingNanoTransaction with PendingTransaction { }); final NanoClient nanoClient; - final BigInt amount; final String id; final List> blocks; String hex = "unused"; @override - String get amountFormatted { - final String amt = NanoAmounts.getRawAsUsableString(amount.toString(), NanoAmounts.rawPerNano); - return amt; - } - - String get accurateAmountFormatted { - final String amt = NanoAmounts.getRawAsUsableString(amount.toString(), NanoAmounts.rawPerNano); - final String acc = NanoAmounts.getRawAccuracy(amount.toString(), NanoAmounts.rawPerNano); - return "$acc$amt"; - } + final Money amount; @override - String get feeFormatted => "$feeFormattedValue XNO"; + String get amountFormatted => amount.toString(); @override - String get feeFormattedValue => "0"; + Money get fee => Money.zero(CryptoCurrency.nano); @override Future commit() async { - for (var block in blocks) { + for (final block in blocks) { await nanoClient.processBlock(block, "send"); } } @override - Future> commitUR() { - throw UnimplementedError(); - } + Future> commitUR() => throw UnimplementedError(); } diff --git a/cw_nano/pubspec.lock b/cw_nano/pubspec.lock index 72684a8fc..509d74498 100644 --- a/cw_nano/pubspec.lock +++ b/cw_nano/pubspec.lock @@ -5,23 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" url: "https://pub.dev" source: hosted - version: "76.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.3" + version: "93.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "10.0.1" args: dependency: transitive description: @@ -100,18 +95,18 @@ packages: dependency: transitive description: name: build - sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 + sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "4.0.6" build_config: dependency: transitive description: name: build_config - sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.3.0" build_daemon: dependency: transitive description: @@ -120,30 +115,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.4" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 - url: "https://pub.dev" - source: hosted - version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" url: "https://pub.dev" source: hosted - version: "2.4.15" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" - url: "https://pub.dev" - source: hosted - version: "8.0.0" + version: "2.15.0" built_collection: dependency: transitive description: @@ -173,10 +152,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: @@ -193,14 +172,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" - code_builder: - dependency: transitive - description: - name: code_builder - sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" - url: "https://pub.dev" - source: hosted - version: "4.10.1" collection: dependency: transitive description: @@ -252,10 +223,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" + sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2" url: "https://pub.dev" source: hosted - version: "2.3.8" + version: "3.1.7" decimal: dependency: "direct main" description: @@ -343,14 +314,6 @@ packages: description: flutter source: sdk version: "0.0.0" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" glob: dependency: transitive description: @@ -376,18 +339,10 @@ packages: source: hosted version: "0.2.0" hive: - dependency: transitive - description: - name: hive - sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" - url: "https://pub.dev" - source: hosted - version: "2.2.3" - hive_generator: dependency: "direct dev" description: - name: hive_generator - sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" + name: hive + sha256: "5184b6ea455689754578267c31c3b0b329cccf20a5f2bfdab6941c29c3d3c09a" url: "https://pub.dev" source: hosted version: "2.0.1" @@ -451,26 +406,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" libcrypto: dependency: "direct main" description: @@ -487,38 +442,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.dev" - source: hosted - version: "0.1.3-main.0" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -531,18 +478,18 @@ packages: dependency: "direct main" description: name: mobx - sha256: bf1a90e5bcfd2851fc6984e20eef69557c65d9e4d0a88f5be4cf72c9819ce6b0 + sha256: "25ea9a3ee69243d578fb6288721d8eeb18550d0391c32f896d377d994154bff2" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" mobx_codegen: dependency: "direct dev" description: name: mobx_codegen - sha256: e0abbbc651a69550440f6b65c99ec222a1e2a4afd7baec8ba0f3088c7ca582a8 + sha256: "6529bcae074838004b58ad01fbe4966be182a176aacfb723686985986ebac19f" url: "https://pub.dev" source: hosted - version: "2.7.1" + version: "2.7.7" nanodart: dependency: transitive description: @@ -812,18 +759,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 url: "https://pub.dev" source: hosted - version: "1.5.0" - source_helper: - dependency: transitive - description: - name: source_helper - sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" - url: "https://pub.dev" - source: hosted - version: "1.3.5" + version: "4.2.3" source_span: dependency: transitive description: @@ -948,18 +887,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.4" - timing: - dependency: transitive - description: - name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" - url: "https://pub.dev" - source: hosted - version: "1.0.1" + version: "0.7.10" torch_dart: dependency: transitive description: @@ -995,10 +926,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -1056,5 +987,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.0-0 <4.0.0" + dart: ">=3.10.0 <4.0.0" flutter: ">=3.27.0" diff --git a/cw_nano/pubspec.yaml b/cw_nano/pubspec.yaml index a85a4aee5..a3ef2ea67 100644 --- a/cw_nano/pubspec.yaml +++ b/cw_nano/pubspec.yaml @@ -12,7 +12,7 @@ environment: dependencies: flutter: sdk: flutter - mobx: ^2.0.7+4 + mobx: ^2.6.0 bip39: ^1.0.6 bip32: ^2.0.0 decimal: ^2.3.3 @@ -31,9 +31,10 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - build_runner: ^2.4.15 - mobx_codegen: ^2.0.7 - hive_generator: ^2.0.1 + build_runner: ^2.15.0 + mobx_codegen: ^2.7.7 +# hive_generator: ^2.0.1 + hive: 2.0.1 dependency_overrides: watcher: ^1.1.0 diff --git a/cw_solana/lib/default_spl_tokens.dart b/cw_solana/lib/default_spl_tokens.dart index 72c3ca4ff..324502fd9 100644 --- a/cw_solana/lib/default_spl_tokens.dart +++ b/cw_solana/lib/default_spl_tokens.dart @@ -765,4 +765,6 @@ class DefaultSPLTokens { return SPLToken.copyWith(token, icon: iconPath, tag: 'SOL'); }).toList(); + + SPLToken get usdc => _defaultTokens.where((e) => e.symbol == "USDC").first; } diff --git a/cw_solana/lib/pending_solana_transaction.dart b/cw_solana/lib/pending_solana_transaction.dart index ddfcf6540..7afeb5ffb 100644 --- a/cw_solana/lib/pending_solana_transaction.dart +++ b/cw_solana/lib/pending_solana_transaction.dart @@ -1,11 +1,10 @@ +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/pending_transaction.dart'; class PendingSolanaTransaction with PendingTransaction { - final double amount; final String serializedTransaction; final String destinationAddress; final Function sendTransaction; - final double fee; String? _sig; PendingSolanaTransaction({ @@ -16,6 +15,12 @@ class PendingSolanaTransaction with PendingTransaction { required this.sendTransaction, }); + @override + final Money amount; + + @override + final Money fee; + @override String get amountFormatted { String stringifiedAmount = amount.toString(); @@ -32,12 +37,6 @@ class PendingSolanaTransaction with PendingTransaction { _sig = await sendTransaction(); } - @override - String get feeFormatted => "$feeFormattedValue SOL"; - - @override - String get feeFormattedValue => fee.toString(); - @override String get hex => serializedTransaction; diff --git a/cw_solana/lib/solana_balance.dart b/cw_solana/lib/solana_balance.dart index 7be967fbe..bc796ef3b 100644 --- a/cw_solana/lib/solana_balance.dart +++ b/cw_solana/lib/solana_balance.dart @@ -1,44 +1,25 @@ -import 'dart:convert'; +import "dart:convert"; -import 'package:cw_core/balance.dart'; +import "package:cw_core/amount/money.dart"; +import "package:cw_core/balance.dart"; +import "package:cw_core/currency.dart"; class SolanaBalance extends Balance { - SolanaBalance(this.balance, bool isToken) : super( - BigInt.from(int.tryParse(balance.toStringAsFixed(isToken ? 6 : 9).replaceFirst(".", "")) ?? 0), - BigInt.from(int.tryParse(balance.toStringAsFixed(isToken ? 6 : 9).replaceFirst(".", "")) ?? 0)); + SolanaBalance(Money balance) : super(balance, balance.copyWith(amount: BigInt.zero)); - // Using raw amount from RPC to avoid decimals mismatch for SPL tokens. - SolanaBalance.forToken(BigInt rawAmount, double uiAmount) - : balance = uiAmount, - super(rawAmount, rawAmount); + factory SolanaBalance.zero(Currency currency) => SolanaBalance(Money.zero(currency)); - final double balance; - - String get formattedAdditionalBalance => _balanceFormatted(); - - String get formattedAvailableBalance => _balanceFormatted(); - - String _balanceFormatted() { - String stringBalance = balance.toString(); - if (stringBalance.toString().length >= 12) { - stringBalance = stringBalance.substring(0, 12); - } - return stringBalance; - } - - static SolanaBalance? fromJSON(String? jsonSource, bool isToken) { - if (jsonSource == null) { - return null; - } + static SolanaBalance? fromJSON(String? jsonSource, Currency currency) { + if (jsonSource == null) return null; final decoded = json.decode(jsonSource) as Map; try { - return SolanaBalance(decoded['balance'], isToken); + return SolanaBalance(currency.parseAmount(decoded["balance"])); } catch (e) { - return SolanaBalance(0.0, isToken); + return SolanaBalance(Money.zero(currency)); } } - String toJSON() => json.encode({'balance': balance.toString()}); + String toJSON() => json.encode({"balance": available.toString()}); } diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart index 543e28026..ad72c74a9 100644 --- a/cw_solana/lib/solana_client.dart +++ b/cw_solana/lib/solana_client.dart @@ -1,9 +1,10 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:math' as math; import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/currency.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/solana_rpc_http_service.dart'; @@ -32,7 +33,7 @@ class TransactionFetchResult { class SolanaWalletClient { // Minimum amount in SOL to consider a transaction valid (to filter spam) - static const double minValidAmount = 0.00000003; + static Money minValidAmount = Money.parse("0.00000003", CryptoCurrency.sol); late final client = ProxyWrapper().getHttpIOClient(); SolanaRPC? _provider; @@ -61,24 +62,17 @@ class SolanaWalletClient { } } - Future getBalance(String walletAddress, {bool throwOnError = false}) async { + Future getBalance(String walletAddress, {bool throwOnError = false}) async { try { final balance = await _provider!.requestWithContext( - SolanaRPCGetBalance( - account: SolAddress(walletAddress), - ), + SolanaRPCGetBalance(account: SolAddress(walletAddress)), ); - - final balInLamp = balance.result.toDouble(); - - final solBalance = balInLamp / SolanaUtils.lamportsPerSol; - - return solBalance; + return Money(balance.result, CryptoCurrency.sol); } catch (_) { if (throwOnError) { rethrow; } - return 0.0; + return Money.zero(CryptoCurrency.sol); } } @@ -100,11 +94,11 @@ class SolanaWalletClient { } } - Future getSplTokenBalance(String mintAddress, String walletAddress, + Future getSplTokenBalance(SPLToken token, String walletAddress, {bool throwOnError = false}) async { try { // Fetch the token accounts (a token can have multiple accounts for various uses) - final tokenAccounts = await getSPLTokenAccounts(mintAddress, walletAddress); + final tokenAccounts = await getSPLTokenAccounts(token.mintAddress, walletAddress); // Handle scenario where there is no token account if (tokenAccounts == null || tokenAccounts.isEmpty) { @@ -112,8 +106,7 @@ class SolanaWalletClient { } // Sum raw amounts and ui amounts across all token accounts - BigInt totalRaw = BigInt.zero; - double totalUi = 0.0; + var totalRaw = BigInt.zero; for (var tokenAccount in tokenAccounts) { final tokenAmountResult = await _provider!.request( @@ -122,56 +115,43 @@ class SolanaWalletClient { final raw = BigInt.tryParse(tokenAmountResult.amount) ?? BigInt.zero; totalRaw += raw; - - final ui = tokenAmountResult.uiAmount ?? - (double.tryParse(tokenAmountResult.uiAmountString ?? '0') ?? 0.0); - totalUi += ui; } - return SolanaBalance.forToken(totalRaw, totalUi); + return SolanaBalance(Money(totalRaw, token)); } catch (_) { - if (throwOnError) { - rethrow; - } + if (throwOnError) rethrow; + return null; } } - Future getFeeForMessage(String message, Commitment commitment) async { + Future getFeeForMessage(String message, Commitment commitment) async { try { final feeForMessage = await _provider!.request( - SolanaRPCGetFeeForMessage( - encodedMessage: message, - commitment: commitment, - ), + SolanaRPCGetFeeForMessage(encodedMessage: message, commitment: commitment), ); - final fee = (feeForMessage?.toDouble() ?? 0.0) / SolanaUtils.lamportsPerSol; - return fee; + return Money(feeForMessage ?? BigInt.zero, CryptoCurrency.sol); } catch (_) { - return 0.0; + return Money.zero(CryptoCurrency.sol); } } - Future getEstimatedFee(SolanaPublicKey publicKey, Commitment commitment) async { + Future getEstimatedFee(SolanaPublicKey publicKey, Commitment commitment) async { final message = await _getMessageForNativeTransaction( publicKey: publicKey, destinationAddress: publicKey.toAddress().address, - lamports: SolanaUtils.lamportsPerSol, + lamports: Money(BigInt.from(1000000000), CryptoCurrency.sol), commitment: commitment, ); - final estimatedFee = await _getFeeFromCompiledMessage( - message, - commitment, - ); - return estimatedFee; + return _getFeeFromCompiledMessage(message, commitment); } Future?> parseTransaction({ VersionedTransactionResponse? txResponse, required String walletAddress, - String? splTokenSymbol, + SPLToken? splToken, }) async { if (txResponse == null) return null; @@ -182,8 +162,7 @@ class SolanaWalletClient { if (meta == null || transaction == null) return null; - final int fee = meta.fee; - final feeInSol = fee / SolanaUtils.lamportsPerSol; + final fee = meta.fee; final message = transaction.message; final instructions = message.compiledInstructions; @@ -201,12 +180,10 @@ class SolanaWalletClient { message: message, meta: meta, fee: fee, - feeInSol: feeInSol, walletAddress: walletAddress, signature: signature, blockTime: blockTime, instructions: instructions, - splTokenSymbol: splTokenSymbol, ); if (swapTransactions.isNotEmpty) return swapTransactions; @@ -230,7 +207,6 @@ class SolanaWalletClient { message: message, meta: meta, fee: fee, - feeInSol: feeInSol, feePayerIndex: feePayerIndex, walletAddress: walletAddress, signature: signature, @@ -248,12 +224,11 @@ class SolanaWalletClient { message: message, meta: meta, fee: fee, - feeInSol: feeInSol, instruction: instruction, walletAddress: walletAddress, signature: signature, blockTime: blockTime, - splTokenSymbol: splTokenSymbol, + splToken: splToken, ); if (transactionModel != null) { @@ -401,12 +376,10 @@ class SolanaWalletClient { required VersionedMessage message, required ConfirmedTransactionMeta meta, required int fee, - required double feeInSol, required String walletAddress, required String signature, required BigInt? blockTime, required List instructions, - String? splTokenSymbol, }) async { final List swapTransactions = []; @@ -452,7 +425,7 @@ class SolanaWalletClient { // Parse outgoing side (what was sent) double outgoingAmount = 0.0; - String outgoingTokenSymbol = ''; + Currency outgoingToken = CryptoCurrency.sol; String? outgoingMintAddress; String? outgoingFrom; String? outgoingTo; @@ -475,7 +448,7 @@ class SolanaWalletClient { if (balanceChange > BigInt.zero) { // The wallet sent SOL outgoingAmount = balanceChange.toDouble() / SolanaUtils.lamportsPerSol; - outgoingTokenSymbol = 'SOL'; + outgoingToken = CryptoCurrency.sol; outgoingMintAddress = null; outgoingFrom = walletAddress; // We find the intermediate account or swap program account @@ -519,7 +492,7 @@ class SolanaWalletClient { outgoingAmount = diff.toDouble(); outgoingMintAddress = mint; final token = await getTokenInfo(mint); - outgoingTokenSymbol = token?.symbol ?? 'TOKEN'; + outgoingToken = token ?? const CryptoCurrency(name: "TOKEN", title: "TOKEN", decimals: 6); outgoingFrom = walletAddress; // We find the intermediate account if (instructions.isNotEmpty && instructions[0].accounts.isNotEmpty) { @@ -541,7 +514,7 @@ class SolanaWalletClient { // Parse incoming side (what was received) double incomingAmount = 0.0; - String incomingTokenSymbol = ''; + Currency incomingToken = CryptoCurrency.sol; String? incomingMintAddress; String? incomingFrom; String? incomingTo; @@ -563,7 +536,7 @@ class SolanaWalletClient { if (balanceChange > BigInt.zero) { // The wallet received SOL incomingAmount = balanceChange.toDouble() / SolanaUtils.lamportsPerSol; - incomingTokenSymbol = 'SOL'; + incomingToken = CryptoCurrency.sol; incomingMintAddress = null; incomingTo = walletAddress; // We find the intermediate account @@ -650,7 +623,9 @@ class SolanaWalletClient { incomingAmount = diff.toDouble(); incomingMintAddress = mint; final token = await getTokenInfo(mint); - incomingTokenSymbol = token?.symbol ?? 'TOKEN'; + printV(token?.symbol); + printV(token?.decimals); + incomingToken = token ?? const CryptoCurrency(name: "TOKEN", title: "TOKEN", decimals: 6); incomingTo = walletAddress; // We find the intermediate account if (instructions.isNotEmpty && instructions[0].accounts.isNotEmpty) { @@ -679,13 +654,12 @@ class SolanaWalletClient { from: outgoingFrom, to: outgoingTo, id: outgoingId, - amount: outgoingAmount, + amount: Money.parse(outgoingAmount.toStringAsFixed(outgoingToken.decimals), outgoingToken), programId: outgoingMintAddress == null ? SystemProgramConst.programId.address : SPLTokenProgramConst.tokenProgramId.address, blockTimeInInt: blockTime?.toInt() ?? 0, - tokenSymbol: outgoingTokenSymbol, - fee: feeInSol, + fee: Money.fromInt(fee, CryptoCurrency.sol), )); } @@ -698,13 +672,12 @@ class SolanaWalletClient { from: incomingFrom, to: incomingTo, id: incomingId, - amount: incomingAmount, + amount: Money.parse(incomingAmount.toStringAsFixed(incomingToken.decimals), incomingToken), programId: incomingMintAddress == null ? SystemProgramConst.programId.address : SPLTokenProgramConst.tokenProgramId.address, blockTimeInInt: blockTime?.toInt() ?? 0, - tokenSymbol: incomingTokenSymbol, - fee: 0.0, // Fee only charged on outgoing side + fee: Money.zero(CryptoCurrency.sol), // Fee only charged on outgoing side )); } @@ -715,7 +688,6 @@ class SolanaWalletClient { required VersionedMessage message, required ConfirmedTransactionMeta meta, required int fee, - required double feeInSol, required int feePayerIndex, required String walletAddress, required String signature, @@ -754,10 +726,9 @@ class SolanaWalletClient { final netChange = walletPaidFee ? walletChange - BigInt.from(fee) : walletChange; final isOutgoing = netChange > BigInt.zero; - final amountLamports = netChange.abs(); - final amountInSol = amountLamports.toDouble() / SolanaUtils.lamportsPerSol; + final amountLamports = Money(netChange.abs(), CryptoCurrency.sol); - if (amountInSol < minValidAmount) return null; + if (amountLamports < minValidAmount) return null; // Find the most likely receiver, the account that has the largest opposite balance change. String? receiver; @@ -783,11 +754,10 @@ class SolanaWalletClient { from: isOutgoing ? walletAddress : receiver, to: isOutgoing ? receiver : walletAddress, id: signature, - amount: amountInSol, + amount: amountLamports, programId: SystemProgramConst.programId.address, - tokenSymbol: 'SOL', blockTimeInInt: blockTime?.toInt() ?? 0, - fee: feeInSol, + fee: Money.fromInt(fee, CryptoCurrency.sol), ); } @@ -795,12 +765,11 @@ class SolanaWalletClient { required VersionedMessage message, required ConfirmedTransactionMeta meta, required int fee, - required double feeInSol, required CompiledInstruction instruction, required String walletAddress, required String signature, required BigInt? blockTime, - String? splTokenSymbol, + SPLToken? splToken, }) async { final preTokenBalances = meta.preTokenBalances; final postTokenBalances = meta.postTokenBalances; @@ -891,11 +860,8 @@ class SolanaWalletClient { final sender = senderOwner ?? accountKeys[sourceAccountIndex].address; final receiver = receiverOwner ?? accountKeys[destinationAccountIndex].address; - String? tokenSymbol = splTokenSymbol; - - if (tokenSymbol == null && mintAddress != null) { - final token = await getTokenInfo(mintAddress); - tokenSymbol = token?.symbol; + if (splToken == null && mintAddress != null) { + splToken = await getTokenInfo(mintAddress); } return SolanaTransactionModel( @@ -903,20 +869,21 @@ class SolanaWalletClient { from: sender, to: receiver, id: signature, - amount: amount, + amount: Money.parse(amount.toStringAsFixed((splToken ?? CryptoCurrency.sol).decimals), + splToken ?? CryptoCurrency.sol), programId: SPLTokenProgramConst.tokenProgramId.address, blockTimeInInt: blockTime?.toInt() ?? 0, - tokenSymbol: tokenSymbol ?? '', - fee: feeInSol, + fee: Money.fromInt(fee, CryptoCurrency.sol), ); } /// Fetches a specific transaction by signature and parses it - /// It returns a TransactionFetchResult object containing both transactions and token mints extracted from the transaction or null if the transaction is not found or cannot be parsed + /// It returns a TransactionFetchResult object containing both transactions and token mints + /// extracted from the transaction or null if the transaction is not found or cannot be parsed Future fetchTransactionBySignature({ required String signature, required String walletAddress, - String? splTokenSymbol, + SPLToken? splToken, }) async { try { final txResponse = await _provider!.request( @@ -936,7 +903,7 @@ class SolanaWalletClient { final parsed = await parseTransaction( txResponse: versionedResponse, walletAddress: walletAddress, - splTokenSymbol: splTokenSymbol, + splToken: splToken, ); if (parsed == null) return null; @@ -985,8 +952,7 @@ class SolanaWalletClient { /// Load the Address's transactions into the account Future> fetchTransactions( SolAddress address, { - String? splTokenSymbol, - int? splTokenDecimal, + SPLToken? splToken, Commitment? commitment, SolAddress? walletAddress, required void Function(List) onUpdate, @@ -1025,7 +991,7 @@ class SolanaWalletClient { final parsedTransactionsFutures = versionedBatchResponses.map((tx) => parseTransaction( txResponse: tx, - splTokenSymbol: splTokenSymbol, + splToken: splToken, walletAddress: walletAddress?.address ?? address.address, )); @@ -1057,8 +1023,7 @@ class SolanaWalletClient { Future> getSPLTokenTransfers({ required String mintAddress, - required String splTokenSymbol, - required int splTokenDecimal, + required SPLToken splToken, required SolanaPrivateKey privateKey, required void Function(List) onUpdate, }) async { @@ -1077,23 +1042,19 @@ class SolanaWalletClient { if (associatedTokenAccount == null) return []; - final accountPublicKey = associatedTokenAccount.address; - - final tokenTransactions = await fetchTransactions( - accountPublicKey, - splTokenSymbol: splTokenSymbol, - splTokenDecimal: splTokenDecimal, + return fetchTransactions( + associatedTokenAccount.address, + splToken: splToken, walletAddress: ownerWalletAddress, onUpdate: onUpdate, ); - - return tokenTransactions; } final Map tokenInfoCache = {}; Future getTokenInfo(String mintAddress) async { if (tokenInfoCache.containsKey(mintAddress)) { + printV("Cached"); return tokenInfoCache[mintAddress]; } else { final token = await fetchSPLTokenInfo(mintAddress); @@ -1119,16 +1080,15 @@ class SolanaWalletClient { }, ); + if (response.statusCode != 200) return null; final decodedResponse = jsonDecode(response.body) as Map; - final symbol = (decodedResponse['symbol'] ?? '') as String; - + final symbol = decodedResponse['symbol'] ?? ''; final name = decodedResponse['name'] ?? ''; final decimal = decodedResponse['decimals'] ?? '0'; final iconPath = decodedResponse['logo'] ?? ''; - String filteredTokenSymbol = - symbol.replaceFirst(RegExp('^\\\$'), '').replaceAll('\u0000', ''); + final filteredTokenSymbol = symbol.replaceFirst(RegExp('^\\\$'), '').replaceAll('\u0000', ''); return SPLToken( name: name, @@ -1182,20 +1142,18 @@ class SolanaWalletClient { SolanaRPC? get getSolanaProvider => _provider; Future signSolanaTransaction({ - required String tokenTitle, - required int tokenDecimals, - required double inputAmount, + required Money inputAmount, required String destinationAddress, required SolanaPrivateKey ownerPrivateKey, required bool isSendAll, - required double solBalance, + required Money solBalance, String? tokenMint, List references = const [], }) async { const commitment = Commitment.confirmed; - if (tokenTitle == CryptoCurrency.sol.title) { - final pendingNativeTokenTransaction = await _signNativeTokenTransaction( + if (inputAmount.currency == CryptoCurrency.sol) { + return _signNativeTokenTransaction( inputAmount: inputAmount, destinationAddress: destinationAddress, ownerPrivateKey: ownerPrivateKey, @@ -1203,10 +1161,9 @@ class SolanaWalletClient { isSendAll: isSendAll, solBalance: solBalance, ); - return pendingNativeTokenTransaction; } else { - final pendingSPLTokenTransaction = _signSPLTokenTransaction( - tokenDecimals: tokenDecimals, + return _signSPLTokenTransaction( + tokenDecimals: inputAmount.currency.decimals, tokenMint: tokenMint!, inputAmount: inputAmount, ownerPrivateKey: ownerPrivateKey, @@ -1214,7 +1171,6 @@ class SolanaWalletClient { commitment: commitment, solBalance: solBalance, ); - return pendingSPLTokenTransaction; } } @@ -1229,25 +1185,24 @@ class SolanaWalletClient { Future _getMessageForNativeTransaction({ required SolanaPublicKey publicKey, required String destinationAddress, - required int lamports, + required Money lamports, required Commitment commitment, }) async { final instructions = [ SystemProgram.transfer( from: publicKey.toAddress(), - layout: SystemTransferLayout(lamports: BigInt.from(lamports)), + layout: SystemTransferLayout(lamports: lamports.amount), to: SolAddress(destinationAddress), ), ]; final latestBlockhash = await _getLatestBlockhash(commitment); - final message = Message.compile( + return Message.compile( transactionInstructions: instructions, payer: publicKey.toAddress(), recentBlockhash: latestBlockhash, ); - return message; } Future _getMessageForSPLTokenTransaction({ @@ -1256,14 +1211,14 @@ class SolanaWalletClient { required int tokenDecimals, required SolAddress mintAddress, required SolAddress sourceAccount, - required int amount, + required Money amount, required Commitment commitment, required SolAddress tokenProgramId, }) async { final instructions = [ SPLTokenProgram.transferChecked( layout: SPLTokenTransferCheckedLayout( - amount: BigInt.from(amount), + amount: amount.amount, decimals: tokenDecimals, ), mint: mintAddress, @@ -1275,61 +1230,46 @@ class SolanaWalletClient { final latestBlockhash = await _getLatestBlockhash(commitment); - final message = Message.compile( + return Message.compile( transactionInstructions: instructions, payer: ownerAddress, recentBlockhash: latestBlockhash, ); - return message; } - Future _getFeeFromCompiledMessage(Message message, Commitment commitment) async { + Future _getFeeFromCompiledMessage(Message message, Commitment commitment) { final base64Message = base64Encode(message.serialize()); - - final fee = await getFeeForMessage(base64Message, commitment); - - return fee; + return getFeeForMessage(base64Message, commitment); } Future hasSufficientFundsLeftForRent({ - required double inputAmount, - required double solBalance, - required double fee, + required Money inputAmount, + required Money solBalance, + required Money fee, }) async { final rent = await _provider!.request( - SolanaRPCGetMinimumBalanceForRentExemption( - size: SolanaTokenAccountUtils.accountSize, - ), + SolanaRPCGetMinimumBalanceForRentExemption(size: SolanaTokenAccountUtils.accountSize), ); - final rentInSol = (rent.toDouble() / SolanaUtils.lamportsPerSol).toDouble(); - - final remnant = solBalance - (inputAmount + fee); - - if (remnant > rentInSol) return true; - - return false; + return (solBalance - (inputAmount + fee)) > Money(rent, CryptoCurrency.sol); } Future _signNativeTokenTransaction({ - required double inputAmount, + required Money inputAmount, required String destinationAddress, required SolanaPrivateKey ownerPrivateKey, required Commitment commitment, required bool isSendAll, - required double solBalance, + required Money solBalance, }) async { - // Convert SOL to lamport - int lamports = (inputAmount * SolanaUtils.lamportsPerSol).toInt(); - - Message message = await _getMessageForNativeTransaction( + final message = await _getMessageForNativeTransaction( publicKey: ownerPrivateKey.publicKey(), destinationAddress: destinationAddress, - lamports: lamports, + lamports: inputAmount, commitment: commitment, ); - SolAddress latestBlockhash = await _getLatestBlockhash(commitment); + final latestBlockhash = await _getLatestBlockhash(commitment); final fee = await _getFeeFromCompiledMessage( message, @@ -1337,7 +1277,7 @@ class SolanaWalletClient { ); if (!isSendAll) { - bool hasSufficientFundsLeft = await hasSufficientFundsLeftForRent( + final hasSufficientFundsLeft = await hasSufficientFundsLeftForRent( inputAmount: inputAmount, fee: fee, solBalance: solBalance, @@ -1350,8 +1290,7 @@ class SolanaWalletClient { String serializedTransaction; if (isSendAll) { - final feeInLamports = (fee * SolanaUtils.lamportsPerSol).toInt(); - final updatedLamports = lamports - feeInLamports; + final updatedLamports = inputAmount - fee; final transaction = _constructNativeTransaction( ownerPrivateKey: ownerPrivateKey, @@ -1369,7 +1308,7 @@ class SolanaWalletClient { ownerPrivateKey: ownerPrivateKey, destinationAddress: destinationAddress, latestBlockhash: latestBlockhash, - lamports: lamports, + lamports: inputAmount, ); serializedTransaction = await _signTransactionInternal( @@ -1383,33 +1322,29 @@ class SolanaWalletClient { commitment: commitment, ); - final pendingTransaction = PendingSolanaTransaction( + return PendingSolanaTransaction( amount: inputAmount, serializedTransaction: serializedTransaction, destinationAddress: destinationAddress, sendTransaction: sendTx, fee: fee, ); - - return pendingTransaction; } SolanaTransaction _constructNativeTransaction({ required SolanaPrivateKey ownerPrivateKey, required String destinationAddress, required SolAddress latestBlockhash, - required int lamports, + required Money lamports, }) { final owner = ownerPrivateKey.publicKey().toAddress(); - /// Create a transfer instruction to move funds from the owner to the receiver. final transferInstruction = SystemProgram.transfer( from: owner, - layout: SystemTransferLayout(lamports: BigInt.from(lamports)), + layout: SystemTransferLayout(lamports: lamports.amount), to: SolAddress(destinationAddress), ); - /// Construct a Solana transaction with the transfer instruction. return SolanaTransaction( instructions: [transferInstruction], recentBlockhash: latestBlockhash, @@ -1609,17 +1544,13 @@ class SolanaWalletClient { Future _signSPLTokenTransaction({ required int tokenDecimals, required String tokenMint, - required double inputAmount, + required Money inputAmount, required String destinationAddress, required SolanaPrivateKey ownerPrivateKey, required Commitment commitment, - required double solBalance, + required Money solBalance, }) async { final mintAddress = SolAddress(tokenMint); - - // Input by the user - final amount = (inputAmount * math.pow(10, tokenDecimals)).toInt(); - final tokenProgramId = await _getTokenProgramId(mintAddress); ProgramDerivedAddress? associatedSenderAccount; @@ -1751,7 +1682,7 @@ class SolanaWalletClient { destination: associatedRecipientAccount.address, mint: mintAddress, owner: ownerPrivateKey.publicKey().toAddress(), - amount: BigInt.from(amount), + amount: inputAmount.amount, decimals: tokenDecimals, ); @@ -1769,75 +1700,61 @@ class SolanaWalletClient { mintAddress: mintAddress, destinationAddress: associatedRecipientAccount.address, sourceAccount: associatedSenderAccount.address, - amount: amount, + amount: inputAmount, commitment: commitment, tokenProgramId: tokenProgramId, ); final fee = await _getFeeFromCompiledMessage(message, commitment); - bool hasSufficientFundsLeft = await hasSufficientFundsLeftForRent( - inputAmount: 0, + final hasSufficientFundsLeft = await hasSufficientFundsLeftForRent( + inputAmount: Money.zero(CryptoCurrency.sol), fee: fee, solBalance: solBalance, ); - if (!hasSufficientFundsLeft) { - throw SolanaSignSPLTokenTransactionRentException(); - } + if (!hasSufficientFundsLeft) throw SolanaSignSPLTokenTransactionRentException(); final serializedTransaction = await _signTransactionInternal( ownerPrivateKey: ownerPrivateKey, transaction: transaction, ); - sendTx() async => await sendTransaction( + sendTx() => sendTransaction( serializedTransaction: serializedTransaction, commitment: commitment, ); - final pendingTransaction = PendingSolanaTransaction( + return PendingSolanaTransaction( amount: inputAmount, serializedTransaction: serializedTransaction, destinationAddress: destinationAddress, sendTransaction: sendTx, fee: fee, ); - return pendingTransaction; } Future _signTransactionInternal({ required SolanaPrivateKey ownerPrivateKey, required SolanaTransaction transaction, }) async { - /// Sign the transaction with the owner's private key. final ownerSignature = ownerPrivateKey.sign(transaction.serializeMessage()); transaction.addSignature(ownerPrivateKey.publicKey().toAddress(), ownerSignature); - /// Serialize the transaction. - final serializedTransaction = transaction.serializeString(); - - return serializedTransaction; + return transaction.serializeString(); } Future sendTransaction({ required String serializedTransaction, - required Commitment commitment, - }) async { - try { - /// Send the transaction to the Solana network. - final signature = await _provider!.request( + required Commitment commitment + }) => + _provider!.request( SolanaRPCSendTransaction( encodedTransaction: serializedTransaction, commitment: commitment, ), ); - return signature; - } catch (e) { - throw Exception(e); - } - } Future getIconImageFromTokenUri(String uri) async { if (uri.isEmpty || uri == '…') return null; diff --git a/cw_solana/lib/solana_transaction_info.dart b/cw_solana/lib/solana_transaction_info.dart index 85c28ac07..94caeefa9 100644 --- a/cw_solana/lib/solana_transaction_info.dart +++ b/cw_solana/lib/solana_transaction_info.dart @@ -1,80 +1,69 @@ -import 'package:cw_core/format_amount.dart'; +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; class SolanaTransactionInfo extends TransactionInfo { SolanaTransactionInfo({ required this.id, - required this.blockTime, + required this.date, required this.to, required this.from, required this.direction, - required this.solAmount, - this.tokenSymbol = "SOL", + required this.amount, required this.isPending, - required this.txFee, - }) : amount = solAmount.toInt(); + required this.fee, + }); + @override final String id; + @override final String? to; + @override final String? from; - final int amount; - final bool isPending; - final double solAmount; - final String tokenSymbol; - final DateTime blockTime; - final double txFee; - final TransactionDirection direction; - - String? _fiatAmount; @override String get txHash => id.replaceFirst(RegExp(r'_(outgoing|incoming)$'), ''); @override - DateTime get date => blockTime; - + final Money amount; @override - String amountFormatted() { - String stringBalance = solAmount.toString(); - if (stringBalance.toString().length >= 12) { - stringBalance = stringBalance.substring(0, 12); - } - return '$stringBalance $tokenSymbol'; - } - + final bool isPending; @override - String fiatAmount() => _fiatAmount ?? ''; - + final Money fee; @override - void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); - + final TransactionDirection direction; @override - String feeFormatted() => '${txFee.toString()} SOL'; + final DateTime date; factory SolanaTransactionInfo.fromJson(Map data) { + final symbol = data['tokenSymbol'] as String? ?? "SOL"; + final decimals = data['tokenDecimals'] as int? ?? 6; + + final currency = CryptoCurrency(name: symbol, title: symbol, decimals: decimals); + return SolanaTransactionInfo( id: data['id'] as String, - solAmount: data['solAmount'], + amount: Money.parse(data['solAmount'].toString(), currency), direction: parseTransactionDirectionFromInt(data['direction'] as int), - blockTime: DateTime.fromMillisecondsSinceEpoch(data['blockTime'] as int), + date: DateTime.fromMillisecondsSinceEpoch(data['blockTime'] as int), isPending: data['isPending'] as bool, - tokenSymbol: data['tokenSymbol'] as String, to: data['to'], from: data['from'], - txFee: data['txFee'], + fee: Money.parse(data['txFee'], CryptoCurrency.sol), ); } Map toJson() => { 'id': id, - 'solAmount': solAmount, + 'solAmount': amount.toString(), 'direction': direction.index, - 'blockTime': blockTime.millisecondsSinceEpoch, + 'blockTime': date.millisecondsSinceEpoch, 'isPending': isPending, - 'tokenSymbol': tokenSymbol, + 'tokenSymbol': amount.currency.symbol, + 'tokenDecimals': amount.currency.decimals, 'to': to, 'from': from, - 'txFee': txFee, + 'txFee': fee.toString(), }; } diff --git a/cw_solana/lib/solana_transaction_model.dart b/cw_solana/lib/solana_transaction_model.dart index c16c49258..6086ccb8a 100644 --- a/cw_solana/lib/solana_transaction_model.dart +++ b/cw_solana/lib/solana_transaction_model.dart @@ -1,3 +1,5 @@ +import 'package:cw_core/amount/money.dart'; + class SolanaTransactionModel { final String id; @@ -5,22 +7,16 @@ class SolanaTransactionModel { final String to; - final double amount; + final Money amount; - // If this is an outgoing transaction final bool isOutgoingTx; // The Program ID of this transaction, e.g, System Program, Token Program... final String programId; - // The DateTime from the UNIX timestamp of the block where the transaction was included final DateTime blockTime; - // The Transaction fee - final double fee; - - // The token symbol - final String tokenSymbol; + final Money fee; SolanaTransactionModel({ required this.id, @@ -30,18 +26,6 @@ class SolanaTransactionModel { required this.programId, required int blockTimeInInt, this.isOutgoingTx = false, - required this.tokenSymbol, required this.fee, }) : blockTime = DateTime.fromMillisecondsSinceEpoch(blockTimeInInt * 1000); - - factory SolanaTransactionModel.fromJson(Map json) => SolanaTransactionModel( - id: json['id'], - blockTimeInInt: int.parse(json["timeStamp"]) * 1000, - from: json["from"], - to: json["to"], - amount: double.parse(json["value"]), - programId: json["programId"], - fee: json['fee'], - tokenSymbol: json['tokenSymbol'], - ); } diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index 76f2d8924..48a1b0e71 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/encryption_file_utils.dart'; @@ -57,7 +58,7 @@ abstract class SolanaWalletBase _client = SolanaWalletClient(), walletAddresses = SolanaWalletAddresses(walletInfo), balance = ObservableMap.of( - {CryptoCurrency.sol: initialBalance ?? SolanaBalance(BigInt.zero.toDouble(), false)}), + {CryptoCurrency.sol: initialBalance ?? SolanaBalance.zero(CryptoCurrency.sol)}), super(walletInfo, derivationInfo) { this.walletInfo = walletInfo; transactionHistory = SolanaTransactionHistory( @@ -83,7 +84,7 @@ abstract class SolanaWalletBase SolanaWalletClient get client => _client; @observable - double? estimatedFee; + Money? estimatedFee; Timer? _transactionsUpdateTimer; @@ -214,9 +215,8 @@ abstract class SolanaWalletBase Future _getEstimatedFees() async { try { estimatedFee = await _client.getEstimatedFee(_solanaPublicKey, Commitment.confirmed); - printV(estimatedFee.toString()); } catch (e) { - estimatedFee = 0.0; + estimatedFee = Money.zero(currency); } } @@ -237,27 +237,16 @@ abstract class SolanaWalletBase orElse: () => throw Exception( 'Currency ${credentials.currency.title} ${credentials.currency.tag} is not accessible in the wallet, try to enable it first.')); - final walletBalanceForCurrency = balance[transactionCurrency]!.balance; + final walletBalanceForCurrency = balance[transactionCurrency]!.available; - final solBalance = balance[CryptoCurrency.sol]!.balance; + final solBalance = balance[CryptoCurrency.sol]!.available; - double totalAmount = 0.0; - - bool isSendAll = false; + var totalAmount = Money.zero(transactionCurrency); + var isSendAll = false; if (hasMultiDestination) { - if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) { - throw SolanaTransactionWrongBalanceException(transactionCurrency); - } - - final totalAmountFromCredentials = - outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)); - - totalAmount = totalAmountFromCredentials.toDouble(); - - if (walletBalanceForCurrency < totalAmount) { - throw SolanaTransactionWrongBalanceException(transactionCurrency); - } + // Solana doesn't have multi destination right now + throw SolanaTransactionCreationException(transactionCurrency); } else { final output = outputs.first; @@ -266,9 +255,7 @@ abstract class SolanaWalletBase if (isSendAll) { totalAmount = walletBalanceForCurrency; } else { - final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0'); - - totalAmount = totalOriginalAmount; + totalAmount = output.cryptoAmount.copyWith(currency: transactionCurrency); } if (walletBalanceForCurrency < totalAmount) { @@ -282,20 +269,16 @@ abstract class SolanaWalletBase tokenMint = (transactionCurrency as SPLToken).mintAddress; } - final pendingSolanaTransaction = await _client.signSolanaTransaction( + return _client.signSolanaTransaction( tokenMint: tokenMint, - tokenTitle: transactionCurrency.title, inputAmount: totalAmount, ownerPrivateKey: _solanaPrivateKey, - tokenDecimals: transactionCurrency.decimals, destinationAddress: solCredentials.outputs.first.isParsedAddress ? solCredentials.outputs.first.extractedAddress! : solCredentials.outputs.first.address, isSendAll: isSendAll, solBalance: solBalance, ); - - return pendingSolanaTransaction; } @override @@ -387,8 +370,7 @@ abstract class SolanaWalletBase try { return await _client.getSPLTokenTransfers( mintAddress: token.mintAddress, - splTokenSymbol: token.symbol, - splTokenDecimal: token.decimal, + splToken: token, privateKey: _solanaPrivateKey, onUpdate: updateTransactions, ); @@ -414,14 +396,13 @@ abstract class SolanaWalletBase id: transactionModel.id, to: transactionModel.to, from: transactionModel.from, - blockTime: transactionModel.blockTime, + date: transactionModel.blockTime, direction: transactionModel.isOutgoingTx ? TransactionDirection.outgoing : TransactionDirection.incoming, - solAmount: transactionModel.amount, + amount: transactionModel.amount, isPending: false, - txFee: transactionModel.fee, - tokenSymbol: transactionModel.tokenSymbol, + fee: transactionModel.fee, ); } @@ -497,7 +478,8 @@ abstract class SolanaWalletBase if (!hasKeysFile) rethrow; } - final balance = SolanaBalance.fromJSON(data?['balance'] as String?, false) ?? SolanaBalance(0.0, false); + final balance = SolanaBalance.fromJSON(data?['balance'] as String?, CryptoCurrency.sol) ?? + SolanaBalance.zero(CryptoCurrency.sol); final WalletKeysData keysData; // Migrate wallet from the old scheme to then new .keys file scheme @@ -545,7 +527,7 @@ abstract class SolanaWalletBase Future _fetchSOLBalance() async { final balance = await _client.getBalance(solanaAddress); - return SolanaBalance(balance, false); + return SolanaBalance(balance); } /// Internal helper to update SPL token balances. @@ -577,7 +559,7 @@ abstract class SolanaWalletBase final results = await Future.wait(batch.map((token) async { try { - final fetched = await _client.getSplTokenBalance(token.mintAddress, solanaAddress); + final fetched = await _client.getSplTokenBalance(token, solanaAddress); return MapEntry(token, fetched); } catch (e) { printV('Error fetching spl token (${token.symbol}) balance ${e.toString()}'); @@ -588,8 +570,7 @@ abstract class SolanaWalletBase for (final entry in results) { final token = entry.key; final fetchedBalance = entry.value; - final currentBalance = balance[token] ?? - SolanaBalance.forToken(BigInt.zero, 0.0); + final currentBalance = balance[token] ?? SolanaBalance.zero(token); balance[token] = fetchedBalance ?? currentBalance; } } @@ -605,7 +586,7 @@ abstract class SolanaWalletBase await _client.getBalance(solanaAddress, throwOnError: true); // Check USDC token balance - const usdcMintAddress = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; + final usdcMintAddress = DefaultSPLTokens().usdc; await _client.getSplTokenBalance(usdcMintAddress, solanaAddress, throwOnError: true); return true; @@ -694,9 +675,8 @@ abstract class SolanaWalletBase await splTokensBox.put(token.mintAddress, token); if (token.enabled) { - final tokenBalance = await _client.getSplTokenBalance(token.mintAddress, solanaAddress) ?? - balance[token] ?? - SolanaBalance.forToken(BigInt.zero, 0.0); + final tokenBalance = await _client.getSplTokenBalance(token, solanaAddress) ?? + balance[token] ?? SolanaBalance.zero(token); balance[token] = tokenBalance; } else { @@ -715,7 +695,8 @@ abstract class SolanaWalletBase } Future _removeTokenTransactionsInHistory(SPLToken token) async { - transactionHistory.transactions.removeWhere((key, value) => value.tokenSymbol == token.title); + transactionHistory.transactions + .removeWhere((key, value) => value.amount.currency.symbol == token.symbol); await transactionHistory.save(); } diff --git a/cw_solana/pubspec.yaml b/cw_solana/pubspec.yaml index fdc946c08..1d0572ddd 100644 --- a/cw_solana/pubspec.yaml +++ b/cw_solana/pubspec.yaml @@ -35,7 +35,7 @@ dev_dependencies: flutter_lints: ^2.0.0 build_runner: ^2.4.15 mobx_codegen: ^2.0.7 - hive_generator: ^2.0.1 +# hive_generator: ^2.0.1 dependency_overrides: watcher: ^1.1.0 diff --git a/cw_tron/lib/default_tron_tokens.dart b/cw_tron/lib/default_tron_tokens.dart index 49c837523..ceafc6fc0 100644 --- a/cw_tron/lib/default_tron_tokens.dart +++ b/cw_tron/lib/default_tron_tokens.dart @@ -104,4 +104,6 @@ class DefaultTronTokens { return TronToken.copyWith(token, icon: iconPath, tag: 'TRX'); }).toList(); + + TronToken get usdt => _defaultTokens.where((e) => e.symbol == "USDT").first; } diff --git a/cw_tron/lib/pending_tron_transaction.dart b/cw_tron/lib/pending_tron_transaction.dart index 5a1afc527..7eac73494 100644 --- a/cw_tron/lib/pending_tron_transaction.dart +++ b/cw_tron/lib/pending_tron_transaction.dart @@ -1,13 +1,10 @@ - - +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:web3dart/crypto.dart'; class PendingTronTransaction with PendingTransaction { final Function sendTransaction; final List signedTransaction; - final String fee; - final String amount; PendingTronTransaction({ required this.sendTransaction, @@ -18,17 +15,17 @@ class PendingTronTransaction with PendingTransaction { }); @override - String get amountFormatted => amount; + final Money amount; + + @override + final Money fee; + + @override + String get amountFormatted => amount.toString(); @override Future commit() async => await sendTransaction(); - @override - String get feeFormatted => "$feeFormattedValue TRX"; - - @override - String get feeFormattedValue => fee; - @override String get hex => bytesToHex(signedTransaction); diff --git a/cw_tron/lib/tron_balance.dart b/cw_tron/lib/tron_balance.dart index 6c28ad337..c01c439ba 100644 --- a/cw_tron/lib/tron_balance.dart +++ b/cw_tron/lib/tron_balance.dart @@ -1,25 +1,25 @@ import 'dart:convert'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/balance.dart'; +import 'package:cw_core/currency.dart'; class TronBalance extends Balance { TronBalance(this.balance) : super(balance, balance); - final BigInt balance; + final Money balance; - String toJSON() => json.encode({ 'balance': balance.toString() }); + String toJSON() => json.encode({ 'balance': balance.amount.toString() }); - static TronBalance? fromJSON(String? jsonSource) { - if (jsonSource == null) { - return null; - } + static TronBalance? fromJSON(String? jsonSource, Currency currency) { + if (jsonSource == null) return null; final decoded = json.decode(jsonSource) as Map; try { - return TronBalance(BigInt.parse(decoded['balance'])); + return TronBalance(Money(BigInt.parse(decoded['balance']), currency)); } catch (e) { - return TronBalance(BigInt.zero); + return TronBalance(Money.zero(currency)); } } } diff --git a/cw_tron/lib/tron_client.dart b/cw_tron/lib/tron_client.dart index 18d797655..4b1ac4725 100644 --- a/cw_tron/lib/tron_client.dart +++ b/cw_tron/lib/tron_client.dart @@ -3,9 +3,12 @@ import 'dart:convert'; import 'dart:developer'; import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/currency.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; +import 'package:cw_tron/default_tron_tokens.dart'; import 'package:cw_tron/pending_tron_transaction.dart'; import 'package:cw_tron/tron_abi.dart'; import 'package:cw_tron/tron_balance.dart'; @@ -234,26 +237,18 @@ class TronClient { } Future getTRCEstimatedFee(TronAddress ownerAddress) async { - String contractAddress = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'; - String constantAmount = - '0'; // We're using 0 as the base amount here as we get an error when balance is zero i.e for new wallets. + final usdtContractAddress = DefaultTronTokens().usdt.contractAddress; final contract = ContractABI.fromJson(trc20Abi, isTron: true); - final function = contract.functionFromName("transfer"); - /// address /// amount - final transferparams = [ - ownerAddress, - TronHelper.toSun(constantAmount), - ]; - - final contractAddr = TronAddress(contractAddress); + // We're using 0 as the base amount here as we get an error when balance is zero i.e for new wallets. + final transferParams = [ownerAddress, BigInt.zero]; final request = await _provider!.request( TronRequestTriggerConstantContract( ownerAddress: ownerAddress, - contractAddress: contractAddr, - data: function.encodeHex(transferparams), + contractAddress: TronAddress(usdtContractAddress), + data: function.encodeHex(transferParams), ), ); @@ -261,21 +256,19 @@ class TronClient { log("Tron TRC20 error: ${request.error} \n ${request.respose}"); } - final feeLimit = await getFeeLimit( + return getFeeLimit( request.transactionRaw!, ownerAddress, ownerAddress, energyUsed: request.energyUsed ?? 0, isEstimatedFeeFlow: true, ); - return feeLimit; } Future signTransaction({ required TronPrivateKey ownerPrivKey, required String toAddress, - required String amount, - required CryptoCurrency currency, + required Money amount, required BigInt tronBalance, required bool sendAll, }) async { @@ -285,9 +278,9 @@ class TronClient { // Define the receiving Tron address for the transaction. final receiverAddress = TronAddress(toAddress); - bool isNativeTransaction = currency == CryptoCurrency.trx; + final isNativeTransaction = amount.currency == CryptoCurrency.trx; - String totalAmount; + Money totalAmount; TransactionRaw rawTransaction; if (isNativeTransaction) { if (sendAll) { @@ -300,12 +293,10 @@ class TronClient { if (availableBandWidth >= 269) { totalAmount = amount; } else { - final amountInSun = TronHelper.toSun(amount).toInt(); - // 5000 added here is a buffer since we're working with "estimated" value of the fee. - final result = amountInSun - (_nativeTxEstimatedFee + 5000); + final result = amount.amount - BigInt.from(_nativeTxEstimatedFee + 5000); - totalAmount = TronHelper.fromSun(BigInt.from(result)); + totalAmount = amount.copyWith(amount: result); } } else { totalAmount = amount; @@ -318,7 +309,7 @@ class TronClient { sendAll, ); } else { - final tokenAddress = (currency as TronToken).contractAddress; + final tokenAddress = (amount.currency as TronToken).contractAddress; totalAmount = amount; rawTransaction = await _signTrcTokenTransaction( ownerAddress, @@ -339,7 +330,7 @@ class TronClient { return PendingTronTransaction( signedTransaction: signature, amount: totalAmount, - fee: TronHelper.fromSun(rawTransaction.feeLimit ?? BigInt.zero), + fee: Money(rawTransaction.feeLimit ?? BigInt.zero, CryptoCurrency.trx), sendTransaction: sendTx, id: rawTransaction.txID ); @@ -348,7 +339,7 @@ class TronClient { Future _signNativeTransaction( TronAddress ownerAddress, TronAddress receiverAddress, - String amount, + Money amount, BigInt tronBalance, bool sendAll, ) async { @@ -359,7 +350,7 @@ class TronClient { final block = await _provider!.request(TronRequestGetNowBlock()); // Create the transfer contract final contract = TransferContract( - amount: TronHelper.toSun(amount), + amount: amount.amount, ownerAddress: ownerAddress, toAddress: receiverAddress, ); @@ -374,8 +365,7 @@ class TronClient { // Set the transaction expiration time (maximum 24 hours) final expireTime = DateTime.now().add(const Duration(minutes: 30)); - // Create a raw transaction - TransactionRaw rawTransaction = TransactionRaw( + final rawTransaction = TransactionRaw( refBlockBytes: block.blockHeader.rawData.refBlockBytes, refBlockHash: block.blockHeader.rawData.refBlockHash, expiration: BigInt.from(expireTime.millisecondsSinceEpoch), @@ -394,45 +384,31 @@ class TronClient { ); } - rawTransaction = rawTransaction.copyWith( - feeLimit: BigInt.from(feeLimitToUse), - ); - - return rawTransaction; + return rawTransaction.copyWith(feeLimit: BigInt.from(feeLimitToUse)); } Future _signTrcTokenTransaction( TronAddress ownerAddress, TronAddress receiverAddress, - String amount, + Money amount, String contractAddress, BigInt tronBalance, ) async { final contract = ContractABI.fromJson(trc20Abi, isTron: true); final function = contract.functionFromName("transfer"); - - /// address /// amount - final transferparams = [ - receiverAddress, - TronHelper.toSun(amount), - ]; - - final contractAddr = TronAddress(contractAddress); - + final transferParams = [receiverAddress, amount.amount]; final request = await _provider!.request( TronRequestTriggerConstantContract( ownerAddress: ownerAddress, - contractAddress: contractAddr, - data: function.encodeHex(transferparams), + contractAddress: TronAddress(contractAddress), + data: function.encodeHex(transferParams), ), ); if (!request.isSuccess) { log("Tron TRC20 error: ${request.error} \n ${request.respose}"); - throw Exception( - 'An error occurred while creating the transfer request. Please try again.', - ); + throw Exception('An error occurred while creating the transfer request. Please try again.'); } final feeLimit = await getFeeLimit( @@ -442,20 +418,14 @@ class TronClient { energyUsed: request.energyUsed ?? 0, ); - final tronBalanceInt = tronBalance.toInt(); - - if (feeLimit > tronBalanceInt) { + if (feeLimit > tronBalance.toInt()) { final feeInTrx = TronHelper.fromSun(BigInt.parse(feeLimit.toString())); throw Exception( 'You don\'t have enough TRX to cover the transaction fee for this transaction. Please top up. Transaction fee: $feeInTrx TRX', ); } - final rawTransaction = request.transactionRaw!.copyWith( - feeLimit: BigInt.from(feeLimit), - ); - - return rawTransaction; + return request.transactionRaw!.copyWith(feeLimit: BigInt.from(feeLimit)); } Future sendTransaction({ @@ -480,20 +450,18 @@ class TronClient { } } - Future fetchTronTokenBalances(String userAddress, String contractAddress, {bool throwOnError = false}) async { + Future fetchTronTokenBalances(String userAddress, String contractAddress, + {bool throwOnError = false, required Currency currency}) async { try { final ownerAddress = TronAddress(userAddress); - final tokenAddress = TronAddress(contractAddress); - final contract = ContractABI.fromJson(trc20Abi, isTron: true); - final function = contract.functionFromName("balanceOf"); final request = await _provider!.request( TronRequestTriggerConstantContract.fromMethod( ownerAddress: ownerAddress, - contractAddress: tokenAddress, + contractAddress: TronAddress(contractAddress), function: function, params: [ownerAddress], ), @@ -501,12 +469,12 @@ class TronClient { final outputResult = request.outputResult?.first ?? BigInt.zero; - return TronBalance(outputResult); + return TronBalance(Money(outputResult, currency)); } catch (_) { if (throwOnError) { rethrow; } - return TronBalance(BigInt.zero); + return TronBalance(Money.zero(currency)); } } diff --git a/cw_tron/lib/tron_transaction_info.dart b/cw_tron/lib/tron_transaction_info.dart index 28c704d20..ed9e40656 100644 --- a/cw_tron/lib/tron_transaction_info.dart +++ b/cw_tron/lib/tron_transaction_info.dart @@ -1,41 +1,55 @@ -import 'package:cw_core/format_amount.dart'; +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; -import 'package:on_chain/on_chain.dart' as onchain; import 'package:on_chain/tron/tron.dart'; class TronTransactionInfo extends TransactionInfo { TronTransactionInfo({ required this.id, - required this.tronAmount, - required this.txFee, + required this.amount, + required this.fee, required this.direction, required this.blockTime, required this.to, required this.from, required this.isPending, - this.tokenSymbol = 'TRX', - }) : amount = tronAmount.toInt(); + }); + @override final String id; + + @override final String? to; + + @override final String? from; - final int amount; - final BigInt tronAmount; - final String tokenSymbol; - final DateTime blockTime; + + @override + final Money amount; + + @override + final Money? fee; + + @override final bool isPending; - final int? txFee; + + @override final TransactionDirection direction; + final DateTime blockTime; + factory TronTransactionInfo.fromJson(Map data) { + final tokenSymbol = data['tokenSymbol'] as String; + final decimals = data['decimals'] as int? ?? CryptoCurrency.trx.decimals; + final currency = CryptoCurrency(name: tokenSymbol, title: tokenSymbol, decimals: decimals); + return TronTransactionInfo( id: data['id'] as String, - tronAmount: BigInt.parse(data['tronAmount']), - txFee: data['txFee'], + amount: Money(BigInt.parse(data['tronAmount']), currency), + fee: Money.tryParse(data['txFee']?.toString() ?? '0', CryptoCurrency.trx, isBaseUnit: true), direction: parseTransactionDirectionFromInt(data['direction'] as int), blockTime: DateTime.fromMillisecondsSinceEpoch(data['blockTime'] as int), - tokenSymbol: data['tokenSymbol'] as String, to: data['to'], from: data['from'], isPending: data['isPending'], @@ -44,41 +58,20 @@ class TronTransactionInfo extends TransactionInfo { Map toJson() => { 'id': id, - 'tronAmount': tronAmount.toString(), - 'txFee': txFee, + 'tronAmount': amount.amount.toString(), + 'txFee': fee?.amount.toString(), 'direction': direction.index, 'blockTime': blockTime.millisecondsSinceEpoch, - 'tokenSymbol': tokenSymbol, 'to': to, 'from': from, 'isPending': isPending, + 'tokenSymbol': amount.currency.symbol, + 'decimals': amount.currency.decimals }; @override DateTime get date => blockTime; - String? _fiatAmount; - - @override - String amountFormatted() { - String formattedAmount = _rawAmountAsString(tronAmount); - - return '$formattedAmount $tokenSymbol'; - } - - @override - String fiatAmount() => _fiatAmount ?? ''; - - @override - void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); - - @override - String feeFormatted() { - final formattedFee = onchain.TronHelper.fromSun(BigInt.from(txFee ?? 0)); - - return '$formattedFee TRX'; - } - String _rawAmountAsString(BigInt amount) { String formattedAmount = TronHelper.fromSun(amount); @@ -89,5 +82,5 @@ class TronTransactionInfo extends TransactionInfo { return formattedAmount; } - String rawTronAmount() => _rawAmountAsString(tronAmount); + String rawTronAmount() => _rawAmountAsString(amount.amount); } diff --git a/cw_tron/lib/tron_transaction_model.dart b/cw_tron/lib/tron_transaction_model.dart index 1748adc53..5aca17b74 100644 --- a/cw_tron/lib/tron_transaction_model.dart +++ b/cw_tron/lib/tron_transaction_model.dart @@ -1,4 +1,5 @@ import 'package:blockchain_utils/hex/hex.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:on_chain/on_chain.dart'; class TronTRC20TransactionModel extends TronTransactionModel { @@ -6,6 +7,11 @@ class TronTRC20TransactionModel extends TronTransactionModel { String? tokenSymbol; + int? decimals; + + CryptoCurrency get currency => CryptoCurrency( + name: tokenSymbol ?? "TRX", title: tokenSymbol ?? "TRX", decimals: decimals ?? 6); + int? timestamp; @override @@ -40,6 +46,7 @@ class TronTRC20TransactionModel extends TronTransactionModel { TronTRC20TransactionModel.fromJson(Map json) { transactionId = json['transaction_id']; tokenSymbol = json['token_info'] != null ? json['token_info']['symbol'] : null; + decimals = json['token_info'] != null ? json['token_info']['decimals'] : null; timestamp = json['block_timestamp']; from = json['from']; to = json['to']; diff --git a/cw_tron/lib/tron_wallet.dart b/cw_tron/lib/tron_wallet.dart index c8d9fb929..1a4d98b48 100644 --- a/cw_tron/lib/tron_wallet.dart +++ b/cw_tron/lib/tron_wallet.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:bip39/bip39.dart' as bip39; import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/encryption_file_utils.dart'; @@ -56,7 +57,7 @@ abstract class TronWalletBase _client = TronClient(), walletAddresses = TronWalletAddresses(walletInfo), balance = ObservableMap.of( - {CryptoCurrency.trx: initialBalance ?? TronBalance(BigInt.zero)}, + {CryptoCurrency.trx: initialBalance ?? TronBalance(Money.zero(CryptoCurrency.trx))}, ), super(walletInfo, derivationInfo) { this.walletInfo = walletInfo; @@ -93,10 +94,10 @@ abstract class TronWalletBase WalletAddresses walletAddresses; @observable - String? nativeTxEstimatedFee; + Money? nativeTxEstimatedFee; @observable - String? trc20EstimatedFee; + Money? trc20EstimatedFee; @override @observable @@ -145,7 +146,8 @@ abstract class TronWalletBase if (!hasKeysFile) rethrow; } - final balance = TronBalance.fromJSON(data?['balance'] as String?) ?? TronBalance(BigInt.zero); + final balance = TronBalance.fromJSON(data?['balance'] as String?, CryptoCurrency.trx) ?? + TronBalance(Money.zero(CryptoCurrency.trx)); final WalletKeysData keysData; // Migrate wallet from the old scheme to then new .keys file scheme @@ -253,10 +255,10 @@ abstract class TronWalletBase Future _getEstimatedFees() async { final nativeFee = await _getNativeTxFee(); - nativeTxEstimatedFee = TronHelper.fromSun(BigInt.from(nativeFee)); + nativeTxEstimatedFee = Money.fromInt(nativeFee, currency); final trc20Fee = await _getTrc20TxFee(); - trc20EstimatedFee = TronHelper.fromSun(BigInt.from(trc20Fee)); + trc20EstimatedFee = Money.fromInt(trc20Fee, currency); log('Native Estimated Fee: $nativeTxEstimatedFee'); log('TRC20 Estimated Fee: $trc20EstimatedFee'); @@ -321,21 +323,11 @@ abstract class TronWalletBase final walletBalanceForCurrency = balance[transactionCurrency]!.balance; - BigInt totalAmount = BigInt.zero; - bool shouldSendAll = false; + var totalAmount = Money.zero(transactionCurrency); + var shouldSendAll = false; if (hasMultiDestination) { - if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) { + // Tron does not have multi Destination right now throw TronTransactionCreationException(transactionCurrency); - } - - final totalAmountFromCredentials = - outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)); - - totalAmount = BigInt.from(totalAmountFromCredentials); - - if (walletBalanceForCurrency < totalAmount) { - throw TronTransactionCreationException(transactionCurrency); - } } else { final output = outputs.first; @@ -344,25 +336,23 @@ abstract class TronWalletBase if (shouldSendAll) { totalAmount = walletBalanceForCurrency; } else { - final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0'); - totalAmount = TronHelper.toSun(totalOriginalAmount.toString()); + totalAmount = output.cryptoAmount.copyWith(currency: transactionCurrency); } - if (walletBalanceForCurrency < totalAmount || totalAmount < BigInt.zero) { + if (walletBalanceForCurrency < totalAmount || totalAmount < Money.zero(transactionCurrency)) { throw TronTransactionCreationException(transactionCurrency); } } - final tronBalance = balance[CryptoCurrency.trx]?.balance ?? BigInt.zero; + final tronBalance = balance[CryptoCurrency.trx]?.balance ?? Money.zero(CryptoCurrency.trx); final pendingTransaction = await _client.signTransaction( ownerPrivKey: _tronPrivateKey, toAddress: tronCredentials.outputs.first.isParsedAddress ? tronCredentials.outputs.first.extractedAddress! : tronCredentials.outputs.first.address, - amount: TronHelper.fromSun(totalAmount), - currency: transactionCurrency, - tronBalance: tronBalance, + amount: totalAmount, + tronBalance: tronBalance.amount, sendAll: shouldSendAll, ); @@ -394,33 +384,41 @@ abstract class TronWalletBase continue; } - // Filter out spam transaactions that involve receiving TRC10 assets transaction, we deal with TRX and TRC20 transactions + // Filter out spam transactions that involve receiving TRC10 assets transaction, we deal with TRX and TRC20 transactions if (transactionModel.contracts?.first.type == "TransferAssetContract") { continue; } - String? tokenSymbol; + var txCurrency = currency; if (transactionModel.contractAddress != null) { final tokenAddress = TronAddress(transactionModel.contractAddress!); - tokenSymbol = (await _client.getTokenDetail( + final tokenSymbol = (await _client.getTokenDetail( contract, "symbol", ownerAddress, tokenAddress, ) as String?) ?? ''; + + final decimals = (await _client.getTokenDetail( + contract, + "decimals", + ownerAddress, + tokenAddress, + ) as BigInt?)?.toInt() ?? txCurrency.decimals; + + txCurrency = CryptoCurrency(name: tokenSymbol, title: tokenSymbol, decimals: decimals); } result[transactionModel.hash] = TronTransactionInfo( id: transactionModel.hash, - tronAmount: transactionModel.amount ?? BigInt.zero, + amount: Money(transactionModel.amount ?? BigInt.zero, txCurrency), direction: TronAddress(transactionModel.from!, visible: false).toAddress() == address ? TransactionDirection.outgoing : TransactionDirection.incoming, blockTime: transactionModel.date, - txFee: transactionModel.fee, - tokenSymbol: tokenSymbol ?? "TRX", + fee: transactionModel.fee != null ? Money.fromInt(transactionModel.fee!, currency) : null, to: transactionModel.to, from: transactionModel.from, isPending: false, @@ -441,20 +439,19 @@ abstract class TronWalletBase final Map result = {}; - for (var transactionModel in transactions) { + for (final transactionModel in transactions) { if (transactionHistory.transactions.containsKey(transactionModel.hash)) { continue; } result[transactionModel.hash] = TronTransactionInfo( id: transactionModel.hash, - tronAmount: transactionModel.amount ?? BigInt.zero, + amount: Money(transactionModel.amount ?? BigInt.zero, transactionModel.currency), direction: transactionModel.from! == address ? TransactionDirection.outgoing : TransactionDirection.incoming, blockTime: transactionModel.date, - txFee: transactionModel.fee, - tokenSymbol: transactionModel.tokenSymbol ?? "TRX", + fee: transactionModel.fee != null ? Money.fromInt(transactionModel.fee!, currency) : null, to: transactionModel.to, from: transactionModel.from, isPending: false, @@ -514,7 +511,7 @@ abstract class TronWalletBase Future _fetchTronBalance() async { final balance = await _client.getBalance(_tronPublicKey.toAddress()); - return TronBalance(balance); + return TronBalance(Money(balance, CryptoCurrency.trx)); } Future _fetchTronTokenBalances() async { @@ -524,6 +521,7 @@ abstract class TronWalletBase balance[token] = await _client.fetchTronTokenBalances( _tronAddress, token.contractAddress, + currency: token, ); } else { balance.remove(token); @@ -542,8 +540,9 @@ abstract class TronWalletBase await _client.getBalance(_tronPublicKey.toAddress(), throwOnError: true); // Check USDT token balance - const usdtContractAddress = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"; - await _client.fetchTronTokenBalances(_tronAddress, usdtContractAddress, throwOnError: true); + final usdtContractAddress = DefaultTronTokens().usdt.contractAddress; + await _client.fetchTronTokenBalances(_tronAddress, usdtContractAddress, + throwOnError: true, currency: CryptoCurrency.usdttrc20); return true; } catch (e) { @@ -582,6 +581,7 @@ abstract class TronWalletBase balance[newToken] = await _client.fetchTronTokenBalances( _tronAddress, newToken.contractAddress, + currency: token ); } else { balance.remove(newToken); @@ -599,7 +599,8 @@ abstract class TronWalletBase } Future _removeTokenTransactionsInHistory(TronToken token) async { - transactionHistory.transactions.removeWhere((key, value) => value.tokenSymbol == token.title); + transactionHistory.transactions + .removeWhere((key, value) => value.amount.currency.symbol == token.title); await transactionHistory.save(); } @@ -649,10 +650,9 @@ abstract class TronWalletBase @override Future verifyMessage(String message, String signature, {String? address}) async { - if (address == null) { - return false; - } - TronPublicKey pubKey = TronPublicKey.fromPersonalSignature(ascii.encode(message), signature)!; + if (address == null) return false; + + final pubKey = TronPublicKey.fromPersonalSignature(ascii.encode(message), signature)!; return pubKey.toAddress().toString() == address; } diff --git a/cw_tron/pubspec.yaml b/cw_tron/pubspec.yaml index f406f3ac6..8e742aae2 100644 --- a/cw_tron/pubspec.yaml +++ b/cw_tron/pubspec.yaml @@ -33,7 +33,7 @@ dev_dependencies: flutter_lints: ^2.0.0 build_runner: ^2.4.15 mobx_codegen: ^2.1.1 - hive_generator: ^2.0.1 +# hive_generator: ^2.0.1 flutter: # assets: # - images/a_dot_burr.jpeg diff --git a/cw_wownero/lib/pending_wownero_transaction.dart b/cw_wownero/lib/pending_wownero_transaction.dart index b8aa7c4ed..1d1f55138 100644 --- a/cw_wownero/lib/pending_wownero_transaction.dart +++ b/cw_wownero/lib/pending_wownero_transaction.dart @@ -1,8 +1,7 @@ +import 'package:cw_core/amount/money.dart'; import 'package:cw_wownero/api/structs/pending_transaction.dart'; -import 'package:cw_wownero/api/transaction_history.dart' - as wownero_transaction_history; +import 'package:cw_wownero/api/transaction_history.dart' as wownero_transaction_history; import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/amount_converter.dart'; import 'package:cw_core/pending_transaction.dart'; @@ -19,6 +18,10 @@ class PendingWowneroTransaction with PendingTransaction { final PendingTransactionDescription pendingTransactionDescription; + Money get amount => Money.fromInt(pendingTransactionDescription.amount, CryptoCurrency.wow); + + Money get fee => Money.fromInt(pendingTransactionDescription.fee, CryptoCurrency.wow); + @override String get id => pendingTransactionDescription.hash; @@ -28,15 +31,7 @@ class PendingWowneroTransaction with PendingTransaction { String get txKey => pendingTransactionDescription.txKey; @override - String get amountFormatted => - AmountConverter.amountIntToString(CryptoCurrency.wow, pendingTransactionDescription.amount); - - @override - String get feeFormatted => "$feeFormattedValue WOW"; - - @override - String get feeFormattedValue => - AmountConverter.amountIntToString(CryptoCurrency.wow, pendingTransactionDescription.fee); + String get amountFormatted => amount.toString(); @override Future commit() async { diff --git a/cw_wownero/lib/wownero_balance.dart b/cw_wownero/lib/wownero_balance.dart new file mode 100644 index 000000000..53d717757 --- /dev/null +++ b/cw_wownero/lib/wownero_balance.dart @@ -0,0 +1,17 @@ +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/balance.dart'; +import 'package:cw_core/crypto_currency.dart'; + +class WowneroBalance extends Balance { + WowneroBalance({ + required this.fullBalance, + required Money unlockedBalance, + Money? frozen, + }) : super( + unlockedBalance, + fullBalance - unlockedBalance, + frozen: frozen ?? Money.zero(CryptoCurrency.wow), + ); + + final Money fullBalance; +} diff --git a/cw_wownero/lib/wownero_transaction_info.dart b/cw_wownero/lib/wownero_transaction_info.dart index db5345e5d..aafdc98e0 100644 --- a/cw_wownero/lib/wownero_transaction_info.dart +++ b/cw_wownero/lib/wownero_transaction_info.dart @@ -1,3 +1,5 @@ +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/wownero_amount_format.dart'; import 'package:cw_core/parseBoolFromString.dart'; @@ -21,18 +23,18 @@ class WowneroTransactionInfo extends TransactionInfo { date = DateTime.fromMillisecondsSinceEpoch( (int.tryParse(map['timestamp'] as String? ?? '') ?? 0) * 1000), isPending = parseBoolFromString(map['isPending'] as String), - amount = map['amount'] as int, + amount = Money.fromInt(map['amount'] as int, CryptoCurrency.wow), accountIndex = int.parse(map['accountIndex'] as String), addressIndex = map['addressIndex'] as int, confirmations = map['confirmations'] as int, key = getTxKey((map['hash'] ?? '') as String), - fee = map['fee'] as int? ?? 0 { - additionalInfo = { - 'key': key, - 'accountIndex': accountIndex, - 'addressIndex': addressIndex - }; - } + fee = Money.fromInt(map['fee'] as int? ?? 0, CryptoCurrency.wow) { + additionalInfo = { + 'key': key, + 'accountIndex': accountIndex, + 'addressIndex': addressIndex + }; + } final String id; final String txHash; @@ -41,23 +43,17 @@ class WowneroTransactionInfo extends TransactionInfo { final DateTime date; final int accountIndex; final bool isPending; - final int amount; - final int fee; + final Money amount; + final Money fee; final int addressIndex; final int confirmations; String? recipientAddress; String? key; String? _fiatAmount; - @override - String amountFormatted() => '${formatAmount(wowneroAmountToString(amount: amount))} WOW'; - @override String fiatAmount() => _fiatAmount ?? ''; @override void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); - - @override - String feeFormatted() => '${formatAmount(wowneroAmountToString(amount: fee))} WOW'; } diff --git a/cw_wownero/lib/wownero_wallet.dart b/cw_wownero/lib/wownero_wallet.dart index 08b7e6541..d1e0538d1 100644 --- a/cw_wownero/lib/wownero_wallet.dart +++ b/cw_wownero/lib/wownero_wallet.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'dart:isolate'; import 'package:cw_core/account.dart'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/monero_transaction_priority.dart'; import 'package:cw_core/monero_wallet_keys.dart'; @@ -20,7 +21,6 @@ import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wownero_amount_format.dart'; -import 'package:cw_core/wownero_balance.dart'; import 'package:cw_wownero/api/account_list.dart'; import 'package:cw_wownero/api/coins_info.dart'; import 'package:cw_wownero/api/structs/pending_transaction.dart'; @@ -31,6 +31,7 @@ import 'package:cw_wownero/api/wownero_output.dart'; import 'package:cw_wownero/exceptions/wownero_transaction_creation_exception.dart'; import 'package:cw_wownero/exceptions/wownero_transaction_no_inputs_exception.dart'; import 'package:cw_wownero/pending_wownero_transaction.dart'; +import 'package:cw_wownero/wownero_balance.dart'; import 'package:cw_wownero/wownero_transaction_creation_credentials.dart'; import 'package:cw_wownero/wownero_transaction_history.dart'; import 'package:cw_wownero/wownero_transaction_info.dart'; @@ -56,8 +57,11 @@ abstract class WowneroWalletBase {required WalletInfo walletInfo, required DerivationInfo derivationInfo, required Box unspentCoinsInfo, required String password}) : balance = ObservableMap.of({ CryptoCurrency.wow: WowneroBalance( - fullBalance: wownero_wallet.getFullBalance(accountIndex: 0), - unlockedBalance: wownero_wallet.getFullBalance(accountIndex: 0)) + fullBalance: + Money.fromInt(wownero_wallet.getFullBalance(accountIndex: 0), CryptoCurrency.wow), + unlockedBalance: + Money.fromInt(wownero_wallet.getFullBalance(accountIndex: 0), CryptoCurrency.wow), + ) }), _isTransactionUpdating = false, _hasSyncAfterStartup = false, @@ -75,8 +79,11 @@ abstract class WowneroWalletBase balance = ObservableMap.of({ currency: WowneroBalance( - fullBalance: wownero_wallet.getFullBalance(accountIndex: account.id), - unlockedBalance: wownero_wallet.getUnlockedBalance(accountIndex: account.id)) + fullBalance: Money.fromInt( + wownero_wallet.getFullBalance(accountIndex: account.id), CryptoCurrency.wow), + unlockedBalance: Money.fromInt( + wownero_wallet.getUnlockedBalance(accountIndex: account.id), CryptoCurrency.wow), + ) }); _updateSubAddress(isEnabledAutoGenerateSubaddress, account: account); _askForUpdateTransactionHistory(); @@ -152,9 +159,13 @@ abstract class WowneroWalletBase await walletAddresses.init(); balance = ObservableMap.of({ currency: WowneroBalance( - fullBalance: wownero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id), - unlockedBalance: - wownero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id)) + fullBalance: Money.fromInt( + wownero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id), + CryptoCurrency.wow), + unlockedBalance: Money.fromInt( + wownero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id), + CryptoCurrency.wow), + ) }); _setListeners(); await updateTransactions(); @@ -290,13 +301,12 @@ abstract class WowneroWalletBase final spendAllCoins = inputs.length == unspentCoins.length; if (hasMultiDestination) { - if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) { + if (outputs.any((item) => item.sendAll || item.cryptoAmount.amount <= BigInt.zero)) { throw WowneroTransactionCreationException( 'You do not have enough WOW to send this amount.'); } - final int totalAmount = - outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)); + final totalAmount = outputs.fold(0, (acc, value) => acc + value.cryptoAmount.amount.toInt()); final estimatedFee = calculateEstimatedFee(_credentials.priority, totalAmount); if (unlockedBalance < totalAmount) { @@ -311,8 +321,7 @@ abstract class WowneroWalletBase final wowneroOutputs = outputs.map((output) { final outputAddress = output.isParsedAddress ? output.extractedAddress : output.address; - return WowneroOutput( - address: outputAddress!, amount: output.cryptoAmount!.replaceAll(',', '.')); + return WowneroOutput(address: outputAddress!, amount: output.cryptoAmount.toString()); }).toList(); pendingTransactionDescription = await transaction_history.createTransactionMultDest( @@ -323,8 +332,8 @@ abstract class WowneroWalletBase } else { final output = outputs.first; final address = output.isParsedAddress ? output.extractedAddress : output.address; - final amount = output.sendAll ? null : output.cryptoAmount!.replaceAll(',', '.'); - final formattedAmount = output.sendAll ? null : output.formattedCryptoAmount; + final amount = output.sendAll ? null : output.cryptoAmount.toString(); + final formattedAmount = output.sendAll ? null : output.cryptoAmount.amount.toInt(); if ((formattedAmount != null && unlockedBalance < formattedAmount) || (formattedAmount == null && unlockedBalance <= 0)) { @@ -635,10 +644,10 @@ abstract class WowneroWalletBase row.isSpend ? TransactionDirection.outgoing : TransactionDirection.incoming, row.timeStamp, row.isPending, - row.amount, + Money.fromInt(row.amount, CryptoCurrency.wow), row.accountIndex, 0, - row.fee, + Money.fromInt(row.fee, CryptoCurrency.wow), row.confirmations, )..additionalInfo = { 'key': row.key, @@ -706,29 +715,31 @@ abstract class WowneroWalletBase final frozenBalance = _getFrozenBalance(); if (balance[currency]!.fullBalance != fullBalance || - balance[currency]!.unlockedBalance != unlockedBalance || - balance[currency]!.frozenBalance != frozenBalance) { + balance[currency]!.available != unlockedBalance || + balance[currency]!.frozen != frozenBalance) { balance[currency] = WowneroBalance( - fullBalance: fullBalance, unlockedBalance: unlockedBalance, frozenBalance: frozenBalance); + fullBalance: fullBalance, unlockedBalance: unlockedBalance, frozen: frozenBalance); } } Future _askForUpdateTransactionHistory() async => await updateTransactions(); - int _getFullBalance() => wownero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id); + Money _getFullBalance() => Money.fromInt( + wownero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id), CryptoCurrency.wow); - int _getUnlockedBalance() => - wownero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id); + Money _getUnlockedBalance() => Money.fromInt( + wownero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id), + CryptoCurrency.wow); - int _getFrozenBalance() { + Money _getFrozenBalance() { var frozenBalance = 0; - for (var coin in unspentCoinsInfo.values.where((element) => + for (final coin in unspentCoinsInfo.values.where((element) => element.walletId == id && element.accountIndex == walletAddresses.account!.id)) { if (coin.isFrozen) frozenBalance += coin.value; } - return frozenBalance; + return Money.fromInt(frozenBalance, CryptoCurrency.wow); } void _onNewBlock(int height, int blocksLeft, double ptc) async { diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock index 3c12da261..4b622b350 100644 --- a/cw_wownero/pubspec.lock +++ b/cw_wownero/pubspec.lock @@ -5,23 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" url: "https://pub.dev" source: hosted - version: "76.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.3" + version: "93.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "10.0.1" args: dependency: transitive description: @@ -76,18 +71,18 @@ packages: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "4.0.6" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.3.0" build_daemon: dependency: transitive description: @@ -96,30 +91,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" - build_resolvers: - dependency: "direct dev" - description: - name: build_resolvers - sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 - url: "https://pub.dev" - source: hosted - version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" url: "https://pub.dev" source: hosted - version: "2.4.15" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" - url: "https://pub.dev" - source: hosted - version: "8.0.0" + version: "2.15.0" built_collection: dependency: transitive description: @@ -132,10 +111,10 @@ packages: dependency: transitive description: name: built_value - sha256: "8b158ab94ec6913e480dc3f752418348b5ae099eb75868b5f4775f0572999c61" + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" url: "https://pub.dev" source: hosted - version: "8.9.4" + version: "8.12.6" cake_backup: dependency: transitive description: @@ -149,10 +128,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: @@ -169,14 +148,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" - code_builder: - dependency: transitive - description: - name: code_builder - sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" - url: "https://pub.dev" - source: hosted - version: "4.10.1" collection: dependency: transitive description: @@ -228,10 +199,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" + sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2" url: "https://pub.dev" source: hosted - version: "2.3.8" + version: "3.1.7" decimal: dependency: transitive description: @@ -298,14 +269,6 @@ packages: description: flutter source: sdk version: "0.0.0" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" glob: dependency: transitive description: @@ -339,18 +302,10 @@ packages: source: hosted version: "2.6.0" hive: - dependency: transitive - description: - name: hive - sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" - url: "https://pub.dev" - source: hosted - version: "2.2.3" - hive_generator: dependency: "direct dev" description: - name: hive_generator - sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" + name: hive + sha256: "5184b6ea455689754578267c31c3b0b329cccf20a5f2bfdab6941c29c3d3c09a" url: "https://pub.dev" source: hosted version: "2.0.1" @@ -414,26 +369,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" logging: dependency: transitive description: @@ -442,38 +397,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.dev" - source: hosted - version: "0.1.3-main.0" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -486,18 +433,18 @@ packages: dependency: "direct main" description: name: mobx - sha256: bf1a90e5bcfd2851fc6984e20eef69557c65d9e4d0a88f5be4cf72c9819ce6b0 + sha256: "25ea9a3ee69243d578fb6288721d8eeb18550d0391c32f896d377d994154bff2" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" mobx_codegen: dependency: "direct dev" description: name: mobx_codegen - sha256: e0abbbc651a69550440f6b65c99ec222a1e2a4afd7baec8ba0f3088c7ca582a8 + sha256: "6529bcae074838004b58ad01fbe4966be182a176aacfb723686985986ebac19f" url: "https://pub.dev" source: hosted - version: "2.7.1" + version: "2.7.7" monero: dependency: "direct main" description: @@ -711,18 +658,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 url: "https://pub.dev" source: hosted - version: "1.5.0" - source_helper: - dependency: transitive - description: - name: source_helper - sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" - url: "https://pub.dev" - source: hosted - version: "1.3.5" + version: "4.2.3" source_span: dependency: transitive description: @@ -847,18 +786,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.4" - timing: - dependency: transitive - description: - name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" - url: "https://pub.dev" - source: hosted - version: "1.0.1" + version: "0.7.10" torch_dart: dependency: transitive description: @@ -894,10 +825,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -955,5 +886,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.0-0 <4.0.0" + dart: ">=3.10.0 <4.0.0" flutter: ">=3.24.0" diff --git a/cw_wownero/pubspec.yaml b/cw_wownero/pubspec.yaml index ca9c70bea..6f9902452 100644 --- a/cw_wownero/pubspec.yaml +++ b/cw_wownero/pubspec.yaml @@ -14,8 +14,8 @@ dependencies: ffi: ^2.0.1 http: ^1.1.0 path_provider: ^2.0.11 - mobx: ^2.0.7+4 - flutter_mobx: ^2.0.6+1 + mobx: ^2.6.0 + flutter_mobx: ^2.3.0 intl: any encrypt: ^5.0.1 polyseed: ^0.0.7 @@ -31,10 +31,10 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - build_runner: ^2.4.15 - build_resolvers: ^2.4.4 - mobx_codegen: ^2.0.7 - hive_generator: ^2.0.1 + build_runner: ^2.15.0 + mobx_codegen: ^2.7.7 +# hive_generator: ^2.0.1 + hive: 2.0.1 dependency_overrides: watcher: ^1.1.0 diff --git a/cw_zano/lib/api/model/transfer.dart b/cw_zano/lib/api/model/transfer.dart index 594490a4f..68d3cc932 100644 --- a/cw_zano/lib/api/model/transfer.dart +++ b/cw_zano/lib/api/model/transfer.dart @@ -1,3 +1,5 @@ +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/zano_asset.dart'; import 'package:cw_zano/api/model/employed_entries.dart'; @@ -113,7 +115,8 @@ class Transfer { confirmations: currentDaemonHeight - transfer.height, isIncome: single.isIncome, assetId: single.assetId, - amount: single.amount, + amount: Money(single.amount, + asset ?? CryptoCurrency(title: ticker, name: ticker, decimals: decimalPoint)), tokenSymbol: isSimple ? ticker : '*${ticker}', decimalPoint: decimalPoint, ); @@ -124,7 +127,7 @@ class Transfer { confirmations: currentDaemonHeight - transfer.height, isIncome: single.isIncome, assetId: single.assetId, - amount: amount, + amount: Money(amount, CryptoCurrency.zano), tokenSymbol: isSimple ? 'ZANO' : '*ZANO', ); }, diff --git a/cw_zano/lib/model/pending_zano_transaction.dart b/cw_zano/lib/model/pending_zano_transaction.dart index 99706d3ac..6d1868f8d 100644 --- a/cw_zano/lib/model/pending_zano_transaction.dart +++ b/cw_zano/lib/model/pending_zano_transaction.dart @@ -1,7 +1,7 @@ +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_zano/api/model/destination.dart'; import 'package:cw_zano/api/model/transfer_result.dart'; -import 'package:cw_zano/zano_formatter.dart'; import 'package:cw_zano/zano_wallet.dart'; class PendingZanoTransaction with PendingTransaction { @@ -11,19 +11,19 @@ class PendingZanoTransaction with PendingTransaction { required this.fee, required this.comment, required this.assetId, - required this.ticker, - this.decimalPoint = ZanoFormatter.defaultDecimalPoint, required this.amount, }); final ZanoWalletBase zanoWallet; final List destinations; - final BigInt fee; final String comment; final String assetId; - final String ticker; - final int decimalPoint; - final BigInt amount; + + @override + final Money amount; + + @override + final Money fee; @override String get id => transferResult?.txHash ?? ''; @@ -32,24 +32,16 @@ class PendingZanoTransaction with PendingTransaction { String get hex => ''; @override - String get amountFormatted => ZanoFormatter.bigIntAmountToString(amount, decimalPoint); - - @override - String get feeFormatted => "$feeFormattedValue ZANO"; - - @override - String get feeFormattedValue => ZanoFormatter.bigIntAmountToString(fee); + String get amountFormatted => amount.toString(); TransferResult? transferResult; @override Future commit() async { - transferResult = await zanoWallet.transfer(destinations, fee, comment); + transferResult = await zanoWallet.transfer(destinations, fee.amount, comment); zanoWallet.fetchTransactions(); } @override - Future> commitUR() { - throw UnimplementedError(); - } + Future> commitUR() => throw UnimplementedError(); } diff --git a/cw_zano/lib/model/zano_balance.dart b/cw_zano/lib/model/zano_balance.dart index 0f1e5c81a..d6a3c8185 100644 --- a/cw_zano/lib/model/zano_balance.dart +++ b/cw_zano/lib/model/zano_balance.dart @@ -1,17 +1,15 @@ +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/balance.dart'; -import 'package:cw_zano/zano_formatter.dart'; +import 'package:cw_core/currency.dart'; class ZanoBalance extends Balance { - final BigInt total; - final BigInt unlocked; - final int decimalPoint; - ZanoBalance({required this.total, required this.unlocked, this.decimalPoint = ZanoFormatter.defaultDecimalPoint}) : super(unlocked, (total - unlocked)); + final Money total; + final Money unlocked; - ZanoBalance.empty({this.decimalPoint = ZanoFormatter.defaultDecimalPoint}): total = BigInt.zero, unlocked = BigInt.zero, super(BigInt.zero, BigInt.zero); + ZanoBalance({required this.total, required this.unlocked}) : super(unlocked, (total - unlocked)); - @override - String get formattedAdditionalBalance => ZanoFormatter.bigIntAmountToString(total - unlocked, decimalPoint); - - @override - String get formattedAvailableBalance => ZanoFormatter.bigIntAmountToString(unlocked, decimalPoint); + ZanoBalance.empty(Currency currency) + : total = Money.zero(currency), + unlocked = Money.zero(currency), + super(Money.zero(currency), Money.zero(currency)); } diff --git a/cw_zano/lib/model/zano_transaction_info.dart b/cw_zano/lib/model/zano_transaction_info.dart index d643e9207..08cb145fe 100644 --- a/cw_zano/lib/model/zano_transaction_info.dart +++ b/cw_zano/lib/model/zano_transaction_info.dart @@ -1,4 +1,5 @@ -import 'package:cw_core/format_amount.dart'; +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_zano/api/model/transfer.dart'; @@ -11,13 +12,13 @@ class ZanoTransactionInfo extends TransactionInfo { required this.direction, required this.date, required this.isPending, - required this.zanoAmount, + required this.amount, required this.fee, required this.confirmations, required this.tokenSymbol, required this.decimalPoint, required String assetId, - }) : amount = zanoAmount.isValidInt ? zanoAmount.toInt() : 0 { + }) { additionalInfo['assetId'] = assetId; } @@ -25,16 +26,15 @@ class ZanoTransactionInfo extends TransactionInfo { {required int confirmations, required bool isIncome, required String assetId, - required BigInt amount, + required Money amount, this.tokenSymbol = 'ZANO', this.decimalPoint = ZanoFormatter.defaultDecimalPoint}) : id = transfer.txHash, height = transfer.height, direction = isIncome ? TransactionDirection.incoming : TransactionDirection.outgoing, date = DateTime.fromMillisecondsSinceEpoch(transfer.timestamp * 1000), - zanoAmount = amount, - amount = amount.isValidInt ? amount.toInt() : 0, - fee = transfer.fee, + amount = amount, + fee = Money.fromInt(transfer.fee, CryptoCurrency.zano), confirmations = confirmations, isPending = confirmations < 10, recipientAddress = transfer.remoteAddresses.isNotEmpty @@ -54,28 +54,11 @@ class ZanoTransactionInfo extends TransactionInfo { final TransactionDirection direction; final DateTime date; final bool isPending; - final BigInt zanoAmount; - final int amount; - final int fee; + final Money amount; + final Money fee; final int confirmations; final int decimalPoint; late String recipientAddress; final String tokenSymbol; - String? _fiatAmount; String? key; - - @override - String amountFormatted() => - '${formatAmount(ZanoFormatter.bigIntAmountToString(zanoAmount, decimalPoint))} $tokenSymbol'; - - @override - String fiatAmount() => _fiatAmount ?? ''; - - @override - void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); - - @override - String feeFormatted() => '${formatAmount(ZanoFormatter.intAmountToString(fee))} $feeCurrency'; - - String get feeCurrency => 'ZANO'; } diff --git a/cw_zano/lib/zano_wallet.dart b/cw_zano/lib/zano_wallet.dart index 913bc083a..024d59b2b 100644 --- a/cw_zano/lib/zano_wallet.dart +++ b/cw_zano/lib/zano_wallet.dart @@ -3,6 +3,7 @@ import 'dart:core'; import 'dart:io'; import 'dart:math'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/node.dart'; @@ -26,7 +27,6 @@ import 'package:cw_zano/model/zano_transaction_creation_exception.dart'; import 'package:cw_zano/model/zano_transaction_credentials.dart'; import 'package:cw_zano/model/zano_transaction_info.dart'; import 'package:cw_zano/model/zano_wallet_keys.dart'; -import 'package:cw_zano/zano_formatter.dart'; import 'package:cw_zano/zano_transaction_history.dart'; import 'package:cw_zano/zano_wallet_addresses.dart'; import 'package:cw_zano/zano_wallet_api.dart'; @@ -106,7 +106,7 @@ abstract class ZanoWalletBase static final int _txChunkSize = (pow(2, 32) - 1).toInt(); ZanoWalletBase(WalletInfo walletInfo, DerivationInfo derivationInfo, String password) - : balance = ObservableMap.of({CryptoCurrency.zano: ZanoBalance.empty()}), + : balance = ObservableMap.of({CryptoCurrency.zano: ZanoBalance.empty(CryptoCurrency.zano)}), _isTransactionUpdating = false, _hasSyncAfterStartup = false, walletAddresses = ZanoWalletAddresses(walletInfo), @@ -199,8 +199,8 @@ abstract class ZanoWalletBase for (final item in result.wi.balances) { if (item.assetInfo.assetId == zanoAssetId) { balance[CryptoCurrency.zano] = ZanoBalance( - total: item.total, - unlocked: item.unlocked, + total: Money(item.total, CryptoCurrency.zano), + unlocked: Money(item.unlocked, CryptoCurrency.zano), ); } } @@ -232,24 +232,25 @@ abstract class ZanoWalletBase final isZano = credentials.currency == CryptoCurrency.zano; final outputs = credentials.outputs; final hasMultiDestination = outputs.length > 1; - final unlockedBalanceZano = balance[CryptoCurrency.zano]?.unlocked ?? BigInt.zero; - final unlockedBalanceCurrency = balance[credentials.currency]?.unlocked ?? BigInt.zero; - final fee = BigInt.from(calculateEstimatedFee(credentials.priority)); - late BigInt totalAmount; + final unlockedBalanceZano = balance[CryptoCurrency.zano]?.unlocked ?? Money.zero(CryptoCurrency.zano); + final unlockedBalanceCurrency = balance[credentials.currency]?.unlocked ?? Money.zero(credentials.currency); + final fee = Money(BigInt.from(calculateEstimatedFee(credentials.priority)), CryptoCurrency.zano); + + var totalAmount = Money.zero(credentials.currency); void checkForEnoughBalances() { if (isZano) { if (totalAmount + fee > unlockedBalanceZano) { throw ZanoTransactionCreationException( - "You don't have enough coins (required: ${ZanoFormatter.bigIntAmountToString(totalAmount + fee)} ZANO, unlocked ${ZanoFormatter.bigIntAmountToString(unlockedBalanceZano)} ZANO)."); + "You don't have enough coins (required: ${(totalAmount + fee).toStringWithSymbol()}, unlocked ${unlockedBalanceZano.toStringWithSymbol()})."); } } else { if (fee > unlockedBalanceZano) { throw ZanoTransactionCreationException( - "You don't have enough coins (required: ${ZanoFormatter.bigIntAmountToString(fee)} ZANO, unlocked ${ZanoFormatter.bigIntAmountToString(unlockedBalanceZano)} ZANO)."); + "You don't have enough coins (required: ${fee.toStringWithSymbol()}, unlocked ${unlockedBalanceZano.toStringWithSymbol()})."); } if (totalAmount > unlockedBalanceCurrency) { throw ZanoTransactionCreationException( - "You don't have enough coins (required: ${ZanoFormatter.bigIntAmountToString(totalAmount, credentials.currency.decimals)} ${credentials.currency.title}, unlocked ${ZanoFormatter.bigIntAmountToString(unlockedBalanceCurrency, credentials.currency.decimals)} ${credentials.currency.title})."); + "You don't have enough coins (required: ${totalAmount.toStringWithSymbol()}, unlocked ${unlockedBalanceCurrency.toStringWithSymbol()})."); } } } @@ -257,15 +258,15 @@ abstract class ZanoWalletBase final assetId = isZano ? zanoAssetId : (credentials.currency as ZanoAsset).assetId; late List destinations; if (hasMultiDestination) { - if (outputs.any((output) => output.sendAll || (output.formattedCryptoAmount ?? 0) <= 0)) { + if (outputs.any((output) => output.sendAll || output.cryptoAmount.amount <= BigInt.zero)) { throw ZanoTransactionCreationException("You don't have enough coins."); } - totalAmount = outputs.fold( - BigInt.zero, (acc, value) => acc + BigInt.from(value.formattedCryptoAmount ?? 0)); + totalAmount = + outputs.fold(Money.zero(credentials.currency), (acc, value) => acc + value.cryptoAmount); checkForEnoughBalances(); destinations = outputs .map((output) => Destination( - amount: BigInt.from(output.formattedCryptoAmount ?? 0), + amount: output.cryptoAmount.amount, address: output.isParsedAddress ? output.extractedAddress! : output.address, assetId: assetId, )) @@ -279,12 +280,12 @@ abstract class ZanoWalletBase totalAmount = unlockedBalanceCurrency; } } else { - totalAmount = BigInt.from(output.formattedCryptoAmount!); + totalAmount = output.cryptoAmount; } checkForEnoughBalances(); destinations = [ Destination( - amount: totalAmount, + amount: totalAmount.amount, address: output.isParsedAddress ? output.extractedAddress! : output.address, assetId: assetId, ) @@ -296,8 +297,6 @@ abstract class ZanoWalletBase fee: fee, comment: outputs.first.note ?? '', assetId: assetId, - ticker: credentials.currency.title, - decimalPoint: credentials.currency.decimals, amount: totalAmount, ); } @@ -414,7 +413,7 @@ abstract class ZanoWalletBase Future checkNodeHealth() async { try { final status = await getWalletStatus(); - + return status.isDaemonConnected; } catch (_) { return false; @@ -453,7 +452,7 @@ abstract class ZanoWalletBase enabled: true, ); zanoAssets[asset.assetId] = asset; - balance[asset] = ZanoBalance.empty(decimalPoint: asset.decimalPoint); + balance[asset] = ZanoBalance.empty(asset); return asset; } @@ -555,32 +554,38 @@ abstract class ZanoWalletBase // 2. set whitelists available in balances as 'enabled' ('disabled' by default) for (final b in walletInfo.wi.balances) { if (b.assetId == zanoAssetId) { - balance[CryptoCurrency.zano] = ZanoBalance(total: b.total, unlocked: b.unlocked); + balance[CryptoCurrency.zano] = ZanoBalance( + total: Money(b.total, CryptoCurrency.zano), + unlocked: Money(b.unlocked, CryptoCurrency.zano), + ); } else { final asset = zanoAssets[b.assetId]; if (asset == null) { printV('balance for an unknown asset ${b.assetInfo.assetId}'); continue; } - if (balance.keys.any( - (element) => element is ZanoAsset && element.assetId == b.assetInfo.assetId)) { - balance[balance.keys.firstWhere((element) => - element is ZanoAsset && element.assetId == b.assetInfo.assetId)] = - ZanoBalance( - total: b.total, unlocked: b.unlocked, decimalPoint: asset.decimalPoint); + + final assetBalanceKey = + balance.keys.where((e) => e is ZanoAsset && e.assetId == asset.assetId).firstOrNull; + if (assetBalanceKey != null) { + balance[assetBalanceKey] = ZanoBalance( + total: Money(b.total, assetBalanceKey), + unlocked: Money(b.unlocked, assetBalanceKey), + ); } else { balance[asset] = ZanoBalance( - total: b.total, unlocked: b.unlocked, decimalPoint: asset.decimalPoint); + total: Money(b.total, asset), + unlocked: Money(b.unlocked, asset), + ); } } } await updateTransactions(); // removing balances for assets missing in wallet info balances balance.removeWhere( - (key, _) => - key != CryptoCurrency.zano && - !walletInfo.wi.balances - .any((element) => element.assetId == (key as ZanoAsset).assetId), + (key, _) => + key != CryptoCurrency.zano && + !walletInfo.wi.balances.any((element) => element.assetId == (key as ZanoAsset).assetId), ); } } diff --git a/cw_zano/pubspec.lock b/cw_zano/pubspec.lock index 080704b93..c4602f532 100644 --- a/cw_zano/pubspec.lock +++ b/cw_zano/pubspec.lock @@ -5,23 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" url: "https://pub.dev" source: hosted - version: "76.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.3" + version: "93.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "10.0.1" args: dependency: transitive description: @@ -76,18 +71,18 @@ packages: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "4.0.6" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.3.0" build_daemon: dependency: transitive description: @@ -96,30 +91,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" - build_resolvers: - dependency: "direct dev" - description: - name: build_resolvers - sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 - url: "https://pub.dev" - source: hosted - version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" url: "https://pub.dev" source: hosted - version: "2.4.15" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" - url: "https://pub.dev" - source: hosted - version: "8.0.0" + version: "2.15.0" built_collection: dependency: transitive description: @@ -132,10 +111,10 @@ packages: dependency: transitive description: name: built_value - sha256: "8b158ab94ec6913e480dc3f752418348b5ae099eb75868b5f4775f0572999c61" + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" url: "https://pub.dev" source: hosted - version: "8.9.4" + version: "8.12.6" cake_backup: dependency: transitive description: @@ -149,10 +128,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: @@ -169,14 +148,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" - code_builder: - dependency: transitive - description: - name: code_builder - sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" - url: "https://pub.dev" - source: hosted - version: "4.10.1" collection: dependency: transitive description: @@ -228,10 +199,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" + sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2" url: "https://pub.dev" source: hosted - version: "2.3.8" + version: "3.1.7" decimal: dependency: "direct main" description: @@ -311,14 +282,6 @@ packages: url: "https://pub.dev" source: hosted version: "8.2.12" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" glob: dependency: transitive description: @@ -336,18 +299,10 @@ packages: source: hosted version: "2.3.2" hive: - dependency: transitive - description: - name: hive - sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" - url: "https://pub.dev" - source: hosted - version: "2.2.3" - hive_generator: dependency: "direct dev" description: - name: hive_generator - sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" + name: hive + sha256: "5184b6ea455689754578267c31c3b0b329cccf20a5f2bfdab6941c29c3d3c09a" url: "https://pub.dev" source: hosted version: "2.0.1" @@ -419,26 +374,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" logging: dependency: transitive description: @@ -447,38 +402,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.dev" - source: hosted - version: "0.1.3-main.0" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -491,18 +438,18 @@ packages: dependency: "direct main" description: name: mobx - sha256: bf1a90e5bcfd2851fc6984e20eef69557c65d9e4d0a88f5be4cf72c9819ce6b0 + sha256: "25ea9a3ee69243d578fb6288721d8eeb18550d0391c32f896d377d994154bff2" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" mobx_codegen: dependency: "direct dev" description: name: mobx_codegen - sha256: e0abbbc651a69550440f6b65c99ec222a1e2a4afd7baec8ba0f3088c7ca582a8 + sha256: "6529bcae074838004b58ad01fbe4966be182a176aacfb723686985986ebac19f" url: "https://pub.dev" source: hosted - version: "2.7.1" + version: "2.7.7" monero: dependency: "direct main" description: @@ -708,18 +655,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 url: "https://pub.dev" source: hosted - version: "1.5.0" - source_helper: - dependency: transitive - description: - name: source_helper - sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" - url: "https://pub.dev" - source: hosted - version: "1.3.5" + version: "4.2.3" source_span: dependency: transitive description: @@ -844,18 +783,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.4" - timing: - dependency: transitive - description: - name: timing - sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" - url: "https://pub.dev" - source: hosted - version: "1.0.2" + version: "0.7.10" torch_dart: dependency: transitive description: @@ -891,10 +822,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -952,5 +883,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.0-0 <4.0.0" + dart: ">=3.10.0 <4.0.0" flutter: ">=3.24.0" diff --git a/cw_zano/pubspec.yaml b/cw_zano/pubspec.yaml index 16585b715..7aa32af60 100644 --- a/cw_zano/pubspec.yaml +++ b/cw_zano/pubspec.yaml @@ -14,8 +14,8 @@ dependencies: ffi: ^2.0.1 http: ^1.1.0 path_provider: ^2.0.11 - mobx: ^2.1.4 - flutter_mobx: ^2.0.6+1 + mobx: ^2.6.0 + flutter_mobx: ^2.3.0 intl: any decimal: ^2.3.3 cw_core: @@ -30,10 +30,10 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - build_runner: ^2.4.15 - mobx_codegen: ^2.1.1 - build_resolvers: ^2.4.4 - hive_generator: ^2.0.1 + build_runner: ^2.15.0 + mobx_codegen: ^2.7.7 +# hive_generator: ^2.0.1 + hive: 2.0.1 dependency_overrides: watcher: ^1.1.0 diff --git a/cw_zcash/lib/src/pending_zcash_transaction.dart b/cw_zcash/lib/src/pending_zcash_transaction.dart index 28be24652..db4f59f7e 100644 --- a/cw_zcash/lib/src/pending_zcash_transaction.dart +++ b/cw_zcash/lib/src/pending_zcash_transaction.dart @@ -1,11 +1,11 @@ import 'dart:convert'; import 'dart:math'; +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/output_info.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_zcash/cw_zcash.dart'; -import 'package:cw_core/currency_for_wallet_type.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:cw_zcash/src/zcash_taddress_rotation.dart'; import 'package:warp_api/data_fb_generated.dart'; import 'package:warp_api/warp_api.dart'; @@ -23,7 +23,7 @@ class PendingZcashTransaction with PendingTransaction { final ZcashTransactionCredentials credentials; final String txPlan; String? _txId; - final int availableBalance; + final Money availableBalance; @override String get id => _txId ?? ''; @@ -32,31 +32,24 @@ class PendingZcashTransaction with PendingTransaction { String get hex => ''; @override - String get amountFormatted { - return walletTypeToCryptoCurrency(WalletType.zcash).formatAmount(BigInt.from(totalAmount)); - } + Money get amount => Money.fromInt(_totalAmount, CryptoCurrency.zec); - int get totalAmount { + @override + String get amountFormatted => amount.toString(); + + int get _totalAmount { final isAll = credentials.outputs.fold(false, (final a, final b) => a || (b.sendAll)); if (isAll) { - return availableBalance; + return availableBalance.amount.toInt(); } return credentials.outputs.fold( 0, - (final a, final b) => a + (b.formattedCryptoAmount ?? 0), + (final a, final b) => a + b.cryptoAmount.amount.toInt(), ); } @override - String get feeFormatted => - '$feeFormattedValue ${walletTypeToCryptoCurrency(WalletType.zcash).title}'; - - @override - late String feeFormattedValue = walletTypeToCryptoCurrency( - WalletType.zcash, - ).formatAmount(BigInt.from(fee)); - - int fee; + final Money fee; @override Future commit() async { @@ -73,7 +66,7 @@ class PendingZcashTransaction with PendingTransaction { txId: _txId, height: 0, timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - value: -totalAmount, + value: -_totalAmount, ).pack, ), ), @@ -83,7 +76,8 @@ class PendingZcashTransaction with PendingTransaction { _txId ?? '', credentials.outputs.reduce((final o1, final o2) { return OutputInfo( - address: o1.address + "," + o2.address, + cryptoAmount: Money.zero(CryptoCurrency.zec), + address: "${o1.address},${o2.address}", sendAll: false, isParsedAddress: false, ); @@ -94,9 +88,7 @@ class PendingZcashTransaction with PendingTransaction { } @override - Future> commitUR() { - throw UnimplementedError('UR not supported for Zcash'); - } + Future> commitUR() => throw UnimplementedError('UR not supported for Zcash'); @override bool shouldCommitUR() => false; diff --git a/cw_zcash/lib/src/zcash_balance.dart b/cw_zcash/lib/src/zcash_balance.dart index ab5ec84d2..c76864adc 100644 --- a/cw_zcash/lib/src/zcash_balance.dart +++ b/cw_zcash/lib/src/zcash_balance.dart @@ -1,32 +1,13 @@ +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/crypto_currency.dart'; class ZcashBalance extends Balance { - ZcashBalance({required this.confirmed, required this.unconfirmed, required final int frozen}) - : _frozen = frozen, - super.fromInt(confirmed, unconfirmed); + ZcashBalance(super.available, super.unavailable, {required super.frozen}); - factory ZcashBalance.zero() => ZcashBalance(confirmed: 0, unconfirmed: 0, frozen: 0); - - final int confirmed; - final int unconfirmed; - final int _frozen; - BigInt get frozen => BigInt.from(_frozen); - - @override - String get formattedAvailableBalance { - return CryptoCurrency.zec.formatAmount(BigInt.from(confirmed)); - } - - @override - String get formattedAdditionalBalance { - if (unconfirmed == 0) return '0.0'; - return CryptoCurrency.zec.formatAmount(BigInt.from(unconfirmed)); - } - - @override - String get formattedUnAvailableBalance { - if (frozen == 0) return ''; - return CryptoCurrency.zec.formatAmount(BigInt.from(_frozen)); - } + factory ZcashBalance.zero() => ZcashBalance( + Money.zero(CryptoCurrency.zec), + Money.zero(CryptoCurrency.zec), + frozen: Money.zero(CryptoCurrency.zec), + ); } diff --git a/cw_zcash/lib/src/zcash_transaction_info.dart b/cw_zcash/lib/src/zcash_transaction_info.dart index 617bd046b..d307cafa7 100644 --- a/cw_zcash/lib/src/zcash_transaction_info.dart +++ b/cw_zcash/lib/src/zcash_transaction_info.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'dart:io'; -import 'package:cw_core/currency_for_wallet_type.dart'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; @@ -14,8 +14,8 @@ import 'package:path/path.dart' as p; class ZcashTransactionInfo extends TransactionInfo { ZcashTransactionInfo({ required final String id, - required final int amount, - required final int fee, + required final Money amount, + required final Money fee, required final TransactionDirection direction, required final bool isPending, required final DateTime date, @@ -54,16 +54,6 @@ class ZcashTransactionInfo extends TransactionInfo { String? _fiatAmount; - @override - String amountFormatted() => - '${walletTypeToCryptoCurrency(WalletType.zcash).formatAmount(BigInt.from(amount))} ${walletTypeToCryptoCurrency(WalletType.zcash).title}'; - - @override - String? feeFormatted() { - if (fee == null || fee == 0) return null; - return '${walletTypeToCryptoCurrency(WalletType.zcash).formatAmount(BigInt.from(fee!))} ${walletTypeToCryptoCurrency(WalletType.zcash).title}'; - } - @override String fiatAmount() => _fiatAmount ?? ''; diff --git a/cw_zcash/lib/src/zcash_wallet.dart b/cw_zcash/lib/src/zcash_wallet.dart index 0ade54d54..d255ae7fb 100644 --- a/cw_zcash/lib/src/zcash_wallet.dart +++ b/cw_zcash/lib/src/zcash_wallet.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/monero_transaction_priority.dart'; import 'package:cw_core/node.dart'; @@ -56,7 +57,7 @@ abstract class ZcashWalletBase @override ObservableMap balance = ObservableMap.of({ - CryptoCurrency.zec: ZcashBalance(confirmed: 0, unconfirmed: 0, frozen: 0), + CryptoCurrency.zec: ZcashBalance.zero(), }); static int internalCalculateEstimatedFee(final TransactionPriority priority, final int? amount) { @@ -135,33 +136,21 @@ abstract class ZcashWalletBase await updateBalance(); final zcashBalance = balance[CryptoCurrency.zec]; - final availableBalance = zcashBalance?.confirmed ?? 0; + final availableBalance = zcashBalance?.available ?? Money.zero(currency); final recipients = []; - int totalAmount = 0; + var totalAmount = Money.zero(currency); for (final output in creds.outputs) { - int amount; + Money amount; if (output.sendAll) { amount = availableBalance; } else { - amount = output.formattedCryptoAmount ?? 0; + amount = output.cryptoAmount; - if (amount == 0 && output.cryptoAmount != null && output.cryptoAmount!.isNotEmpty) { - try { - final parsedAmount = CryptoCurrency.zec.parseAmount( - output.cryptoAmount!.replaceAll(',', '.'), - ); - amount = parsedAmount.toInt(); - printV("Parsed amount from cryptoAmount '${output.cryptoAmount}': $amount"); - } catch (e) { - printV("Failed to parse cryptoAmount '${output.cryptoAmount}': $e"); - } - } - - if (amount <= 0) { + if (amount <= Money.zero(currency)) { throw Exception( - 'Invalid amount for output. Amount: ${output.cryptoAmount}, Formatted: ${output.formattedCryptoAmount}', + 'Invalid amount for output. Amount: ${output.cryptoAmount}, Formatted: ${output.cryptoAmount}', ); } } @@ -200,7 +189,7 @@ abstract class ZcashWalletBase final builder = RecipientObjectBuilder( address: address, pools: recipientPools, - amount: amount, + amount: amount.amount.toInt(), feeIncluded: output.sendAll, replyTo: false, memo: memo.isNotEmpty ? memo : null, @@ -240,7 +229,7 @@ abstract class ZcashWalletBase zcashWallet: this as ZcashWallet, credentials: creds, txPlan: txPlan, - fee: internalCalculateEstimatedFee(creds.priority, null), + fee: Money.fromInt(internalCalculateEstimatedFee(creds.priority, null), currency), availableBalance: availableBalance, ); } @@ -307,8 +296,8 @@ abstract class ZcashWalletBase final txInfo = ZcashTransactionInfo( id: txId.trim().replaceAll('"', ''), - amount: tx.value.abs(), - fee: 0, + amount: Money.fromInt(tx.value.abs(), currency), + fee: Money.zero(currency), direction: direction, isPending: tx.height == 0, date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), @@ -807,9 +796,9 @@ abstract class ZcashWalletBase unawaited(_autoShield()); balance[CryptoCurrency.zec] = ZcashBalance( - confirmed: confirmedSpendable, - unconfirmed: spendable - confirmedSpendable, - frozen: 0, + Money.fromInt(confirmedSpendable, currency), + Money.fromInt(spendable - confirmedSpendable, currency), + frozen: Money.zero(currency), ); } catch (e, stackTrace) { printV("Balance update error: $e"); @@ -1158,13 +1147,13 @@ abstract class ZcashWalletBase } _didRunRescanInternalChange = true; final bal = - balance[CryptoCurrency.zec]!.confirmed + - balance[CryptoCurrency.zec]!.unconfirmed + - balance[CryptoCurrency.zec]!.frozen.toInt(); + balance[CryptoCurrency.zec]!.available + + balance[CryptoCurrency.zec]!.unavailable + + (balance[CryptoCurrency.zec]!.frozen ?? Money.zero(CryptoCurrency.zec)); final osCacheDir = await getApplicationCacheDirectory(); final cacheDir = osCacheDir.createTempSync("zkool-import"); zkoolSweep = ZkoolSweep( - currentBalance: bal, + currentBalance: bal.amount.toInt(), cacheDir: cacheDir.path, seed: seed ?? '', passphrase: password, diff --git a/cw_zcash/pubspec.yaml b/cw_zcash/pubspec.yaml index 835981eb2..72e6de2be 100644 --- a/cw_zcash/pubspec.yaml +++ b/cw_zcash/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: path: ../scripts/zcash_lib/packages/warp_api_ffi cw_core: path: ../cw_core - mobx: ^2.5.0 + mobx: ^2.6.0 bip39: ^1.0.6 mutex: ^3.1.0 @@ -31,8 +31,7 @@ dependency_overrides: dev_dependencies: flutter_test: sdk: flutter - build_runner: ^2.4.15 - build_resolvers: ^2.4.4 - mobx_codegen: ^2.0.7 - hive_generator: ^2.0.1 - ffigen: ^16.1.0 + build_runner: ^2.15.0 + mobx_codegen: ^2.7.7 +# hive_generator: ^2.0.1 + ffigen: ^20.1.1 diff --git a/docs/ADDING_NEW_WALLET_TYPES.md b/docs/ADDING_NEW_WALLET_TYPES.md index 91a69b9f8..4beecba8a 100644 --- a/docs/ADDING_NEW_WALLET_TYPES.md +++ b/docs/ADDING_NEW_WALLET_TYPES.md @@ -121,7 +121,6 @@ cd cw_walletx && flutter pub get && dart run build_runner build --delete-conflic dev_dependencies: build_runner: ^2.0.0 mobx_codegen: ^2.0.0 - hive_generator: ^2.0.0 ``` --- diff --git a/docs/builds/ANDROID.md b/docs/builds/ANDROID.md index 4964cfec6..f74fb4d0a 100644 --- a/docs/builds/ANDROID.md +++ b/docs/builds/ANDROID.md @@ -18,8 +18,8 @@ In order to build the latest version of Cake Wallet, simply run the following: git clone --branch main https://github.com/cake-tech/cake_wallet.git # NOTE: Replace `main` with the latest release tag available at https://github.com/cake-tech/cake_wallet/releases/latest. cd cake_wallet -# docker build -t ghcr.io/cake-tech/cake_wallet:debian13-flutter3.32.0-ndkr28-go1.24.1-ruststablenightly . # Uncomment to build the docker image yourself instead of pulling it from the registry -docker run -v$(pwd):$(pwd) -w $(pwd) -i --rm ghcr.io/cake-tech/cake_wallet:debian13-flutter3.32.0-ndkr28-go1.24.1-ruststablenightly bash -x << EOF +# docker build -t ghcr.io/cake-tech/cake_wallet:debian13-flutter3.41.9-ndkr28-go1.24.1-ruststablenightly . # Uncomment to build the docker image yourself instead of pulling it from the registry +docker run -v$(pwd):$(pwd) -w $(pwd) -i --rm ghcr.io/cake-tech/cake_wallet:debian13-flutter3.41.9-ndkr28-go1.24.1-ruststablenightly bash -x << EOF set -x -e git config --global --add safe.directory '*' pushd scripts/android diff --git a/docs/builds/LINUX.md b/docs/builds/LINUX.md index 27bce5f14..13eb05551 100644 --- a/docs/builds/LINUX.md +++ b/docs/builds/LINUX.md @@ -20,8 +20,8 @@ In order to build the latest version of Cake Wallet, simply run the following: git clone --branch main https://github.com/cake-tech/cake_wallet.git # NOTE: Replace `main` with the latest release tag available at https://github.com/cake-tech/cake_wallet/releases/latest. cd cake_wallet -# docker build -t ghcr.io/cake-tech/cake_wallet:debian13-flutter3.32.0-ndkr28-go1.24.1-ruststablenightly . # Uncomment to build the docker image yourself instead of pulling it from the registry -docker run --privileged -v$(pwd):$(pwd) -w $(pwd) -i --rm ghcr.io/cake-tech/cake_wallet:debian13-flutter3.32.0-ndkr28-go1.24.1-ruststablenightly bash -x << EOF +# docker build -t ghcr.io/cake-tech/cake_wallet:debian13-flutter3.41.9-ndkr28-go1.24.1-ruststablenightly . # Uncomment to build the docker image yourself instead of pulling it from the registry +docker run --privileged -v$(pwd):$(pwd) -w $(pwd) -i --rm ghcr.io/cake-tech/cake_wallet:debian13-flutter3.41.9-ndkr28-go1.24.1-ruststablenightly bash -x << EOF set -x -e git config --global --add safe.directory '*' pushd scripts diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d683b7c17..29a2ad974 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -85,9 +85,6 @@ PODS: - OrderedSet (6.0.3) - package_info_plus (0.4.5): - Flutter - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - payjoin_flutter (0.23.0) - permission_handler_apple (9.4.8): - Flutter @@ -182,7 +179,6 @@ DEPENDENCIES: - integration_test (from `.symlinks/plugins/integration_test/ios`) - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - payjoin_flutter (from `.symlinks/plugins/payjoin_flutter/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - quick_actions_ios (from `.symlinks/plugins/quick_actions_ios/ios`) @@ -262,8 +258,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/local_auth_darwin/darwin" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" - path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/darwin" payjoin_flutter: :path: ".symlinks/plugins/payjoin_flutter/ios" permission_handler_apple: @@ -306,31 +300,30 @@ SPEC CHECKSUMS: breez_sdk_spark_flutter: ed3b6709b8ce9b40309d8728640eade5b19e8a0d connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd CryptoSwift: e64e11850ede528a02a0f3e768cec8e9d92ecb90 - cw_decred: a02cf30175a46971c1e2fa22c48407534541edc6 - cw_mweb: 3aea2fb35b2bd04d8b2d21b83216f3b8fb768d85 - device_display_brightness: 04374ebd653619292c1d996f00f42877ea19f17f - device_info_plus: 335f3ce08d2e174b9fdc3db3db0f4e3b1f66bd89 - devicelocale: bd64aa714485a8afdaded0892c1e7d5b7f680cf8 + cw_decred: 9c0e1df74745b51a1289ec5e91fb9e24b68fa14a + cw_mweb: 22cd01dfb8ad2d39b15332006f22046aaa8352a3 + device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7 + device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 + devicelocale: 35ba84dc7f45f527c3001535d8c8d104edd5d926 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 dnssec_proof: d461cac7bd3301eb7447f87936745a0c1ae0a67e - fast_scanner: 2cb1ad3e69e645e9980fb4961396ce5804caa3e3 - file_picker: 9b3292d7c8bc68c8a7bf8eb78f730e49c8efc517 - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 - flutter_local_authentication: 989278c681612f1ee0e36019e149137f114b9d7f - flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb - flutter_mailer: 3a8cd4f36c960fb04528d5471097270c19fec1c4 - flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf - flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418 - fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 - image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a - in_app_review: 7dd1ea365263f834b8464673f9df72c80c17c937 - integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e - local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19 + fast_scanner: 44c00940355a51258cd6c2085734193cd23d95bc + file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 + flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb + flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f + flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83 + flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 + flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be + fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f + image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b + in_app_review: 436034b18594851a7328d7f1c2ed5ec235b79cfc + integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 + local_auth_darwin: 63c73d6d28cc3e239be2b6aa460ea6e317cd5100 OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 - package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 payjoin_flutter: d9d4c8aa16bd5dfedb9b21d0edc8199e0187d96e permission_handler_apple: 92d754bbaa7361d436db2d6c3c1c2a0fdcec462e quick_actions_ios: 4b07fb49d8d8f3518d7565fbb7a91014067a7d82 @@ -338,13 +331,13 @@ SPEC CHECKSUMS: rive_common: dd421daaf9ae69f0125aa761dd96abd278399952 rlz: 3f4022361f540a5c26d9d2474fea1d2455b69d8d SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a - sensitive_clipboard: 161e9abc3d56b3131309d8a321eb4690a803c16b - share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 - sp_scanner: b1bc9321690980bdb44bba7ec85d5543e716d1b5 - sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986 + share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f + shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 + sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12 + sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b - sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1 + sqlite3_flutter_libs: 86f82662868ee26ff3451f73cac9c5fc2a1f57fa SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 torch_dart: f4620705d10f05492fab047f2fa1c3a600e7d17d uni_links: ed8c961e47ed9ce42b6d91e1de8049e38a4b3152 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 6d177539a..2c2584e8e 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -483,7 +483,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -582,7 +582,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -632,7 +632,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 853372825..691c85409 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -2,7 +2,7 @@ import UIKit import Flutter @main -@objc class AppDelegate: FlutterAppDelegate { +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? @@ -11,101 +11,120 @@ import Flutter UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate } - makeSecure() - - let controller : FlutterViewController = window?.rootViewController as! FlutterViewController - let legacyMigrationChannel = FlutterMethodChannel( - name: "com.cakewallet.cakewallet/legacy_wallet_migration", - binaryMessenger: controller.binaryMessenger) - legacyMigrationChannel.setMethodCallHandler({ - (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in - - switch call.method { - case "decrypt": - guard let args = call.arguments as? Dictionary, - let data = args["bytes"] as? FlutterStandardTypedData, - let key = args["key"] as? String, - let salt = args["salt"] as? String else { - result(nil) - return - } - - let content = decrypt(data: data.data, key: key, salt: salt) - result(content) - case "read_user_defaults": - guard let args = call.arguments as? Dictionary, - let key = args["key"] as? String, - let type = args["type"] as? String else { - result(nil) - return - } - - var value: Any? - - switch (type) { - case "string": - value = UserDefaults.standard.string(forKey: key) - case "int": - value = UserDefaults.standard.integer(forKey: key) - case "bool": - value = UserDefaults.standard.bool(forKey: key) - default: - break - } - - result(value) - default: - result(FlutterMethodNotImplemented) - } - }) - - let utilsChannel = FlutterMethodChannel( - name: "com.cake_wallet/native_utils", - binaryMessenger: controller.binaryMessenger) - utilsChannel.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in - switch call.method { - case "sec_random": - guard let args = call.arguments as? Dictionary, - let count = args["count"] as? Int else { - result(nil) - return - } + DispatchQueue.main.async { [weak self] in + self?.makeSecure() + } - result(secRandom(count: count)) - - case "setIsAppSecure": - guard let args = call.arguments as? Dictionary, - let isAppSecure = args["isAppSecure"] else { - result(nil) - return - } - if isAppSecure { - self?.textField.isSecureTextEntry = true - } else { - self?.textField.isSecureTextEntry = false - } - - result(nil) - default: - result(FlutterMethodNotImplemented) - } - }) - - GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + + let messenger = engineBridge.applicationRegistrar.messenger() + + let legacyMigrationChannel = FlutterMethodChannel( + name: "com.cakewallet.cakewallet/legacy_wallet_migration", + binaryMessenger: messenger) + legacyMigrationChannel.setMethodCallHandler({ + (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in + + switch call.method { + case "decrypt": + guard let args = call.arguments as? Dictionary, + let data = args["bytes"] as? FlutterStandardTypedData, + let key = args["key"] as? String, + let salt = args["salt"] as? String else { + result(nil) + return + } + + let content = decrypt(data: data.data, key: key, salt: salt) + result(content) + case "read_user_defaults": + guard let args = call.arguments as? Dictionary, + let key = args["key"] as? String, + let type = args["type"] as? String else { + result(nil) + return + } + + var value: Any? + + switch (type) { + case "string": + value = UserDefaults.standard.string(forKey: key) + case "int": + value = UserDefaults.standard.integer(forKey: key) + case "bool": + value = UserDefaults.standard.bool(forKey: key) + default: + break + } + + result(value) + default: + result(FlutterMethodNotImplemented) + } + }) + + let utilsChannel = FlutterMethodChannel( + name: "com.cake_wallet/native_utils", + binaryMessenger: messenger) + utilsChannel.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in + switch call.method { + case "sec_random": + guard let args = call.arguments as? Dictionary, + let count = args["count"] as? Int else { + result(nil) + return + } + + result(secRandom(count: count)) + + case "setIsAppSecure": + guard let args = call.arguments as? Dictionary, + let isAppSecure = args["isAppSecure"] else { + result(nil) + return + } + + if isAppSecure { + self?.textField.isSecureTextEntry = true + } else { + self?.textField.isSecureTextEntry = false + } + + result(nil) + default: + result(FlutterMethodNotImplemented) + } + }) + } private var textField = UITextField() - private func makeSecure() { - if (!self.window.subviews.contains(textField)) { - let view = UIView(frame: CGRect(x: 0, y: 0, width: textField.frame.self.width, height: textField.frame.self.height)) - self.window.addSubview(textField) - self.window.layer.superlayer?.addSublayer(textField.layer) - textField.layer.sublayers?.last!.addSublayer(self.window.layer) - textField.leftView = view - textField.leftViewMode = .always - } +private func keyWindow() -> UIWindow? { + UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .flatMap { $0.windows } + .first { $0.isKeyWindow } +} + +private func makeSecure() { + guard let window = self.window ?? keyWindow() else { return } + + if (!window.subviews.contains(textField)) { + let view = UIView(frame: CGRect(x: 0, y: 0, width: textField.frame.self.width, height: textField.frame.self.height)) + window.addSubview(textField) + window.layer.superlayer?.addSublayer(textField.layer) + + textField.layer.sublayers?.last?.addSublayer(window.layer) + textField.leftView = view + textField.leftViewMode = .always } +} + } \ No newline at end of file diff --git a/ios/Runner/InfoBase.plist b/ios/Runner/InfoBase.plist index d82fec087..ff0f43b61 100644 --- a/ios/Runner/InfoBase.plist +++ b/ios/Runner/InfoBase.plist @@ -449,5 +449,28 @@ FlutterDeepLinkingEnabled + ITSAppUsesNonExemptEncryption + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/lib/anonpay/anonpay_invoice_info.dart b/lib/anonpay/anonpay_invoice_info.dart index bd6776d00..d770f7384 100644 --- a/lib/anonpay/anonpay_invoice_info.dart +++ b/lib/anonpay/anonpay_invoice_info.dart @@ -3,37 +3,37 @@ import 'package:cw_core/hive_type_ids.dart'; import 'package:cw_core/keyable.dart'; import 'package:hive/hive.dart'; -part 'anonpay_invoice_info.g.dart'; +part 'anonpay_invoice_info.part.dart'; -@HiveType(typeId: AnonpayInvoiceInfo.typeId) +// @HiveType(typeId: AnonpayInvoiceInfo.typeId) class AnonpayInvoiceInfo extends HiveObject with Keyable implements AnonpayInfoBase { - @HiveField(0) + // @HiveField(0) final String invoiceId; - @HiveField(1) + // @HiveField(1) String status; - @HiveField(2) + // @HiveField(2) final double? fiatAmount; - @HiveField(3) + // @HiveField(3) final String? fiatEquiv; - @HiveField(4) + // @HiveField(4) final double? amountTo; - @HiveField(5) + // @HiveField(5) final String coinTo; - @HiveField(6) + // @HiveField(6) final String address; - @HiveField(7) + // @HiveField(7) final String clearnetUrl; - @HiveField(8) + // @HiveField(8) final String onionUrl; - @HiveField(9) + // @HiveField(9) final String clearnetStatusUrl; - @HiveField(10) + // @HiveField(10) final String onionStatusUrl; - @HiveField(11) + // @HiveField(11) final DateTime createdAt; - @HiveField(12) + // @HiveField(12) final String walletId; - @HiveField(13) + // @HiveField(13) final String provider; static const typeId = ANONPAY_INVOICE_INFO_TYPE_ID; diff --git a/lib/anonpay/anonpay_invoice_info.part.dart b/lib/anonpay/anonpay_invoice_info.part.dart new file mode 100644 index 000000000..7eba138c3 --- /dev/null +++ b/lib/anonpay/anonpay_invoice_info.part.dart @@ -0,0 +1,80 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'anonpay_invoice_info.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class AnonpayInvoiceInfoAdapter extends TypeAdapter { + @override + final int typeId = 10; + + @override + AnonpayInvoiceInfo read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return AnonpayInvoiceInfo( + invoiceId: fields[0] as String, + clearnetUrl: fields[7] as String, + onionUrl: fields[8] as String, + clearnetStatusUrl: fields[9] as String, + onionStatusUrl: fields[10] as String, + status: fields[1] as String, + fiatAmount: fields[2] as double?, + fiatEquiv: fields[3] as String?, + amountTo: fields[4] as double?, + coinTo: fields[5] as String, + address: fields[6] as String, + createdAt: fields[11] as DateTime, + walletId: fields[12] as String, + provider: fields[13] as String, + ); + } + + @override + void write(BinaryWriter writer, AnonpayInvoiceInfo obj) { + writer + ..writeByte(14) + ..writeByte(0) + ..write(obj.invoiceId) + ..writeByte(1) + ..write(obj.status) + ..writeByte(2) + ..write(obj.fiatAmount) + ..writeByte(3) + ..write(obj.fiatEquiv) + ..writeByte(4) + ..write(obj.amountTo) + ..writeByte(5) + ..write(obj.coinTo) + ..writeByte(6) + ..write(obj.address) + ..writeByte(7) + ..write(obj.clearnetUrl) + ..writeByte(8) + ..write(obj.onionUrl) + ..writeByte(9) + ..write(obj.clearnetStatusUrl) + ..writeByte(10) + ..write(obj.onionStatusUrl) + ..writeByte(11) + ..write(obj.createdAt) + ..writeByte(12) + ..write(obj.walletId) + ..writeByte(13) + ..write(obj.provider); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is AnonpayInvoiceInfoAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 6f5b9bfa4..a5d118322 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -156,13 +156,12 @@ class CWBitcoin extends Bitcoin { outputs .map((out) => OutputInfo( fiatAmount: out.fiatAmount, - cryptoAmount: out.cryptoAmount, + cryptoAmount: out.cryptoAmountMoney, address: out.address, note: out.note, sendAll: out.sendAll, extractedAddress: out.extractedAddress, isParsedAddress: out.isParsedAddress, - formattedCryptoAmount: out.formattedCryptoAmount, memo: out.memo.isNotEmpty ? out.memo : null, extra: out.extra, )) @@ -191,7 +190,7 @@ class CWBitcoin extends Bitcoin { } @override - Future estimateFakeSendAllTxAmount(Object wallet, TransactionPriority priority, + Future estimateFakeSendAllTxAmount(WalletBase wallet, TransactionPriority priority, {UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) async { try { final sk = ECPrivate.random(); @@ -233,7 +232,7 @@ class CWBitcoin extends Bitcoin { return estimatedTx.amount; } catch (_) { - return 0; + return Money.zero(wallet.currency); } } @@ -247,10 +246,6 @@ class CWBitcoin extends Bitcoin { String formatterBitcoinAmountToString({required int amount}) => bitcoinAmountToString(amount: amount); - @override - double formatterBitcoinAmountToDouble({required int amount}) => - bitcoinAmountToDouble(amount: amount); - @override int formatterStringDoubleToBitcoinAmount(String amount) => stringDoubleToBitcoinAmount(amount); diff --git a/lib/cake_pay/src/cards/cake_pay_buy_card_page.dart b/lib/cake_pay/src/cards/cake_pay_buy_card_page.dart index 78fa71d49..5442dcff1 100644 --- a/lib/cake_pay/src/cards/cake_pay_buy_card_page.dart +++ b/lib/cake_pay/src/cards/cake_pay_buy_card_page.dart @@ -22,7 +22,6 @@ import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.da import 'package:cake_wallet/src/widgets/bottom_sheet/cake_pay_transaction_sent_bottom_sheet.dart'; import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart'; import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart'; -import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; import 'package:cake_wallet/typography.dart'; @@ -36,7 +35,6 @@ import 'package:cw_core/unspent_coin_type.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:mobx/mobx.dart'; class CakePayBuyCardPage extends BasePage { @@ -612,16 +610,13 @@ class CakePayBuyCardPage extends BasePage { titleIconPath: _sendViewModel.selectedCryptoCurrency.iconPath, currency: _sendViewModel.selectedCryptoCurrency, amount: S.of(bottomSheetContext).send_amount, - amountValue: _sendViewModel.amountParsingProxy.getDisplayCryptoAmount( - _sendViewModel.pendingTransaction!.amountFormatted, - _sendViewModel.selectedCryptoCurrency), + amountValue: _sendViewModel.amountParsingProxy.asDisplayStringWithSymbol( + _sendViewModel.pendingTransaction!.amount), quantity: 'QTY: ${cakePayBuyCardViewModel.quantity}', fiatAmountValue: _sendViewModel.pendingTransactionFiatAmountFormatted, fee: S.of(bottomSheetContext).send_fee, - feeValue: "${_sendViewModel.amountParsingProxy.getDisplayCryptoAmount( - _sendViewModel.pendingTransaction!.feeFormattedValue, - _sendViewModel.selectedCryptoCurrency)} ${_sendViewModel.amountParsingProxy - .getCryptoSymbol(_sendViewModel.wallet.currency)}", + feeValue: _sendViewModel.amountParsingProxy.asDisplayStringWithSymbol( + _sendViewModel.pendingTransaction!.fee), feeFiatAmount: _sendViewModel.pendingTransactionFeeFiatAmountFormatted, outputs: displayingOutputs, footerType: FooterType.slideActionButton, @@ -699,13 +694,12 @@ class CakePayBuyCardPage extends BasePage { child: Icon(Icons.check, size: 16, color: Colors.white), ), output: displayingOutputs.first, - currency: _sendViewModel.selectedCryptoCurrency, amount: S.of(sheetCtx).send_amount, - amountValue: _sendViewModel.pendingTransaction!.amountFormatted, + amountValue: _sendViewModel.pendingTransaction!.amount.toStringWithSymbol(), quantity: 'QTY: ${cakePayBuyCardViewModel.quantity}', fiatAmountValue: _sendViewModel.pendingTransactionFiatAmountFormatted, fee: S.of(sheetCtx).send_fee, - feeValue: _sendViewModel.pendingTransaction!.feeFormatted, + feeValue: _sendViewModel.pendingTransaction!.fee.toStringWithSymbol(), feeFiatAmount: _sendViewModel.pendingTransactionFeeFiatAmountFormatted, paymentId: 'Order ID', paymentIdValue: order?.orderId ?? '', diff --git a/lib/core/amount_parsing_proxy.dart b/lib/core/amount_parsing_proxy.dart index 4b941129e..5a7e41789 100644 --- a/lib/core/amount_parsing_proxy.dart +++ b/lib/core/amount_parsing_proxy.dart @@ -1,7 +1,9 @@ import 'package:cake_wallet/entities/bitcoin_amount_display_mode.dart'; import 'package:cake_wallet/src/screens/wallet_connect/utils/string_parsing.dart'; +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/crypto_amount_format.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; class AmountParsingProxy { @@ -20,70 +22,54 @@ class AmountParsingProxy { /// [getCryptoOutputAmount] turns the input [amount] into the preferred representation of [cryptoCurrency] String getDisplayCryptoAmount(String amount, CryptoCurrency cryptoCurrency) { - try { if (useSatoshi(cryptoCurrency) && amount.isNotEmpty) { - return cryptoCurrency.parseAmount(amount.withMaxDecimals(cryptoCurrency.decimals)).toString(); + return cryptoCurrency + .parseAmount(amount.withMaxDecimals(cryptoCurrency.decimals)) + .toStringWithPrecision(useBaseUnit: true); } return amount.withMaxDecimals(cryptoCurrency.decimals); - - } catch(_) { - printV("failed to parse amount $amount for currency ${cryptoCurrency.title}, falling back to showing unparsed"); + } catch (_) { + printV( + "failed to parse amount $amount for currency ${cryptoCurrency.title}, falling back to showing unparsed"); return amount; } - } - /// [getCryptoStringRepresentation] turns the input [amount] into the preferred representation of [cryptoCurrency] - String getDisplayCryptoString(int amount, CryptoCurrency cryptoCurrency) { - if (useSatoshi(cryptoCurrency)) { - return "$amount"; - } + String getDisplayCryptoString(int amount, CryptoCurrency cryptoCurrency) => + asDisplayString(Money.fromInt(amount, cryptoCurrency)); - return cryptoCurrency.formatAmount(BigInt.from(amount)); - } + /// [getDisplayCryptoString] turns the input [amount] into the preferred representation of [cryptoCurrency] + /// + /// if [displayMode] is [BitcoinAmountDisplayMode.satoshi] it returns 1 BTC as 100000000 + /// if [displayMode] is [BitcoinAmountDisplayMode.bitcoin] it returns 1 BTC as 1 + /// hardcoding fractionalDigits to 8 as it's a display string, so no need to show more than that + String asDisplayString(Money amount) => + amount.toStringWithPrecision(useBaseUnit: useSatoshi(amount.currency), fractionalDigits: 8); - String getDisplayCryptoStringFromBigInt(BigInt amount, CryptoCurrency cryptoCurrency) { - if (useSatoshi(cryptoCurrency)) { - return "$amount"; - } + /// [asDisplayStringWithSymbol] turns the input [amount] into the preferred representation of + /// [cryptoCurrency] following the symbol of the unit + /// + /// if [displayMode] is [BitcoinAmountDisplayMode.satoshi] it returns 1 BTC as 100000000 sats + /// if [displayMode] is [BitcoinAmountDisplayMode.bitcoin] it returns 1 BTC as 1 BTC + /// hardcoding fractionalDigits to 8 as it's a display string, so no need to show more than that + String asDisplayStringWithSymbol(Money amount) => + amount.toStringWithSymbol(useBaseUnit: useSatoshi(amount.currency), fractionalDigits: 8); - return cryptoCurrency.formatAmount(amount); - } + /// [parseCryptoString] turns the the display representation [string] into `Money` with [cryptoCurrency] + Money parseCryptoString(String amount, CryptoCurrency cryptoCurrency) => + Money.parse(amount, cryptoCurrency, isBaseUnit: useSatoshi(cryptoCurrency)); - /// [getCryptoStringFromDouble] turns the input [amount] into the preferred representation of [cryptoCurrency] and - String getDisplayCryptoStringFromDouble(double amount, CryptoCurrency cryptoCurrency) { - if (useSatoshi(cryptoCurrency)) { - return "$amount"; - } - - return cryptoCurrency.formatAmount(BigInt.from(amount)); - } - - /// [parseCryptoString] turns the input [string] into a `BigInt` presentation of the [cryptoCurrency] - BigInt parseCryptoString(String amount, CryptoCurrency cryptoCurrency) { - if (useSatoshi(cryptoCurrency)) { - return BigInt.parse(amount); - } - - return cryptoCurrency.parseAmount(amount); - } - - /// [tryParseCryptoString] tries to turn the display representation [string] into a `BigInt` presentation of the [cryptoCurrency] - BigInt? tryParseCryptoString(String amount, CryptoCurrency cryptoCurrency) { - if (useSatoshi(cryptoCurrency)) { - return BigInt.tryParse(amount); - } - - return cryptoCurrency.tryParseAmount(amount); - } + /// [tryParseCryptoString] tries to turn the display representation [string] into `Money` with [cryptoCurrency] + Money? tryParseCryptoString(String amount, CryptoCurrency cryptoCurrency) => + Money.tryParse(amount, cryptoCurrency, isBaseUnit: useSatoshi(cryptoCurrency)); /// [getCryptoSymbol] returns the correct Symbol related to the presentation String getCryptoSymbol(CryptoCurrency cryptoCurrency) => useSatoshi(cryptoCurrency) ? "sats" : cryptoCurrency.title.safeSubString(0, 8); - bool useSatoshi(CryptoCurrency cryptoCurrency) => + bool useSatoshi(Currency cryptoCurrency) => ([CryptoCurrency.btc, CryptoCurrency.btcln].contains(cryptoCurrency) && displayMode == BitcoinAmountDisplayMode.satoshi) || (CryptoCurrency.btcln == cryptoCurrency && diff --git a/lib/core/background_sync.dart b/lib/core/background_sync.dart index 7a50d2b1e..a63618d0c 100644 --- a/lib/core/background_sync.dart +++ b/lib/core/background_sync.dart @@ -213,7 +213,7 @@ class BackgroundSync { final action = tx.direction == TransactionDirection.incoming ? "Received" : "Sent"; if (sharedPreferences.getBool(PreferencesKey.backgroundSyncNotificationsEnabled) ?? false) { await showNotification( - "$action ${wallet.currency.fullName} in ${wallet.name}", "${tx.amountFormatted()}"); + "$action ${wallet.currency.fullName} in ${wallet.name}", "${tx.amount.toString()}"); } printV( "${wallet.currency.fullName} in ${wallet.name}: TX: ${tx.date} ${tx.amount} ${tx.direction}"); diff --git a/lib/core/backup_service_v3.dart b/lib/core/backup_service_v3.dart index 9e9b5b667..7d746ff42 100644 --- a/lib/core/backup_service_v3.dart +++ b/lib/core/backup_service_v3.dart @@ -144,6 +144,21 @@ class BackupMetadata { } } +class IncompatibleBackupAppException implements Exception { + IncompatibleBackupAppException({ + required this.sourceAppName, + required this.currentAppName, + }); + + final String sourceAppName; + final String currentAppName; + + @override + String toString() { + return 'This backup was created in $sourceAppName and cannot be restored in $currentAppName.'; + } +} + class BackupServiceV3 extends $BackupService { BackupServiceV3(super.secureStorage, super.transactionDescriptionBox, super.keyService, super.sharedPreferences); @@ -197,7 +212,8 @@ class BackupServiceV3 extends $BackupService { } } - Future importBackupFile(File file, String password, {String nonce = secrets.backupSalt}) { + Future importBackupFile(File file, String password, + {String nonce = secrets.backupSalt, bool checkBackupApp = true}) { final version = getVersionFile(file); switch (version) { case BackupVersion.unknown: @@ -210,11 +226,12 @@ class BackupServiceV3 extends $BackupService { case BackupVersion.v2: return super.importBackupV2(file.readAsBytesSync(), password); case BackupVersion.v3: - return importBackupFileV3(file, password, nonce: nonce); + return importBackupFileV3(file, password, nonce: nonce, checkBackupApp: checkBackupApp); } } - Future importBackupFileV3(File file, String password, {String nonce = secrets.backupSalt}) async{ + Future importBackupFileV3(File file, String password, + {String nonce = secrets.backupSalt,bool checkBackupApp = true}) async{ // Overall design of v3 backup is the following: // 1. backup.zip - plaintext zip file that user can open with any archive manager // 2. backup.zip/README.txt - text file to let user know what is inside of this file @@ -223,6 +240,11 @@ class BackupServiceV3 extends $BackupService { final inputStream = InputFileStream(file.path); final archive = ZipDecoder().decodeStream(inputStream); + + if (checkBackupApp) { + await _throwIfBackupWasCreatedInAnotherApp(archive); + } + final metadataFile = archive.findFile('metadata.json'); if (metadataFile == null) { throw Exception('Invalid v3 backup: missing metadata.json'); @@ -425,7 +447,7 @@ class BackupServiceV3 extends $BackupService { metadata.sha512sum = (await sha512.bind(dataBinUnencrypted.openRead()).first).toString(); final raf = await dataBinUnencrypted.open(); - + while (true) { printV("Reading chunk ${chunkIndex++}"); @@ -491,6 +513,33 @@ This backup was created on ${DateTime.now().toIso8601String()} return file; } + Future _throwIfBackupWasCreatedInAnotherApp(Archive archive) async { + final readmeFile = archive.findFile('README.txt'); + if (readmeFile == null) return; + + final readmeBytes = readmeFile.rawContent?.readBytes(); + if (readmeBytes == null) return; + + final readmeString = utf8.decode(readmeBytes, allowMalformed: true); + final sourceAppName = _extractBackupAppName(readmeString); + if (sourceAppName == null) return; + + + final currentAppName = (await PackageInfo.fromPlatform()).appName; + if (sourceAppName == currentAppName) return; + + + throw IncompatibleBackupAppException( + sourceAppName: sourceAppName, + currentAppName: currentAppName, + ); + } + + String? _extractBackupAppName(String readme) { + final match = RegExp(r'^This is a (.+) backup\.', multiLine: true).firstMatch(readme); + return match?.group(1)?.trim(); + } + static const chunkSize = 24 * 1024 * 1024; // 24MiB File setVersionFile(File file, BackupVersion version) { diff --git a/lib/core/csv_export_service.dart b/lib/core/csv_export_service.dart index ae6fa347a..ae75edb6a 100644 --- a/lib/core/csv_export_service.dart +++ b/lib/core/csv_export_service.dart @@ -80,21 +80,15 @@ class CsvExportService { ? tx.to! : (tx.outputAddresses?.firstOrNull ?? '')); - // Split "0.2769 LTC" → amount="0.2769", currency="LTC" - final (amount, currency) = _splitAmountCurrency(tx.amountFormatted()); - - // fee is stored as (inputAmount - totalOutputAmount) which is negative for incoming - // transactions — only include it when the raw value is a sensible positive fee. - final fee = tx.fee != null && tx.fee! > 0 ? (tx.feeFormatted() ?? '') : ''; - final (feeAmount, _) = fee.isNotEmpty ? _splitAmountCurrency(fee) : ('', ''); + final fee = tx.fee != null && !tx.fee!.isZero ? tx.fee.toString() : ''; return _row([ 'transaction', _isoDate(tx.date), type, - amount, - currency, - feeAmount, + tx.amount.toString(), + tx.amount.currency.symbol, + fee, tx.id, address, status, @@ -109,15 +103,6 @@ class CsvExportService { ]); } - /// Splits a formatted amount like "0.2769 LTC" into ("0.2769", "LTC"). - /// If there is no trailing symbol the whole string is returned as the amount. - (String, String) _splitAmountCurrency(String formatted) { - final trimmed = formatted.trim(); - final idx = trimmed.lastIndexOf(' '); - if (idx == -1) return (trimmed, ''); - return (trimmed.substring(0, idx), trimmed.substring(idx + 1)); - } - String _tradeRow(TradeListItem item) { final trade = item.trade; return _row([ diff --git a/lib/decred/cw_decred.dart b/lib/decred/cw_decred.dart index 7fe2ee5c2..18b955af8 100644 --- a/lib/decred/cw_decred.dart +++ b/lib/decred/cw_decred.dart @@ -19,9 +19,8 @@ class CWDecred extends Decred { DecredRestoreWalletFromPubkeyCredentials(name: name, pubkey: pubkey, password: password); @override - WalletService createDecredWalletService(Box unspentCoinSource) { - return DecredWalletService(unspentCoinSource); - } + WalletService createDecredWalletService(Box unspentCoinSource) => + DecredWalletService(unspentCoinSource); @override List getTransactionPriorities() => DecredTransactionPriority.all; @@ -39,18 +38,19 @@ class CWDecred extends Decred { @override Object createDecredTransactionCredentials(List outputs, TransactionPriority priority) => DecredTransactionCredentials( - outputs - .map((out) => OutputInfo( + outputs + .map((out) => OutputInfo( fiatAmount: out.fiatAmount, - cryptoAmount: out.cryptoAmount, + cryptoAmount: out.cryptoAmountMoney, address: out.address, note: out.note, sendAll: out.sendAll, extractedAddress: out.extractedAddress, isParsedAddress: out.isParsedAddress, - formattedCryptoAmount: out.formattedCryptoAmount)) - .toList(), - priority: priority as DecredTransactionPriority); + )) + .toList(), + priority: priority as DecredTransactionPriority, + ); List getAddressInfos(Object wallet) { final decredWallet = wallet as DecredWallet; @@ -69,17 +69,6 @@ class CWDecred extends Decred { await decredWallet.walletAddresses.generateNewAddress(label); } - @override - String formatterDecredAmountToString({required int amount}) => - decredAmountToString(amount: amount); - - @override - double formatterDecredAmountToDouble({required int amount}) => - decredAmountToDouble(amount: amount); - - @override - int formatterStringDoubleToDecredAmount(String amount) => stringDoubleToDecredAmount(amount); - @override List getUnspents(Object wallet) { final decredWallet = wallet as DecredWallet; diff --git a/lib/entities/calculate_fiat_amount.dart b/lib/entities/calculate_fiat_amount.dart index e4e4b29a4..86f2dfe68 100644 --- a/lib/entities/calculate_fiat_amount.dart +++ b/lib/entities/calculate_fiat_amount.dart @@ -1,9 +1,11 @@ +import 'package:cw_core/amount/amount_sanitizer.dart'; + String calculateFiatAmount({double? price, String? cryptoAmount, bool raw = false}) { if (price == null || cryptoAmount == null) { return '0.00'; } - cryptoAmount = cryptoAmount.replaceAll(',', '.'); + cryptoAmount = cryptoAmount.sanitized(); final _amount = double.tryParse(cryptoAmount); if (_amount == null || _amount.isNaN) return '0.00'; diff --git a/lib/entities/contact.dart b/lib/entities/contact.dart index bd7fcf9a3..5c34e9c23 100644 --- a/lib/entities/contact.dart +++ b/lib/entities/contact.dart @@ -3,9 +3,9 @@ import 'package:cw_core/hive_type_ids.dart'; import 'package:cw_core/keyable.dart'; import 'package:hive/hive.dart'; -part 'contact.g.dart'; +part 'contact.part.dart'; -@HiveType(typeId: Contact.typeId) +// @HiveType(typeId: Contact.typeId) class Contact extends HiveObject with Keyable { Contact({required this.name, required this.address, CryptoCurrency? type, DateTime? lastChange, this.displayName = ""}) : lastChange = lastChange ?? DateTime.now() { @@ -17,19 +17,19 @@ class Contact extends HiveObject with Keyable { static const typeId = CONTACT_TYPE_ID; static const boxName = 'Contacts'; - @HiveField(0, defaultValue: '') + // @HiveField(0, defaultValue: '') String name; - @HiveField(1, defaultValue: '') + // @HiveField(1, defaultValue: '') String address; - @HiveField(2, defaultValue: 0) + // @HiveField(2, defaultValue: 0) late int raw; - @HiveField(3) + // @HiveField(3) DateTime lastChange; - @HiveField(4, defaultValue: "") + // @HiveField(4, defaultValue: "") String displayName; CryptoCurrency get type => CryptoCurrency.deserialize(raw: raw); diff --git a/lib/entities/contact.part.dart b/lib/entities/contact.part.dart new file mode 100644 index 000000000..f09d31026 --- /dev/null +++ b/lib/entities/contact.part.dart @@ -0,0 +1,52 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'contact.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class ContactAdapter extends TypeAdapter { + @override + final int typeId = 0; + + @override + Contact read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return Contact( + name: fields[0] == null ? '' : fields[0] as String, + address: fields[1] == null ? '' : fields[1] as String, + lastChange: fields[3] as DateTime?, + displayName: fields[4] == null ? '' : fields[4] as String, + )..raw = fields[2] == null ? 0 : fields[2] as int; + } + + @override + void write(BinaryWriter writer, Contact obj) { + writer + ..writeByte(5) + ..writeByte(0) + ..write(obj.name) + ..writeByte(1) + ..write(obj.address) + ..writeByte(2) + ..write(obj.raw) + ..writeByte(3) + ..write(obj.lastChange) + ..writeByte(4) + ..write(obj.displayName); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ContactAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/entities/fiat_currency.dart b/lib/entities/fiat_currency.dart index 55eef1afe..ca100332b 100644 --- a/lib/entities/fiat_currency.dart +++ b/lib/entities/fiat_currency.dart @@ -1,18 +1,26 @@ +import 'package:cw_core/amount/money.dart'; import 'package:cw_core/currency.dart'; import 'package:cw_core/enumerable_item.dart'; class FiatCurrency extends EnumerableItem with Serializable implements Currency { const FiatCurrency({ - required String symbol, + required this.symbol, required this.countryCode, required this.fullName, this.decimals = 2, }) : super(title: symbol, raw: symbol); final String countryCode; + + @override final String fullName; + + @override final int decimals; + @override + final String symbol; + static List get all => _all.values.toList(); static List get currenciesAvailableToBuyWith => [ @@ -182,4 +190,10 @@ class FiatCurrency extends EnumerableItem with Serializable impl @override String get iconPath => "assets/images/flags/$countryCode.png"; + + @override + Money parseAmount(String value) => Money.parse(value, this); + + @override + Money? tryParseAmount(String value) => Money.tryParse(value, this); } diff --git a/lib/entities/haven_seed_store.dart b/lib/entities/haven_seed_store.dart index f899799d8..f9a362a31 100644 --- a/lib/entities/haven_seed_store.dart +++ b/lib/entities/haven_seed_store.dart @@ -1,9 +1,9 @@ import 'package:cw_core/hive_type_ids.dart'; import 'package:hive/hive.dart'; -part 'haven_seed_store.g.dart'; +part 'haven_seed_store.part.dart'; -@HiveType(typeId: HavenSeedStore.typeId) +// @HiveType(typeId: HavenSeedStore.typeId) class HavenSeedStore extends HiveObject { HavenSeedStore({required this.id, this.seed}); @@ -11,9 +11,9 @@ class HavenSeedStore extends HiveObject { static const boxName = 'HavenSeedStore'; static const boxKey = 'havenSeedStoreKey'; - @HiveField(0, defaultValue: '') + // @HiveField(0, defaultValue: '') String id; - @HiveField(2) + // @HiveField(2) String? seed; } diff --git a/lib/entities/haven_seed_store.part.dart b/lib/entities/haven_seed_store.part.dart new file mode 100644 index 000000000..ed83d226b --- /dev/null +++ b/lib/entities/haven_seed_store.part.dart @@ -0,0 +1,44 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'haven_seed_store.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class HavenSeedStoreAdapter extends TypeAdapter { + @override + final int typeId = 21; + + @override + HavenSeedStore read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return HavenSeedStore( + id: fields[0] == null ? '' : fields[0] as String, + seed: fields[2] as String?, + ); + } + + @override + void write(BinaryWriter writer, HavenSeedStore obj) { + writer + ..writeByte(2) + ..writeByte(0) + ..write(obj.id) + ..writeByte(2) + ..write(obj.seed); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is HavenSeedStoreAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/entities/template.dart b/lib/entities/template.dart index 7cdd2c74a..302a09fcb 100644 --- a/lib/entities/template.dart +++ b/lib/entities/template.dart @@ -1,9 +1,9 @@ import 'package:cw_core/hive_type_ids.dart'; import 'package:hive/hive.dart'; -part 'template.g.dart'; +part 'template.part.dart'; -@HiveType(typeId: Template.typeId) +// @HiveType(typeId: Template.typeId) class Template extends HiveObject { Template( {required this.nameRaw, @@ -18,28 +18,28 @@ class Template extends HiveObject { static const typeId = TEMPLATE_TYPE_ID; static const boxName = 'Template'; - @HiveField(0) + // @HiveField(0) String? nameRaw; - @HiveField(1) + // @HiveField(1) String? addressRaw; - @HiveField(2) + // @HiveField(2) String? cryptoCurrencyRaw; - @HiveField(3) + // @HiveField(3) String? amountRaw; - @HiveField(4) + // @HiveField(4) String? fiatCurrencyRaw; - @HiveField(5) + // @HiveField(5) bool? isCurrencySelectedRaw; - @HiveField(6) + // @HiveField(6) String? amountFiatRaw; - @HiveField(7) + // @HiveField(7) List