Merge branch 'dev' into cw-1497-transaction-history-improved-layout

# Conflicts:
#	lib/new-ui/widgets/coins_page/assets_history/history_section.dart
#	lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart
This commit is contained in:
Robert Malikowski
2026-07-01 16:53:59 +02:00
263 changed files with 6371 additions and 4762 deletions
+4 -1
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
+1 -2
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
}
+20 -19
View File
@@ -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,
+18 -5
View File
@@ -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;
}
}
+32 -29
View File
@@ -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,
});
}
+17 -29
View File
@@ -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<String>? inputAddresses,
List<String>? 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<dynamic>? ?? [];
final outputAddresses = data['outputAddresses'] as List<dynamic>? ?? [];
final unspents = data['unspents'] as List<dynamic>? ?? [];
final additionalInfo = data['additionalInfo'] as Map<String, dynamic>?;
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<String, dynamic>))
.toList(),
isReceivedSilentPayment: data['isReceivedSilentPayment'] as bool? ?? false,
additionalInfo: data['additionalInfo'] as Map<String, dynamic>?,
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 = <String, dynamic>{};
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;
+92 -86
View File
@@ -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<EstimatedTxResult> estimateTxForAmount(
int credentialsAmount,
Money credentialsAmount,
List<BitcoinOutput> outputs,
List<BitcoinOutput> 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<int> _maxSpendableNoChangeAmount({
Future<Money> _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<int> 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<int>(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<PendingBitcoinTransaction> replaceByFee(String hash, int newFee) async {
@@ -2194,14 +2203,14 @@ abstract class ElectrumWalletBase
int totalOutAmount =
outputs.fold<int>(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<void> _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<void> _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<UtxoWithAddress> utxos;
final List<ECPrivateInfo> inputPrivKeyInfos;
final Map<String, PublicKeyWithDerivationPath> publicKeys; // PubKey to derivationPath
final int fee;
final int amount;
final Money fee;
final Money amount;
final bool spendsSilentPayment;
// final bool sendsToSilentPayment;
+20 -3
View File
@@ -170,9 +170,22 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@computed
List<BitcoinAddressRecord> 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 = <BitcoinAddressRecord>[
...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;
}
+11 -3
View File
@@ -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};
+53 -27
View File
@@ -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<SdkEvent>? _eventSubscription;
Stream<SdkEvent>? _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<void> 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<BigInt> getBalance() async {
Future<Money> 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<String> 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},
@@ -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<String> Function() commitOverride;
final List<void Function()> _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;
+23 -36
View File
@@ -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<void> 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<String> inputAddresses = {};
var amount = 0;
var inputAddresses = <String>{};
var output = convert.AccumulatorSink<Digest>();
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,
+14 -6
View File
@@ -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
+28 -51
View File
@@ -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<void> _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<String> 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"),
});
}
}
+41 -102
View File
@@ -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"
+6 -6
View File
@@ -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
@@ -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 = <void Function(ElectrumTransactionInfo transaction)>[];
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<void Function(ElectrumTransactionInfo transaction)> _listeners;
@override
Future<void> 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<Map<String, String>> commitUR() {
throw UnimplementedError();
}
}
+6 -5
View File
@@ -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
+5 -5
View File
@@ -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;
}
+47
View File
@@ -0,0 +1,47 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'address_info.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class AddressInfoAdapter extends TypeAdapter<AddressInfo> {
@override
final int typeId = 11;
@override
AddressInfo read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
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;
}
+16
View File
@@ -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);
}
+211
View File
@@ -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<Money> {
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;
}
}
-74
View File
@@ -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';
+14 -20
View File
@@ -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;
}
+25 -13
View File
@@ -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<int> with Serializable<int> implements Currency {
const CryptoCurrency({
@@ -21,18 +21,30 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> 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<String> 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<int> with Serializable<int> 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);
}
+17 -1
View File
@@ -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;
}
-2
View File
@@ -1,5 +1,3 @@
import 'package:flutter/foundation.dart';
abstract class EnumerableItem<T> {
const EnumerableItem({required this.title, required this.raw});
+1 -1
View File
@@ -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;
+10 -10
View File
@@ -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;
+61
View File
@@ -0,0 +1,61 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'erc20_token.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class Erc20TokenAdapter extends TypeAdapter<Erc20Token> {
@override
final int typeId = 12;
@override
Erc20Token read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
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;
}
+1 -7
View File
@@ -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;
+6 -4
View File
@@ -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<int?> getPayRequestAmount(String lnurl) async {
static Future<Money?> 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;
}
-17
View File
@@ -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;
}
+8 -8
View File
@@ -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;
}
+56
View File
@@ -0,0 +1,56 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'mweb_utxo.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class MwebUtxoAdapter extends TypeAdapter<MwebUtxo> {
@override
final int typeId = 20;
@override
MwebUtxo read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
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;
}
+6 -6
View File
@@ -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;
}
+50
View File
@@ -0,0 +1,50 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'nano_account.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class NanoAccountAdapter extends TypeAdapter<NanoAccount> {
@override
final int typeId = 13;
@override
NanoAccount read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
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;
}
+1 -1
View File
@@ -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<void> performNodeHiveMigration() async {
if(!CakeHive.isAdapterRegistered(Node.typeId)) {
+78
View File
@@ -0,0 +1,78 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'node_legacy.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class NodeAdapter extends TypeAdapter<Node> {
@override
final int typeId = 1;
@override
Node read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
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;
}
+19 -18
View File
@@ -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<String, dynamic> extra;
}
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<String, dynamic> extra;
}
+1 -1
View File
@@ -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);
+11 -11
View File
@@ -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;
+65
View File
@@ -0,0 +1,65 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'payjoin_session.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class PayjoinSessionAdapter extends TypeAdapter<PayjoinSession> {
@override
final int typeId = 23;
@override
PayjoinSession read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
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;
}
+8 -9
View File
@@ -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<void> commit();
+11 -11
View File
@@ -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({
+64
View File
@@ -0,0 +1,64 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'spl_token.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class SPLTokenAdapter extends TypeAdapter<SPLToken> {
@override
final int typeId = 16;
@override
SPLToken read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
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;
}
+8 -6
View File
@@ -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<String, dynamic> additionalInfo = {};
String? _fiatAmount;
String fiatAmount() => _fiatAmount ?? '';
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
}
+10 -10
View File
@@ -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;
+61
View File
@@ -0,0 +1,61 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tron_token.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class TronTokenAdapter extends TypeAdapter<TronToken> {
@override
final int typeId = 18;
@override
TronToken read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
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;
}
+14 -14
View File
@@ -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 ?? '';
+74
View File
@@ -0,0 +1,74 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'unspent_coins_info.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class UnspentCoinsInfoAdapter extends TypeAdapter<UnspentCoinsInfo> {
@override
final int typeId = 9;
@override
UnspentCoinsInfo read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
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;
}
-2
View File
@@ -43,8 +43,6 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
ObservableMap<CryptoCurrency, BalanceType> get balance;
String formatCryptoAmount(String amount) => amount;
SyncStatus get syncStatus;
set syncStatus(SyncStatus status);
+22 -22
View File
@@ -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,
}
+136
View File
@@ -0,0 +1,136 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'wallet_type.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class WalletTypeAdapter extends TypeAdapter<WalletType> {
@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;
}
-17
View File
@@ -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;
}
+15 -15
View File
@@ -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<String, dynamic>? info;
bool get enabled => _enabled;
+76
View File
@@ -0,0 +1,76 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'zano_asset.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class ZanoAssetAdapter extends TypeAdapter<ZanoAsset> {
@override
final int typeId = 22;
@override
ZanoAsset read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
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<String, dynamic>(),
).._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;
}
+38 -107
View File
@@ -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"
+6 -6
View File
@@ -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
@@ -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");
});
});
}
+351
View File
@@ -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<Comparable<Money>>());
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<Exception>()),
);
});
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");
});
});
});
}
+14 -11
View File
@@ -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));
});
});
}
-26
View File
@@ -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;
}
+9 -19
View File
@@ -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));
}
+15 -24
View File
@@ -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<void> 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<void> commit() async {
return send();
}
Future<void> commit() => send();
@override
Future<Map<String, String>> commitUR() {
throw UnimplementedError();
}
Future<Map<String, String>> commitUR() => throw UnimplementedError();
}
+3 -22
View File
@@ -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);
}
+33 -38
View File
@@ -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<void> init() async {
final getSeed = () async {
@@ -354,13 +349,12 @@ abstract class DecredWalletBase
Future<PendingTransaction> 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<Map<String, DecredTransactionInfo>> fetchTransactions() async {
return this.fetchFiveTransactions(0);
}
Future<Map<String, DecredTransactionInfo>> fetchTransactions() => fetchFiveTransactions(0);
Future<Map<String, DecredTransactionInfo>> fetchFiveTransactions(int from) async {
try {
final res = await _libwallet.listTransactions(walletInfo.name, from.toString(), "5");
final decoded = json.decode(res);
var txs = <String, DecredTransactionInfo>{};
final txs = <String, DecredTransactionInfo>{};
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<void> 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 = <Unspent>[];
final unspents = <Unspent>[];
for (final d in decoded) {
final spendable = d["spendable"] ?? false;
if (!spendable) {
-1
View File
@@ -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<
+42 -111
View File
@@ -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"
+4 -5
View File
@@ -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
+1 -1
View File
@@ -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;
+6 -5
View File
@@ -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
+24 -30
View File
@@ -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<String, dynamic>;
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<PendingEVMChainTransaction> 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<PendingEVMChainTransaction> 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<EVMChainERC20Balance> fetchERC20Balances(
EthereumAddress userAddress, String contractAddress) async {
Future<EVMChainERC20Balance> 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()}');
}
+96 -52
View File
@@ -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<BigInt> get savingsBalance async =>
(await _savingsGateway.savings(accountOwner: _address)).saved;
Future<Money> get savingsBalance async =>
Money((await _savings.savings(accountOwner: _address)).saved, CryptoCurrency.deuro);
Future<BigInt> get accruedInterest => _savingsGateway.accruedInterest(accountOwner: _address);
Future<Money> get savingsBalanceV1 async => Money(
(await _getSavingsGateway(_wallet.getWeb3Client()!).savings(accountOwner: _address)).saved,
CryptoCurrency.deuro);
Future<BigInt> get interestRate => _savingsGateway.currentRatePPM();
Future<Money> get accruedInterest async =>
Money(await _savings.accruedInterest(accountOwner: _address), CryptoCurrency.deuro);
Future<BigInt> get approvedBalance => _dEuro.allowance(_address, _savingsGateway.self.address);
Future<BigInt> get interestRate => _savings.currentRatePPM();
Future<BigInt> get approvedBalance => _dEuro.allowance(_address, _savings.self.address);
Future<void> _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<PendingEVMChainTransaction> 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')) {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+23 -32
View File
@@ -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<String, dynamic> 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<String, dynamic> 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,
+74 -94
View File
@@ -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<CryptoCurrency, EVMChainERC20Balance>.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<bool> 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<GasParamsHandler> 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>(
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>(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<PendingTransaction> 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<PendingTransaction> createApprovalTransaction(BigInt amount, String spender,
CryptoCurrency token, EVMChainTransactionPriority? priority, String feeCurrency,
Future<PendingTransaction> 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(),
+10 -20
View File
@@ -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));
}
}
}
+11 -13
View File
@@ -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<void> 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<Map<String, String>> commitUR() {
throw UnimplementedError();
}
Future<Map<String, String>> commitUR() => throw UnimplementedError();
}
+3 -4
View File
@@ -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,
-34
View File
@@ -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(".");
+1 -1
View File
@@ -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:
+12 -15
View File
@@ -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<int, Map<int, Map<int, String>>> 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<bool> 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<String, List<int>> debugCallLength() => monero.debugCallLength;
@@ -438,4 +435,4 @@ Map<String, dynamic> getWalletCacheDebug() {
"error": e.toString(),
};
}
}
}
+17
View File
@@ -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;
}
+3 -17
View File
@@ -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';
}
+28 -47
View File
@@ -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<MoneroBalance,
};
}
bool needExportOutputs(int amount) {
bool needExportOutputs(Money amount) {
if (int.tryParse(currentWallet!.secretSpendKey()) != 0) {
return false;
}
// viewOnlyBalance - balance that we can spend
// TODO(mrcyjanek): remove hasUnknownKeyImages when we cleanup coin control
return (currentWallet!.viewOnlyBalance(
accountIndex: walletAddresses.account!.id) < amount) ||
currentWallet!.hasUnknownKeyImages();
return (currentWallet!.viewOnlyBalance(accountIndex: walletAddresses.account!.id) <
amount.amount.toInt()) ||
currentWallet!.hasUnknownKeyImages();
}
MoneroTrezorService? trezorService;
@@ -444,30 +444,22 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
}
if (hasMultiDestination) {
if (outputs.any(
(item) => 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<MoneroBalance,
final output = outputs.first;
final address =
output.isParsedAddress ? output.extractedAddress : output.address;
final amount =
output.sendAll ? null : output.cryptoAmount!.replaceAll(',', '.');
final amount = output.sendAll ? null : output.cryptoAmount.toString();
// if ((formattedAmount != null && unlockedBalance < formattedAmount) ||
// (formattedAmount == null && unlockedBalance <= 0)) {
@@ -492,8 +483,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
// 'You do not have enough unlocked balance. Unlocked: $formattedBalance. Transaction amount: ${output.cryptoAmount}.');
// }
if (inputs.isEmpty) MoneroTransactionCreationException(
'No inputs selected');
if (inputs.isEmpty) MoneroTransactionCreationException('No inputs selected');
pendingTransactionDescription =
await transaction_history.createTransactionSync(
address: address!,
@@ -835,10 +825,10 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
: TransactionDirection.incoming,
row.timeStamp,
row.isPending,
row.amount,
Money.fromInt(row.amount, currency),
row.accountIndex,
0,
row.fee,
Money.fromInt(row.fee,currency),
row.confirmations,
)..additionalInfo = <String, dynamic>{
'key': row.key,
@@ -893,8 +883,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
}
int _getHeightDistance(DateTime date) {
final distance =
DateTime.now().difference(date).inSeconds;
final distance = DateTime.now().difference(date).inSeconds;
final daysTmp = (distance / 86400).round();
final days = daysTmp < 1 ? 1 : daysTmp;
@@ -915,31 +904,28 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
void _askForUpdateBalance() {
final unlockedBalance = _getUnlockedBalance();
final fullBalance = monero_wallet.getFullBalance(
accountIndex: walletAddresses.account!.id);
final fullBalance = monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id);
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] = MoneroBalance(
fullBalance: fullBalance,
unlockedBalance: unlockedBalance,
frozenBalance: frozenBalance);
fullBalance: fullBalance, unlockedBalance: unlockedBalance, frozen: frozenBalance);
}
}
int _getUnlockedBalance() => 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<MoneroBalance,
Future<void> setLedgerConnection(LedgerConnection connection) async {
await enableLedgerExchange(connection);
}
@override
String formatCryptoAmount(String amount) {
return moneroAmountToString(amount: int.parse(amount));
}
}
+9 -15
View File
@@ -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;
+43 -104
View File
@@ -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"
+6 -6
View File
@@ -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:
-35
View File
@@ -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);
}
}
+16 -30
View File
@@ -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),
);
}
+7 -5
View File
@@ -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;
}
+7 -18
View File
@@ -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<String, dynamic> 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<String, dynamic> toJson() => {
'id': id,
'height': height,
'amountRaw': amountRaw.toString(),
'amountRaw': amount.amount.toString(),
'direction': direction.index,
'date': date.millisecondsSinceEpoch,
'confirmed': confirmed,
+15 -13
View File
@@ -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<CryptoCurrency, NanoBalance>.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<Map<String, String>> 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:
+7 -18
View File
@@ -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<Map<String, String>> 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<void> commit() async {
for (var block in blocks) {
for (final block in blocks) {
await nanoClient.processBlock(block, "send");
}
}
@override
Future<Map<String, String>> commitUR() {
throw UnimplementedError();
}
Future<Map<String, String>> commitUR() => throw UnimplementedError();
}

Some files were not shown because too many files have changed in this diff Show More