From f43c9d4d4ef9a8aeedcff1c8a0369e9b2e9dc925 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 13 Jul 2026 13:20:30 +0200 Subject: [PATCH 1/4] feat: add ExchangeRate class for currency conversion with tests --- cw_core/lib/amount/exchange_rate.dart | 31 +++++ cw_core/test/amount/exchange_rate_test.dart | 147 ++++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 cw_core/lib/amount/exchange_rate.dart create mode 100644 cw_core/test/amount/exchange_rate_test.dart diff --git a/cw_core/lib/amount/exchange_rate.dart b/cw_core/lib/amount/exchange_rate.dart new file mode 100644 index 0000000000..4933103e07 --- /dev/null +++ b/cw_core/lib/amount/exchange_rate.dart @@ -0,0 +1,31 @@ +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/currency.dart'; + +class ExchangeRate { + final Currency baseCurrency; + + final Money quote; + + const ExchangeRate({required this.baseCurrency, required this.quote}); + + Money convert(Money amount) { + if (baseCurrency == quote.currency && baseCurrency == amount.currency) return amount; + + final scale = BigInt.from(10).pow(baseCurrency.decimals); + + if (amount.currency == baseCurrency) { + if (quote.isZero) return Money.zero(quote.currency); + + return Money(amount.amount * quote.amount ~/ scale, quote.currency); + } + + if (amount.currency == quote.currency) { + if (quote.isZero) return Money.zero(baseCurrency); + + return Money(amount.amount * scale ~/ quote.amount, baseCurrency); + } + + throw ArgumentError( + "Unable to convert ${amount.currency.symbol} in ${baseCurrency.symbol}/${quote.currency.symbol} pair"); + } +} diff --git a/cw_core/test/amount/exchange_rate_test.dart b/cw_core/test/amount/exchange_rate_test.dart new file mode 100644 index 0000000000..2cd26a3a70 --- /dev/null +++ b/cw_core/test/amount/exchange_rate_test.dart @@ -0,0 +1,147 @@ +import 'package:cw_core/amount/exchange_rate.dart'; +import 'package:cw_core/amount/money.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/currency.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('ExchangeRate', () { + final rate = ExchangeRate( + baseCurrency: CryptoCurrency.btc, + quote: Money.parse("60000", EUR), + ); + + test('convert base currency to quote currency: 1 BTC to 60.000 EUR', () { + final result = rate.convert(Money.parse('1', CryptoCurrency.btc)); + expect(result, Money.parse("60000.00", EUR)); + }); + + test('convert base currency to quote currency: 0.5 BTC to 30.000 EUR', () { + final result = rate.convert(Money.parse('0.5', CryptoCurrency.btc)); + expect(result, Money.parse("30000.00", EUR)); + }); + + test('convert quote currency to base currency: 60.000 EUR to 1 BTC', () { + final result = rate.convert(Money.parse("60000.00", EUR)); + expect(result, Money.parse('1', CryptoCurrency.btc)); + }); + + test('convert quote currency to base currency: 30 EUR to 0.0005 BTC', () { + final result = rate.convert(Money.parse("30.00", EUR)); + expect(result, Money.parse('0.0005', CryptoCurrency.btc)); + }); + }); + + group('Edge Cases', () { + final rate = ExchangeRate( + baseCurrency: CryptoCurrency.btc, + quote: Money.parse("60000", EUR), + ); + + test('Converting zero turns into zero', () { + expect(rate.convert(Money.zero(CryptoCurrency.btc)), Money.zero(EUR)); + expect(rate.convert(Money.zero(EUR)), Money.zero(CryptoCurrency.btc)); + }); + + test('truncate to smallest unit', () { + final result = rate.convert(Money(BigInt.one, CryptoCurrency.btc)); // 1 sat + expect(result, Money.zero(EUR)); + }); + + test('Truncate base unit of base currency', () { + final result = rate.convert(Money(BigInt.one, EUR)); + expect(result, Money.fromInt(16, CryptoCurrency.btc)); + }); + + test('No int overflow with large numbers', () { + final allBtc = Money.parse('21000000', CryptoCurrency.btc); + final result = rate.convert(allBtc); + expect(result, Money(BigInt.from(126000000000000), EUR)); + }); + + test('truncation for negative amounts', () { + final result = rate.convert(Money.fromInt(-1, EUR)); + expect(result, Money.fromInt(-16, CryptoCurrency.btc)); + }); + + test('Base currency without decimals places: JPY', () { + final rate = ExchangeRate( + baseCurrency: JPY, + quote: Money.fromInt(1, EUR), + ); + + expect(rate.convert(Money.fromInt(500, JPY)), Money.fromInt(500, EUR)); + expect(rate.convert(Money.fromInt(500, EUR)), Money.fromInt(500, JPY)); + }); + + test('Base currency with 18 decimals places: ETH', () { + final rate = ExchangeRate( + baseCurrency: CryptoCurrency.eth, + quote: Money.fromInt(300000, EUR), + ); + + expect(rate.convert(Money.parse('1', CryptoCurrency.eth)), Money.fromInt(300000, EUR)); + expect(rate.convert(Money.fromInt(3000, EUR)), Money.parse('0.01', CryptoCurrency.eth)); + }); + + test('Smalles base unit is less than smalles base unit of quote', () { + final rate = ExchangeRate( + baseCurrency: CryptoCurrency.eth, + quote: Money.fromInt(300000, EUR), + ); + expect(rate.convert(Money(BigInt.one, CryptoCurrency.eth)), Money(BigInt.zero, EUR)); + }); + + test('Cannot convert currency outside of pair', () { + expect(() => rate.convert(Money.parse("60000", USD)), throwsArgumentError); + }); + + test('handle quote being 0 correctly', () { + final zeroRate = ExchangeRate( + baseCurrency: CryptoCurrency.btc, + quote: Money(BigInt.zero, EUR), + ); + expect(zeroRate.convert(Money.fromInt(100, CryptoCurrency.btc)), Money(BigInt.zero, EUR)); + expect(zeroRate.convert(Money.fromInt(100, EUR)), Money(BigInt.zero, CryptoCurrency.btc)); + }); + }); +} + +class FiatCurrency implements Currency { + const FiatCurrency({ + required this.symbol, + required this.countryCode, + required this.fullName, + this.decimals = 2, + }); + + final String countryCode; + + @override + final String fullName; + + @override + final int decimals; + + @override + final String symbol; + + @override + String? get iconPath => throw UnimplementedError(); + + @override + String get name => throw UnimplementedError(); + + @override + String? get tag => throw UnimplementedError(); + + @override + Money parseAmount(String value) => Money.parse(value, this); + + @override + Money? tryParseAmount(String value) => Money.tryParse(value, this); +} + +const EUR = FiatCurrency(symbol: 'EUR', countryCode: "eur", fullName: "Euro"); +const USD = FiatCurrency(symbol: 'USD', countryCode: "usd", fullName: "US Dollar"); +const JPY = FiatCurrency(symbol: 'JPY', countryCode: "jpn", fullName: "Japanese Yen", decimals: 0); From 30c290014ba12ce78155beeea21bb1f9c13294e0 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 13 Jul 2026 13:26:16 +0200 Subject: [PATCH 2/4] docs: add detailed comments to ExchangeRate class --- cw_core/lib/amount/exchange_rate.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cw_core/lib/amount/exchange_rate.dart b/cw_core/lib/amount/exchange_rate.dart index 4933103e07..052ac04283 100644 --- a/cw_core/lib/amount/exchange_rate.dart +++ b/cw_core/lib/amount/exchange_rate.dart @@ -2,12 +2,21 @@ import 'package:cw_core/amount/money.dart'; import 'package:cw_core/currency.dart'; class ExchangeRate { + /// The currency being priced (e.g. BTC in a BTC/USD pair). final Currency baseCurrency; + /// The price of one whole unit of [baseCurrency], in the quote currency + /// (e.g. 45000 USD in a BTC/USD pair). final Money quote; const ExchangeRate({required this.baseCurrency, required this.quote}); + /// Converts [amount] between the base and quote currencies. + /// + /// Results are truncated toward zero. + /// + /// Throws an [ArgumentError] if [amount]'s currency is not part of + /// this pair. Money convert(Money amount) { if (baseCurrency == quote.currency && baseCurrency == amount.currency) return amount; From 990aeeb053a27279c5115f4b429048a263427e40 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 13 Jul 2026 15:34:23 +0200 Subject: [PATCH 3/4] refactor: rename `baseCurrency` to `base` in `ExchangeRate` --- cw_core/lib/amount/exchange_rate.dart | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cw_core/lib/amount/exchange_rate.dart b/cw_core/lib/amount/exchange_rate.dart index 052ac04283..efcb06abd1 100644 --- a/cw_core/lib/amount/exchange_rate.dart +++ b/cw_core/lib/amount/exchange_rate.dart @@ -3,13 +3,13 @@ import 'package:cw_core/currency.dart'; class ExchangeRate { /// The currency being priced (e.g. BTC in a BTC/USD pair). - final Currency baseCurrency; + final Currency base; - /// The price of one whole unit of [baseCurrency], in the quote currency + /// The price of one whole unit of [base], in the quote currency /// (e.g. 45000 USD in a BTC/USD pair). final Money quote; - const ExchangeRate({required this.baseCurrency, required this.quote}); + const ExchangeRate({required this.base, required this.quote}); /// Converts [amount] between the base and quote currencies. /// @@ -18,23 +18,23 @@ class ExchangeRate { /// Throws an [ArgumentError] if [amount]'s currency is not part of /// this pair. Money convert(Money amount) { - if (baseCurrency == quote.currency && baseCurrency == amount.currency) return amount; + if (base == quote.currency && base == amount.currency) return amount; - final scale = BigInt.from(10).pow(baseCurrency.decimals); + final scale = BigInt.from(10).pow(base.decimals); - if (amount.currency == baseCurrency) { + if (amount.currency == base) { if (quote.isZero) return Money.zero(quote.currency); return Money(amount.amount * quote.amount ~/ scale, quote.currency); } if (amount.currency == quote.currency) { - if (quote.isZero) return Money.zero(baseCurrency); + if (quote.isZero) return Money.zero(base); - return Money(amount.amount * scale ~/ quote.amount, baseCurrency); + return Money(amount.amount * scale ~/ quote.amount, base); } throw ArgumentError( - "Unable to convert ${amount.currency.symbol} in ${baseCurrency.symbol}/${quote.currency.symbol} pair"); + "Unable to convert ${amount.currency.symbol} in ${base.symbol}/${quote.currency.symbol} pair"); } } From 6ad1b70b9f6785555dd917756af11ed398283255 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Tue, 14 Jul 2026 20:10:28 +0200 Subject: [PATCH 4/4] auto-reformat --- cw_bitcoin/lib/address_from_output.dart | 15 +- cw_bitcoin/lib/bitcoin_address_record.dart | 12 +- cw_bitcoin/lib/bitcoin_amount_format.dart | 4 +- .../bitcoin_commit_transaction_exception.dart | 1 - .../lib/bitcoin_transaction_priority.dart | 18 +- cw_bitcoin/lib/bitcoin_wallet.dart | 70 +- cw_bitcoin/lib/bitcoin_wallet_addresses.dart | 8 +- .../bitcoin_wallet_creation_credentials.dart | 32 +- cw_bitcoin/lib/bitcoin_wallet_keys.dart | 13 +- cw_bitcoin/lib/bitcoin_wallet_service.dart | 24 +- cw_bitcoin/lib/electrum.dart | 39 +- .../lib/electrum_transaction_history.dart | 1 - cw_bitcoin/lib/electrum_transaction_info.dart | 8 +- cw_bitcoin/lib/electrum_wallet.dart | 167 +- cw_bitcoin/lib/electrum_wallet_addresses.dart | 216 +- cw_bitcoin/lib/exceptions.dart | 2 +- cw_bitcoin/lib/hardware/bitbox_service.dart | 3 +- .../lib/hardware/litecoin_ledger_service.dart | 6 +- .../lib/lightning/lightning_wallet.dart | 32 +- .../pending_lightning_transaction.dart | 3 +- cw_bitcoin/lib/litecoin_wallet.dart | 56 +- cw_bitcoin/lib/litecoin_wallet_addresses.dart | 20 +- cw_bitcoin/lib/litecoin_wallet_service.dart | 17 +- cw_bitcoin/lib/payjoin/manager.dart | 31 +- .../lib/payjoin/payjoin_receive_worker.dart | 31 +- .../lib/payjoin/payjoin_send_worker.dart | 8 +- cw_bitcoin/lib/payjoin/storage.dart | 36 +- cw_bitcoin/lib/psbt/signer.dart | 57 +- cw_bitcoin/lib/psbt/transaction_builder.dart | 31 +- cw_bitcoin/lib/psbt/utils.dart | 4 +- cw_bitcoin/lib/psbt/v0_deserialize.dart | 7 +- cw_bitcoin/lib/psbt/v0_finalizer.dart | 7 +- cw_bitcoin/lib/utils.dart | 2 +- cw_bitcoin/pubspec.lock | 10 +- .../lib/src/bitcoin_cash_wallet_service.dart | 8 +- .../lib/src/exceptions/exceptions.dart | 2 +- cw_core/lib/account.dart | 2 +- cw_core/lib/account_list.dart | 1 - cw_core/lib/address_info.part.dart | 4 +- cw_core/lib/amount/amount_sanitizer.dart | 1 - cw_core/lib/amount_converter.dart | 9 +- cw_core/lib/balance_card_style_settings.dart | 30 +- cw_core/lib/card_design.dart | 233 +- cw_core/lib/crypto_amount_format.dart | 1 - cw_core/lib/crypto_currency.dart | 997 ++++- cw_core/lib/currency_for_wallet_type.dart | 3 +- cw_core/lib/db/sqlite_debug.dart | 2 +- cw_core/lib/encryption_file_utils.dart | 55 +- cw_core/lib/erc20_token.part.dart | 6 +- cw_core/lib/exceptions.dart | 2 +- cw_core/lib/format_amount.dart | 6 +- cw_core/lib/format_fixed.dart | 9 +- cw_core/lib/get_height_by_date.dart | 3 +- cw_core/lib/get_height_by_date_xmr.dart | 1 - cw_core/lib/hive_type_ids.dart | 48 +- cw_core/lib/key.dart | 3 +- cw_core/lib/keyable.dart | 2 +- cw_core/lib/lnurl.dart | 15 +- cw_core/lib/monero_wallet_keys.dart | 12 +- cw_core/lib/mweb_utxo.part.dart | 4 +- cw_core/lib/nano_account.part.dart | 4 +- cw_core/lib/node.dart | 64 +- cw_core/lib/node_legacy.dart | 46 +- cw_core/lib/node_legacy.part.dart | 7 +- cw_core/lib/node_list.dart | 21 +- cw_core/lib/parseBoolFromString.dart | 2 +- cw_core/lib/parse_fixed.dart | 3 +- cw_core/lib/pathForWallet.dart | 3 +- cw_core/lib/payjoin_session.dart | 1 - cw_core/lib/payjoin_session.part.dart | 4 +- cw_core/lib/payment_uris.dart | 7 +- cw_core/lib/root_dir.dart | 28 +- cw_core/lib/sec_random_native.dart | 3 +- cw_core/lib/solana_rpc_http_service.dart | 6 +- cw_core/lib/spl_token.part.dart | 4 +- cw_core/lib/transaction_direction.dart | 8 +- cw_core/lib/transaction_history.dart | 3 +- cw_core/lib/transaction_priority.dart | 6 +- cw_core/lib/tron_token.part.dart | 4 +- cw_core/lib/utils/file.dart | 4 +- cw_core/lib/utils/proxy_logger/abstract.dart | 6 +- .../proxy_logger/memory_proxy_logger.dart | 22 +- .../lib/utils/proxy_logger/silent_logger.dart | 4 +- cw_core/lib/utils/proxy_socket/abstract.dart | 17 +- cw_core/lib/utils/proxy_socket/insecure.dart | 21 +- cw_core/lib/utils/proxy_socket/secure.dart | 18 +- cw_core/lib/utils/proxy_socket/socks.dart | 12 +- cw_core/lib/utils/proxy_wrapper.dart | 50 +- cw_core/lib/utils/tor/abstract.dart | 2 +- cw_core/lib/utils/tor/disabled.dart | 2 +- cw_core/lib/utils/tor/socks.dart | 2 +- cw_core/lib/utils/tor/torch.dart | 2 +- cw_core/lib/utils/zpub.dart | 22 +- cw_core/lib/wallet_info.dart | 242 +- cw_core/lib/wallet_info_legacy.dart | 2 +- cw_core/lib/wallet_info_legacy.part.dart | 16 +- cw_core/lib/wallet_keys_file.dart | 9 +- cw_core/lib/wallet_type.dart | 25 +- cw_core/lib/wallet_type.part.dart | 4 +- cw_core/lib/wownero_amount_format.dart | 2 +- cw_core/lib/zano_asset.part.dart | 4 +- cw_core/pubspec.lock | 10 +- .../test/amount/amount_sanitizer_test.dart | 10 +- cw_core/test/amount/money_test.dart | 4 +- cw_core/test/crypto_amount_format.dart | 6 +- cw_core/test/format_fixed_test.dart | 12 +- cw_core/test/lnurl_test.dart | 27 +- cw_decred/lib/wallet.dart | 14 +- cw_decred/lib/wallet_service.dart | 12 +- cw_decred/pubspec.lock | 8 +- cw_dogecoin/lib/cw_dogecoin.dart | 1 - .../src/dogecoin_transaction_priority.dart | 7 +- cw_dogecoin/lib/src/dogecoin_wallet.dart | 3 +- .../lib/src/dogecoin_wallet_addresses.dart | 9 +- cw_dogecoin/test/cw_dogecoin_test.dart | 3 +- cw_evm/lib/clients/arbitrum_client.dart | 1 - cw_evm/lib/clients/base_client.dart | 1 - cw_evm/lib/clients/bsc_client.dart | 1 - cw_evm/lib/clients/ethereum_client.dart | 1 - cw_evm/lib/clients/evm_chain_client.dart | 15 +- cw_evm/lib/clients/polygon_client.dart | 1 - cw_evm/lib/contract/erc20.dart | 47 +- cw_evm/lib/deuro/deuro_savings.dart | 17 +- cw_evm/lib/deuro/deuro_savings_contract.dart | 6 +- .../deuro/deuro_savings_gateway_contract.dart | 110 +- cw_evm/lib/evm_chain_exceptions.dart | 13 +- cw_evm/lib/evm_chain_registry.dart | 3 +- cw_evm/lib/evm_chain_transaction_model.dart | 3 +- cw_evm/lib/evm_chain_wallet.dart | 15 +- cw_evm/lib/evm_chain_wallet_addresses.dart | 1 - cw_evm/lib/evm_erc20_balance.dart | 2 +- .../hardware/evm_chain_bitbox_service.dart | 5 +- .../evm_chain_ledger_credentials.dart | 28 +- .../hardware/evm_chain_ledger_service.dart | 6 +- cw_evm/lib/tokens/base_tokens.dart | 4 +- cw_evm/lib/tokens/polygon_tokens.dart | 4 +- cw_evm/lib/usdt0/usdt0_config.dart | 20 +- cw_evm/lib/usdt0/usdt0_service.dart | 2 +- cw_evm/lib/utils/network_chain_utils.dart | 1 - cw_monero/lib/api/account_list.dart | 2 +- .../creation_transaction_exception.dart | 4 +- .../exceptions/setup_wallet_exception.dart | 4 +- .../exceptions/wallet_creation_exception.dart | 2 +- .../exceptions/wallet_opening_exception.dart | 2 +- .../wallet_restore_from_keys_exception.dart | 4 +- .../wallet_restore_from_seed_exception.dart | 4 +- cw_monero/lib/api/monero_output.dart | 2 +- .../lib/api/structs/pending_transaction.dart | 15 +- cw_monero/lib/api/subaddress_list.dart | 32 +- cw_monero/lib/api/transaction_history.dart | 120 +- cw_monero/lib/api/wallet.dart | 59 +- cw_monero/lib/api/wallet_manager.dart | 58 +- cw_monero/lib/bip39_seed.dart | 15 +- ...monero_transaction_creation_exception.dart | 2 +- ...onero_transaction_no_inputs_exception.dart | 3 +- cw_monero/lib/ledger.dart | 13 +- .../lib/mnemonics/chinese_simplified.dart | 2 +- cw_monero/lib/mnemonics/dutch.dart | 2 +- cw_monero/lib/mnemonics/french.dart | 3258 ++++++++--------- cw_monero/lib/mnemonics/german.dart | 2 +- cw_monero/lib/mnemonics/italian.dart | 3258 ++++++++--------- cw_monero/lib/mnemonics/japanese.dart | 2 +- cw_monero/lib/mnemonics/portuguese.dart | 2 +- cw_monero/lib/mnemonics/russian.dart | 2 +- cw_monero/lib/mnemonics/spanish.dart | 2 +- cw_monero/lib/monero_account_list.dart | 25 +- cw_monero/lib/monero_subaddress_list.dart | 24 +- cw_monero/lib/monero_transaction_history.dart | 11 +- cw_monero/lib/monero_wallet.dart | 131 +- cw_monero/lib/monero_wallet_addresses.dart | 34 +- cw_monero/lib/monero_wallet_service.dart | 67 +- cw_monero/lib/pending_monero_transaction.dart | 6 +- cw_monero/lib/trezor.dart | 14 +- cw_monero/pubspec.lock | 10 +- cw_monero/test/bip39_seed_test.dart | 19 +- .../test/monero_wallet_service_test.dart | 12 +- cw_monero/test/utils/setup_monero_c.dart | 13 +- cw_mweb/lib/cw_mweb.dart | 14 +- cw_mweb/lib/mweb_ffi.dart | 3 +- cw_mweb/lib/mwebd.pb.dart | 1398 ++++--- cw_mweb/lib/mwebd.pbgrpc.dart | 215 +- cw_nano/lib/nano_client.dart | 12 +- cw_nano/lib/nano_transaction_model.dart | 4 +- cw_nano/lib/nano_wallet.dart | 6 +- cw_nano/lib/nano_wallet_service.dart | 3 +- cw_nano/lib/pending_nano_transaction.dart | 2 +- cw_nano/pubspec.lock | 8 +- cw_solana/lib/pending_solana_transaction.dart | 2 +- cw_solana/lib/solana_client.dart | 12 +- cw_solana/lib/solana_wallet.dart | 24 +- cw_solana/lib/solana_wallet_service.dart | 10 +- cw_tron/lib/pending_tron_transaction.dart | 2 +- cw_tron/lib/tron_balance.dart | 2 +- cw_tron/lib/tron_client.dart | 11 +- cw_tron/lib/tron_exception.dart | 3 +- cw_tron/lib/tron_http_provider.dart | 16 +- cw_tron/lib/tron_wallet.dart | 25 +- cw_wownero/pubspec.lock | 8 +- cw_zano/lib/api/consts.dart | 2 +- cw_zano/lib/api/model/asset_id_params.dart | 6 +- cw_zano/lib/api/model/balance.dart | 5 +- .../lib/api/model/create_wallet_result.dart | 7 +- cw_zano/lib/api/model/destination.dart | 13 +- cw_zano/lib/api/model/employed_entries.dart | 19 +- .../model/get_recent_txs_and_info_params.dart | 15 +- .../model/get_recent_txs_and_info_result.dart | 11 +- .../api/model/get_wallet_status_result.dart | 3 +- cw_zano/lib/api/model/recent_history.dart | 12 +- cw_zano/lib/api/model/store_result.dart | 2 +- cw_zano/lib/api/model/subtransfer.dart | 3 +- cw_zano/lib/api/model/transfer.dart | 44 +- cw_zano/lib/api/model/transfer_params.dart | 21 +- cw_zano/lib/api/model/wi_extended.dart | 18 +- cw_zano/lib/mnemonics/english.dart | 3252 ++++++++-------- .../lib/model/pending_zano_transaction.dart | 2 +- cw_zano/lib/model/zano_asset.dart | 1 + .../zano_transaction_creation_exception.dart | 2 +- .../model/zano_transaction_credentials.dart | 3 +- cw_zano/lib/model/zano_transaction_info.dart | 15 +- cw_zano/lib/model/zano_wallet_keys.dart | 8 +- cw_zano/lib/zano_formatter.dart | 54 +- cw_zano/lib/zano_transaction_history.dart | 10 +- cw_zano/lib/zano_wallet.dart | 19 +- cw_zano/lib/zano_wallet_api.dart | 61 +- cw_zano/lib/zano_wallet_exceptions.dart | 6 +- cw_zano/lib/zano_wallet_service.dart | 35 +- cw_zano/pubspec.lock | 8 +- lib/anonpay/anonpay_api.dart | 4 +- lib/anonpay/anonpay_donation_link_info.dart | 6 +- lib/anonpay/anonpay_info_base.dart | 2 +- lib/anypay/any_pay_chain.dart | 8 +- lib/anypay/any_pay_payment.dart | 108 +- .../any_pay_payment_committed_info.dart | 24 +- lib/anypay/any_pay_payment_instruction.dart | 49 +- .../any_pay_payment_instruction_output.dart | 14 +- lib/anypay/any_pay_trasnaction.dart | 10 +- lib/anypay/anypay_api.dart | 162 +- lib/bitcoin/cw_bitcoin.dart | 38 +- lib/bitcoin_cash/cw_bitcoin_cash.dart | 8 +- lib/buy/buy_amount.dart | 13 +- lib/buy/buy_exception.dart | 3 +- lib/buy/buy_provider.dart | 17 +- lib/buy/dfx/dfx_buy_provider.dart | 17 +- lib/buy/get_buy_provider_icon.dart | 11 +- lib/buy/kryptonim/kryptonim.dart | 19 +- lib/buy/meld/meld_buy_provider.dart | 32 +- lib/buy/moonpay/moonpay_provider.dart | 23 +- lib/buy/onramper/onramper_buy_provider.dart | 72 +- lib/buy/robinhood/robinhood_buy_provider.dart | 5 +- lib/buy/sell_buy_states.dart | 3 +- lib/buy/wyre/wyre_buy_provider.dart | 16 +- .../src/auth/cake_pay_account_page.dart | 15 +- .../src/auth/cake_pay_verify_otp_page.dart | 4 +- lib/cake_pay/src/cake_pay_states.dart | 1 - .../src/cards/cake_pay_buy_card_page.dart | 17 +- .../src/cards/cake_pay_cards_page.dart | 4 +- lib/cake_pay/src/models/cake_pay_card.dart | 7 +- lib/cake_pay/src/models/cake_pay_order.dart | 31 +- .../src/models/cake_pay_user_credentials.dart | 8 +- lib/cake_pay/src/widgets/cake_pay_tile.dart | 12 +- .../widgets/denominations_amount_widget.dart | 14 +- .../src/widgets/enter_amount_widget.dart | 19 +- .../src/widgets/flip_card_widget.dart | 17 +- lib/cake_pay/src/widgets/link_extractor.dart | 2 +- .../widgets/rounded_overlay_cards_widget.dart | 15 +- .../three_checkbox_alert_content_widget.dart | 18 +- lib/cake_pay/src/widgets/user_card_item.dart | 9 +- .../address_lookup_provider.dart | 1 - .../address_resolver_service.dart | 3 +- .../address_resolver_utils.dart | 3 +- .../bip_353/bip_353_address_provider.dart | 16 +- .../bip_353/bip_353_record.dart | 3 +- lib/core/address_resolver/ens/ens_record.dart | 24 +- .../fio/fio_address_provider.dart | 30 +- .../lnurl_pay/lnurl_pay_address_provider.dart | 5 +- .../lnurl_pay/lnurlpay_record.dart | 2 +- .../mastodon/mastodon_api.dart | 2 - .../mastodon/mastodon_user.dart | 11 +- .../address_resolver/nostr/nostr_api.dart | 8 +- .../address_resolver/nostr/nostr_user.dart | 3 +- .../openalias/openalias_record.dart | 2 +- lib/core/address_resolver/parsed_address.dart | 2 +- .../twitter/twitter_address_provider.dart | 1 - .../address_resolver/twitter/twitter_api.dart | 4 +- .../unstoppable_address_provider.dart | 6 +- .../wellknown/wellknown_record.dart | 8 +- lib/core/address_resolver/yat/yat_record.dart | 2 +- .../address_resolver/yat/yat_service.dart | 15 +- lib/core/address_resolver/yat/yat_store.dart | 64 +- .../zano/zano_alias_address_provider.dart | 1 - .../zcash/zcash_names_record.dart | 3 +- lib/core/address_validator.dart | 57 +- lib/core/amount_validator.dart | 6 +- lib/core/auth_state.dart | 1 - lib/core/backup_service.dart | 47 +- lib/core/backup_service_v3.dart | 63 +- lib/core/csv_export_service.dart | 11 +- lib/core/email_validator.dart | 3 +- lib/core/execution_state.dart | 2 +- lib/core/fiat_conversion_service.dart | 18 +- lib/core/mnemonic_length.dart | 2 +- lib/core/monero_account_label_validator.dart | 8 +- .../open_cryptopay_service.dart | 22 +- lib/core/seed_validator.dart | 5 +- lib/core/selectable_option.dart | 3 - .../socks_proxy_node_address_validator.dart | 5 +- lib/core/template_validator.dart | 11 +- lib/core/universal_address_detector.dart | 2 +- lib/core/utilities.dart | 3 +- lib/core/validator.dart | 10 +- lib/core/wallet_creation_service.dart | 3 +- lib/core/wallet_creation_state.dart | 2 +- lib/core/wallet_loading_service.dart | 81 +- lib/di.dart | 474 ++- lib/dogecoin/cw_dogecoin.dart | 5 +- .../auto_generate_subaddress_status.dart | 12 +- lib/entities/balance_display_mode.dart | 9 +- lib/entities/biometric_auth.dart | 2 +- lib/entities/bitcoin_amount_display_mode.dart | 3 +- lib/entities/calculate_fiat_amount.dart | 4 +- lib/entities/calculate_fiat_amount_raw.dart | 2 +- lib/entities/contact.dart | 7 +- lib/entities/contact.part.dart | 4 +- lib/entities/contact_base.dart | 2 +- lib/entities/country.dart | 7 +- lib/entities/default_settings_migration.dart | 33 +- lib/entities/emoji_string_extension.dart | 8 +- .../evm_transaction_error_fees_handler.dart | 18 +- lib/entities/exchange_api_mode.dart | 2 +- lib/entities/fiat_currency.dart | 30 +- lib/entities/format_amount.dart | 6 +- .../require_hardware_wallet_connection.dart | 7 +- lib/entities/haven_seed_store.part.dart | 4 +- lib/entities/ios_legacy_helper.dart | 18 +- .../new_ui_entities/list_item/list_item.dart | 6 +- .../list_item/list_item_selector.dart | 2 +- .../list_item/list_item_text_field.dart | 19 +- lib/entities/node_check.dart | 23 +- lib/entities/node_list.dart | 1 + lib/entities/pin_code_required_duration.dart | 2 +- lib/entities/preferences_key.dart | 3 +- lib/entities/provider_types.dart | 26 +- lib/entities/qr_scanner.dart | 3 +- lib/entities/qr_view_data.dart | 2 +- lib/entities/seed_phrase_length.dart | 3 +- lib/entities/seed_type.dart | 3 +- lib/entities/service_status.dart | 3 +- lib/entities/sort_balance_types.dart | 2 +- lib/entities/template.part.dart | 4 +- .../transaction_creation_credentials.dart | 2 +- lib/entities/transaction_description.dart | 13 +- .../transaction_description.part.dart | 3 +- lib/entities/transaction_history.dart | 3 +- lib/entities/wallet_description.dart | 4 +- lib/entities/wallet_manager.dart | 8 +- lib/entities/wallet_nft_response.dart | 4 +- .../exchange_provider_description.dart | 118 +- .../provider/chainflip_exchange_provider.dart | 75 +- .../provider/exolix_exchange_provider.dart | 46 +- .../provider/jupiter_exchange_provider.dart | 3 +- .../near_Intents_exchange_provider.dart | 75 +- .../provider/sideshift_exchange_provider.dart | 22 +- .../simpleswap_exchange_provider.dart | 25 +- .../provider/swapsxyz_exchange_provider.dart | 103 +- .../provider/swaptrade_exchange_provider.dart | 33 +- .../provider/thorchain_exchange.provider.dart | 78 +- .../provider/xoswap_exchange_provider.dart | 56 +- lib/exchange/trade_legacy.part.dart | 10 +- lib/haven/cw_haven.dart | 1 - lib/locales/hausa_intl.dart | 9 +- lib/locales/yoruba_intl.dart | 7 +- lib/main.dart | 7 +- lib/monero/cw_monero.dart | 33 +- lib/new-ui/modal_navigator.dart | 39 +- lib/new-ui/pages/about_page.dart | 2 +- lib/new-ui/pages/account_customizer.dart | 12 +- lib/new-ui/pages/addresses_page.dart | 3 +- .../pages/bridge/bridge_confirm_sheet.dart | 6 +- .../pages/bridge/bridge_network_page.dart | 6 +- .../bridge_receive_address_input_page.dart | 8 +- lib/new-ui/pages/card_customizer.dart | 3 +- lib/new-ui/pages/coin_control_page.dart | 22 +- lib/new-ui/pages/home_page.dart | 173 +- lib/new-ui/pages/lightning_username_page.dart | 16 +- lib/new-ui/pages/receive_page.dart | 57 +- lib/new-ui/pages/scan_page.dart | 10 +- lib/new-ui/pages/send_page.dart | 63 +- lib/new-ui/pages/settings_page.dart | 65 +- lib/new-ui/pages/swap_page.dart | 47 +- .../card_customizer/card_customizer_bloc.dart | 18 +- .../card_customizer_event.dart | 2 - .../card_customizer_state.dart | 32 +- .../lightning_username_bloc.dart | 6 +- .../lightning_username_event.dart | 2 +- .../widgets/addresses_page/address_info.dart | 3 +- .../addresses_page/address_label_input.dart | 3 +- lib/new-ui/widgets/animated_dropdown.dart | 33 +- lib/new-ui/widgets/apps_widget.dart | 14 +- .../widgets/bridge/confirm_details_card.dart | 5 +- .../widgets/bridge/transfer_history_row.dart | 6 +- lib/new-ui/widgets/changelog_modal.dart | 2 +- .../coin_control_list_item.dart | 136 +- .../action_row/coin_action_row.dart | 135 +- .../coins_page/assets_history/asset_tile.dart | 22 +- .../assets_history_section.dart | 44 +- .../assets_history/assets_section.dart | 102 +- .../assets_history/assets_top_bar.dart | 107 +- .../assets_history/history_filters_page.dart | 9 +- .../assets_history/history_modal.dart | 6 +- .../assets_history/history_order_tile.dart | 26 +- .../assets_history/history_section.dart | 330 +- .../history_swap_providers_page.dart | 6 +- .../assets_history/history_tile.dart | 60 +- .../assets_history/history_tile_base.dart | 49 +- .../assets_history/history_top_bar.dart | 20 +- .../assets_history/history_trade_tile.dart | 21 +- .../transaction_details_modal.dart | 70 +- .../coins_page/cards/balance_card.dart | 82 +- .../widgets/coins_page/cards/cards_view.dart | 84 +- lib/new-ui/widgets/coins_page/mweb_ad.dart | 4 +- .../coins_page/top_bar_widget/chain_icon.dart | 6 +- .../top_bar_widget/lightning_switcher.dart | 8 +- .../top_bar_widget/pulsing_dot.dart | 12 +- .../coins_page/top_bar_widget/sync_bar.dart | 25 +- .../coins_page/top_bar_widget/top_bar.dart | 6 +- .../unconfirmed_balance_widget.dart | 127 +- .../widgets/coins_page/wallet_info.dart | 14 +- lib/new-ui/widgets/confirm_swiper.dart | 16 +- lib/new-ui/widgets/copy_wrapper.dart | 2 +- .../currency_picker/currency_picker_args.dart | 8 +- .../picker_recents_loader.dart | 4 +- .../single_network_currency_picker.dart | 6 +- lib/new-ui/widgets/dropdown_row.dart | 3 +- lib/new-ui/widgets/keyboard_hide_overlay.dart | 3 +- lib/new-ui/widgets/line_tab_switcher.dart | 80 +- lib/new-ui/widgets/long_press_menu.dart | 3 +- lib/new-ui/widgets/modal_header.dart | 12 +- lib/new-ui/widgets/modal_page_wrapper.dart | 16 +- lib/new-ui/widgets/modern_button.dart | 2 +- lib/new-ui/widgets/new_primary_button.dart | 33 +- lib/new-ui/widgets/picker.dart | 147 +- .../receive_page/payjoin_copy_modal.dart | 2 +- .../receive_page/receive_address_type.dart | 3 +- .../receive_address_type_selector.dart | 11 +- .../receive_page/receive_amount_display.dart | 108 +- .../receive_page/receive_amount_modal.dart | 16 +- .../receive_page/receive_bottom_buttons.dart | 49 +- .../receive_page/receive_info_box.dart | 45 +- .../receive_page/receive_label_modal.dart | 12 +- .../receive_page/receive_label_widget.dart | 3 +- .../receive_large_amount_preview.dart | 3 +- .../widgets/receive_page/receive_qr_code.dart | 14 +- .../receive_page/receive_token_display.dart | 11 +- .../widgets/receive_page/receive_top_bar.dart | 50 +- .../widgets/send_page/fiat_amount_bar.dart | 16 +- .../send_page/floating_icon_button.dart | 16 +- .../send_page/l2_send_external_modal.dart | 39 +- .../widgets/send_page/recipient_dot_row.dart | 26 +- .../widgets/send_page/send_address_input.dart | 2 +- .../widgets/send_page/send_amount_input.dart | 30 +- .../send_page/send_confirm_bottom_widget.dart | 4 +- .../widgets/send_page/send_confirm_sheet.dart | 131 +- .../widgets/send_page/send_memo_input.dart | 3 +- .../send_page/send_syncing_indicator.dart | 35 +- .../swap_page/provider_options_page.dart | 14 +- .../swap_page/refund_address_modal.dart | 8 +- .../swap_address_selection_modal.dart | 81 +- .../widgets/swap_page/swap_options_page.dart | 17 +- ...wap_provider_initial_preference_modal.dart | 2 +- .../swap_page/swap_send_external_modal.dart | 10 +- .../trocador_providers_settings.dart | 3 +- lib/order/order.part.dart | 4 +- lib/order/order_provider_description.dart | 4 +- lib/order/order_source_description.dart | 2 +- lib/reactions/bootstrap.dart | 2 +- lib/reactions/fiat_rate_update.dart | 3 +- .../on_authentication_state_change.dart | 3 +- .../on_current_fiat_api_mode_change.dart | 17 +- lib/reactions/on_current_fiat_change.dart | 17 +- lib/reactions/on_current_wallet_change.dart | 3 +- lib/router.dart | 34 +- lib/solana/cw_solana.dart | 4 +- lib/src/screens/Info_page.dart | 2 +- lib/src/screens/auth/auth_page.dart | 8 +- lib/src/screens/backup/backup_page.dart | 11 +- .../backup/edit_backup_password_page.dart | 5 +- lib/src/screens/buy/buy_sell_page.dart | 17 +- lib/src/screens/buy/webview_page.dart | 5 +- .../connect_device/connect_device_page.dart | 3 +- .../monero_hardware_wallet_options_page.dart | 2 +- .../connect_device/widgets/device_tile.dart | 6 +- .../screens/contact/contact_list_page.dart | 60 +- lib/src/screens/contact/contact_page.dart | 33 +- lib/src/screens/dashboard/dashboard_page.dart | 7 +- .../dashboard/desktop_dashboard_page.dart | 2 +- .../desktop_action_button.dart | 14 +- .../desktop_dashboard_actions.dart | 114 +- .../desktop_dashboard_navbar.dart | 3 +- .../desktop_sidebar/side_menu.dart | 2 +- .../desktop_wallet_selection_dropdown.dart | 35 +- .../dashboard/favorite_token_modal.dart | 5 +- .../dashboard/pages/balance/balance_page.dart | 3 +- .../pages/balance/crypto_balance_widget.dart | 10 +- .../dashboard/pages/cake_features_page.dart | 66 +- .../dashboard/pages/navigation_dock.dart | 27 +- .../dashboard/pages/nft_listing_page.dart | 6 +- .../dashboard/pages/transactions_page.dart | 9 +- lib/src/screens/dashboard/sign_page.dart | 14 +- .../dashboard/widgets/action_button.dart | 3 +- .../dashboard/widgets/date_section_raw.dart | 4 +- .../dashboard/widgets/filter_tile.dart | 2 +- .../dashboard/widgets/filter_widget.dart | 19 +- .../screens/dashboard/widgets/header_row.dart | 8 +- .../widgets/new_main_navbar_widget.dart | 193 +- .../screens/dashboard/widgets/order_row.dart | 20 +- .../dashboard/widgets/page_indicator.dart | 64 +- .../screens/dashboard/widgets/sign_form.dart | 10 +- .../widgets/solana_nft_tile_widget.dart | 14 +- .../dashboard/widgets/sync_indicator.dart | 3 +- .../dashboard/widgets/transaction_raw.dart | 17 +- .../dashboard/widgets/verify_form.dart | 2 +- lib/src/screens/dev/moneroc_cache_debug.dart | 45 +- .../screens/dev/moneroc_call_profiler.dart | 25 +- lib/src/screens/dev/network_requests.dart | 58 +- lib/src/screens/dev/qr_tools_page.dart | 17 +- .../screens/dev/secure_preferences_page.dart | 22 +- .../screens/dev/shared_preferences_page.dart | 73 +- .../screens/disclaimer/disclaimer_page.dart | 2 +- lib/src/screens/exchange/exchange_page.dart | 10 +- .../widgets/currency_picker_widget.dart | 2 +- .../mobile_exchange_cards_section.dart | 4 +- .../screens/exchange/widgets/picker_item.dart | 5 +- .../exchange_trade_external_send_page.dart | 6 +- .../exchange_trade/exchange_trade_item.dart | 2 +- .../exchange_trade/exchange_trade_page.dart | 19 +- .../exchange_trade/widgets/timer_widget.dart | 6 +- .../integrations/deuro/savings_page.dart | 6 +- .../integrations/deuro/widgets/numpad.dart | 14 +- .../monero_accounts/widgets/account_tile.dart | 2 +- .../wallet_group_description_page.dart | 2 +- ..._group_existing_seed_description_page.dart | 17 +- .../new_wallet/widgets/select_button.dart | 20 +- .../nodes/node_create_or_edit_page.dart | 104 +- .../nodes/pow_node_create_or_edit_page.dart | 43 +- lib/src/screens/nodes/widgets/node_form.dart | 183 +- .../screens/nodes/widgets/node_list_row.dart | 53 +- lib/src/screens/pin_code/pin_code.dart | 8 +- lib/src/screens/pin_code/pin_code_widget.dart | 6 +- .../widgets/anonpay_status_section.dart | 24 +- .../receive/widgets/copy_link_item.dart | 4 +- .../release_notes/release_notes_screen.dart | 20 +- lib/src/screens/rescan/rescan_page.dart | 1 - .../restore/restore_from_backup_page.dart | 16 +- .../screens/restore/restore_options_page.dart | 10 +- .../wallet_restore_choose_derivation.dart | 7 +- .../wallet_restore_from_keys_form.dart | 38 +- .../wallet_restore_from_seed_form.dart | 2 +- .../screens/restore/wallet_restore_page.dart | 5 +- lib/src/screens/root/root.dart | 2 - .../seed_verification_success_view.dart | 16 +- lib/src/screens/seed/wallet_seed_page.dart | 19 +- lib/src/screens/send/send_page.dart | 665 ++-- .../widgets/choose_yat_address_alert.dart | 11 +- lib/src/screens/send/widgets/send_card.dart | 17 +- lib/src/screens/settings/attributes.dart | 2 +- .../settings/connection_sync_page.dart | 311 +- .../desktop_settings_page.dart | 12 +- .../settings/display_settings_page.dart | 350 +- .../screens/settings/domain_lookups_page.dart | 12 +- .../screens/settings/items/item_headers.dart | 2 +- .../screens/settings/manage_nodes_page.dart | 21 +- lib/src/screens/settings/mweb_logs_page.dart | 12 +- lib/src/screens/settings/mweb_node_page.dart | 41 +- lib/src/screens/settings/mweb_settings.dart | 2 +- .../screens/settings/other_settings_page.dart | 137 +- lib/src/screens/settings/privacy_page.dart | 92 +- .../settings/security_backup_page.dart | 21 +- .../settings/silent_payments_logs_page.dart | 9 +- .../settings/silent_payments_settings.dart | 25 +- .../widgets/settings_choices_cell.dart | 7 +- .../settings/widgets/settings_picker_row.dart | 2 +- .../widgets/settings_theme_choice.dart | 52 +- .../widgets/wallet_connect_button.dart | 4 +- .../setup_2fa/setup_2fa_enter_code_page.dart | 12 +- .../widgets/popup_cancellable_alert.dart | 11 +- .../setup_pin_code/setup_pin_code.dart | 13 +- lib/src/screens/splash/splash_page.dart | 6 +- lib/src/screens/start_tor/start_tor_page.dart | 4 +- .../support_chat/support_chat_page.dart | 24 +- .../support_chat/widgets/chatwoot_widget.dart | 7 +- .../trade_details/track_trade_list_item.dart | 5 +- .../trade_details_list_card.dart | 12 +- .../trade_details/trade_details_page.dart | 8 +- .../trade_details_status_item.dart | 3 +- .../address_list_item.dart | 2 +- .../confirmations_list_item.dart | 6 +- .../transaction_details/rbf_details_page.dart | 3 +- .../textfield_list_item.dart | 2 +- .../transaction_details_page.dart | 32 +- .../transaction_expandable_list_item.dart | 2 +- .../unspent_coins_list_page.dart | 82 +- .../widgets/unspent_coins_list_item.dart | 48 +- lib/src/screens/ur/animated_ur_page.dart | 12 +- .../widgets/qr_format_info_bottom_sheet.dart | 5 +- .../ur/widgets/qr_selection_dialog.dart | 9 +- lib/src/screens/ur/widgets/urqr.dart | 14 +- .../eth/evm_supported_methods.dart | 2 +- .../services/walletkit_service.dart | 39 +- .../wallet_connect/utils/method_utils.dart | 13 +- .../utils/wc_permissions_mapper.dart | 3 +- .../wc_connections_listing_view.dart | 2 +- .../enter_wallet_connect_uri_widget.dart | 4 +- .../wallet_connect/widgets/wc_hero_card.dart | 1 - .../screens/wallet_keys/wallet_keys_page.dart | 2 +- .../screens/wallet_list/wallet_list_page.dart | 86 +- .../wallet_unlock_arguments.dart | 5 +- .../welcome/create_pin_welcome_page.dart | 3 +- lib/src/screens/welcome/welcome_page.dart | 10 +- .../yat/widgets/first_introduction.dart | 95 +- .../yat/widgets/second_introduction.dart | 64 +- .../yat/widgets/third_introduction.dart | 22 +- lib/src/screens/yat/widgets/yat_bar.dart | 20 +- .../yat/widgets/yat_page_indicator.dart | 10 +- lib/src/widgets/adaptable_page_view.dart | 3 +- lib/src/widgets/alert_with_picker_option.dart | 1 - lib/src/widgets/base_alert_dialog.dart | 97 +- lib/src/widgets/base_text_form_field.dart | 3 +- lib/src/widgets/blockchain_height_widget.dart | 2 +- ...ake_pay_transaction_sent_bottom_sheet.dart | 21 +- .../confirm_sending_bottom_sheet_widget.dart | 3 +- .../info_bottom_sheet_widget.dart | 4 +- .../info_steps_bottom_sheet_widget.dart | 22 +- .../payment_confirmation_bottom_sheet.dart | 3 +- .../swap_confirmation_bottom_sheet.dart | 3 +- .../swap_details_bottom_sheet.dart | 11 +- .../token_selection_bottom_sheet.dart | 13 +- lib/src/widgets/check_box_picker.dart | 11 +- lib/src/widgets/checkbox_widget.dart | 1 + lib/src/widgets/evm_switcher.dart | 3 +- .../widgets/haven_wallet_removal_popup.dart | 20 +- lib/src/widgets/index.dart | 1 + .../new_list_row/list_Item_style_wrapper.dart | 40 +- .../list_item_checkbox_widget.dart | 64 +- .../list_item_regular_row_widget.dart | 2 +- .../list_item_selector_widget.dart | 13 +- .../list_item_text_field_widget.dart | 5 +- .../new_list_row/list_item_toggle_widget.dart | 3 +- .../new_list_row/new_list_section.dart | 17 +- lib/src/widgets/number_text_fild_widget.dart | 10 +- lib/src/widgets/picker.dart | 3 +- .../widgets/picker_inner_wrapper_widget.dart | 10 +- lib/src/widgets/provider_optoin_tile.dart | 4 +- lib/src/widgets/rounded_checkbox.dart | 28 +- lib/src/widgets/rounded_icon_button.dart | 2 +- .../scrollable_with_bottom_section.dart | 2 +- lib/src/widgets/seed_widget.dart | 13 +- lib/src/widgets/seedphrase_grid_widget.dart | 8 +- lib/src/widgets/simple_checkbox.dart | 6 +- lib/src/widgets/standard_checkbox.dart | 11 +- lib/src/widgets/standard_list.dart | 19 +- lib/src/widgets/standard_list_status_row.dart | 6 +- .../widgets/standard_slide_button_widget.dart | 4 +- .../validable_annotated_editable_text.dart | 19 +- lib/src/widgets/vulnerable_seeds_popup.dart | 21 +- lib/store/app_store.dart | 1 - lib/store/dashboard/order_filter_store.dart | 5 +- .../dashboard/payjoin_transactions_store.dart | 3 +- lib/store/dashboard/trade_filter_store.dart | 52 +- lib/store/node_list_store.dart | 1 + lib/store/seed_settings_store.dart | 1 - lib/store/settings_store.dart | 160 +- lib/store/templates/send_template_store.dart | 6 +- lib/store/wallet_list_store.dart | 2 +- lib/themes/core/theme_store.dart | 4 +- .../light_theme_custom_colors.dart | 12 +- lib/tron/cw_tron.dart | 3 +- lib/utils/address_formatter.dart | 15 +- lib/utils/brightness_util.dart | 2 +- lib/utils/clipboard_util.dart | 3 +- lib/utils/date_picker.dart | 26 +- lib/utils/debounce.dart | 6 +- lib/utils/device_info.dart | 4 +- lib/utils/feature_flag.dart | 3 +- lib/utils/item_cell.dart | 4 +- lib/utils/list_item.dart | 2 +- lib/utils/list_section.dart | 2 +- lib/utils/mobx.dart | 12 +- lib/utils/package_info.dart | 77 +- lib/utils/tor.dart | 16 +- lib/utils/totp_utils.dart | 2 +- lib/view_model/animated_ur_model.dart | 5 +- lib/view_model/auth_state.dart | 1 - lib/view_model/auth_view_model.dart | 2 +- lib/view_model/backup_view_model.dart | 6 +- .../bridge/bridge_details_view_model.dart | 4 +- lib/view_model/bridge/bridge_view_model.dart | 20 +- lib/view_model/buy/buy_amount_view_model.dart | 4 +- lib/view_model/buy/buy_item.dart | 5 +- lib/view_model/buy/buy_sell_view_model.dart | 18 +- .../cake_pay_buy_card_view_model.dart | 7 +- .../cake_pay_cards_list_view_model.dart | 1 - .../contact_list/contact_view_model.dart | 26 +- .../dashboard/action_list_item.dart | 2 +- .../dashboard/dashboard_view_model.dart | 33 +- lib/view_model/dashboard/filter_item.dart | 19 +- .../dashboard/home_settings_view_model.dart | 24 +- lib/view_model/dashboard/order_list_item.dart | 2 +- .../dashboard/receive_option_view_model.dart | 1 - .../dashboard/transaction_list_item.dart | 4 +- lib/view_model/dashboard/wallet_balance.dart | 2 +- .../dev/background_sync_logs_view_model.dart | 8 +- .../exchange_provider_logs_view_model.dart | 5 +- .../dev/network_requests_view_model.dart | 2 +- lib/view_model/dev/qr_tools_view_model.dart | 26 +- lib/view_model/dev/secure_preferences.dart | 19 +- .../dev/send_network_requests_view_model.dart | 5 +- lib/view_model/dev/shared_preferences.dart | 9 +- .../dev/socket_health_logs_view_model.dart | 1 - .../edit_backup_password_view_model.dart | 9 +- .../exchange/exchange_trade_view_model.dart | 38 +- .../exchange/exchange_view_model.dart | 93 +- .../hardware_wallet/bitbox_view_model.dart | 5 +- .../hardware_wallet_view_model.dart | 1 - .../hardware_wallet/ledger_view_model.dart | 20 +- .../trezor_connect_view_model.dart | 17 +- .../integrations/deuro_view_model.dart | 5 +- .../account_list_item.dart | 3 +- ...ero_account_edit_or_create_view_model.dart | 23 +- .../monero_account_list_view_model.dart | 43 +- .../node_create_or_edit_view_model.dart | 31 +- .../node_list/node_list_view_model.dart | 34 +- .../node_list/pow_node_list_view_model.dart | 1 + .../payjoin_details_view_model.dart | 6 +- lib/view_model/rescan_view_model.dart | 2 +- lib/view_model/restore/restore_mode.dart | 2 +- .../restore_from_backup_view_model.dart | 5 +- lib/view_model/seed_settings_view_model.dart | 3 +- lib/view_model/send/output.dart | 15 +- .../send/send_template_view_model.dart | 4 +- lib/view_model/send/send_view_model.dart | 144 +- .../send/send_view_model_state.dart | 3 + .../settings/connection_sync_view_model.dart | 6 +- .../settings/display_settings_view_model.dart | 5 +- lib/view_model/settings/link_list_item.dart | 12 +- .../settings/other_settings_view_model.dart | 18 +- .../settings/privacy_settings_view_model.dart | 6 +- .../settings/regular_list_item.dart | 2 +- .../security_settings_view_model.dart | 7 +- .../settings/switcher_list_item.dart | 5 +- .../trocador_providers_view_model.dart | 5 +- .../settings/version_list_item.dart | 2 +- lib/view_model/setup_pin_code_view_model.dart | 5 +- lib/view_model/start_tor_view_model.dart | 13 +- lib/view_model/trade_details_view_model.dart | 24 +- .../transaction_details_view_model.dart | 52 +- .../unspent_coins_details_view_model.dart | 3 +- .../unspent_coins/unspent_coins_item.dart | 28 +- .../unspent_coins_list_view_model.dart | 10 +- .../unspent_coins_switch_item.dart | 13 +- .../wallet_account_list_header.dart | 2 +- .../wallet_address_hidden_list_header.dart | 2 +- .../wallet_address_list_item.dart | 30 +- .../wallet_address_list_view_model.dart | 27 +- .../wallet_address_util.dart | 10 +- lib/view_model/wallet_creation_vm.dart | 13 +- .../wallet_groups_display_view_model.dart | 23 +- .../wallet_hardware_restore_view_model.dart | 17 +- .../wallet_list/wallet_list_view_model.dart | 7 +- lib/view_model/wallet_restore_view_model.dart | 26 +- lib/view_model/wallet_seed_view_model.dart | 1 - lib/wallet_type_utils.dart | 18 +- lib/wownero/cw_wownero.dart | 12 +- lib/zano/cw_zano.dart | 34 +- lib/zcash/cw_zcash.dart | 39 +- 774 files changed, 14907 insertions(+), 14118 deletions(-) diff --git a/cw_bitcoin/lib/address_from_output.dart b/cw_bitcoin/lib/address_from_output.dart index 0d985b2370..072a92dc29 100644 --- a/cw_bitcoin/lib/address_from_output.dart +++ b/cw_bitcoin/lib/address_from_output.dart @@ -17,21 +17,16 @@ BitcoinBaseAddress addressFromScript(Script script, switch (addressType) { case P2pkhAddressType.p2pkh: - return P2pkhAddress.fromScriptPubkey( - script: script, network: BitcoinNetwork.mainnet); + return P2pkhAddress.fromScriptPubkey(script: script, network: BitcoinNetwork.mainnet); case P2shAddressType.p2pkhInP2sh: case P2shAddressType.p2pkInP2sh: - return P2shAddress.fromScriptPubkey( - script: script, network: BitcoinNetwork.mainnet); + return P2shAddress.fromScriptPubkey(script: script, network: BitcoinNetwork.mainnet); case SegwitAddresType.p2wpkh: - return P2wpkhAddress.fromScriptPubkey( - script: script, network: BitcoinNetwork.mainnet); + return P2wpkhAddress.fromScriptPubkey(script: script, network: BitcoinNetwork.mainnet); case SegwitAddresType.p2wsh: - return P2wshAddress.fromScriptPubkey( - script: script, network: BitcoinNetwork.mainnet); + return P2wshAddress.fromScriptPubkey(script: script, network: BitcoinNetwork.mainnet); case SegwitAddresType.p2tr: - return P2trAddress.fromScriptPubkey( - script: script, network: BitcoinNetwork.mainnet); + return P2trAddress.fromScriptPubkey(script: script, network: BitcoinNetwork.mainnet); } throw ArgumentError("Invalid script"); diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart index d6de05051b..65b728904b 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -72,17 +72,16 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord { required super.type, String? scriptHash, required super.network, - }) { + }) { try { this.scriptHash = scriptHash ?? - (network != null ? BitcoinAddressUtils.scriptHash(address, network: network!) : null); + (network != null ? BitcoinAddressUtils.scriptHash(address, network: network!) : null); } catch (e) { printV(e); } -} + } static bool _legacyDefaultForType(BitcoinAddressType type) { - // Some address types (p2wpkh, p2wsh) were historically derived from the same account/path as our new standard // but using legacy formats. For these, we default to legacy = false. if (type == SegwitAddresType.p2wpkh || type == SegwitAddresType.p2wsh) return false; @@ -154,6 +153,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord { } return scriptHash!; } + @override String get derivationPath { if (type == SegwitAddresType.mweb) { @@ -162,9 +162,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord { final coinType = _coinTypeForNetwork(); final purpose = _purposeForType(type); - final accountPath = isLegacyDerivation - ? electrum_path - : "m/$purpose'/$coinType'/0'"; + final accountPath = isLegacyDerivation ? electrum_path : "m/$purpose'/$coinType'/0'"; final chain = isHidden ? 1 : 0; return "$accountPath/$chain/$index"; diff --git a/cw_bitcoin/lib/bitcoin_amount_format.dart b/cw_bitcoin/lib/bitcoin_amount_format.dart index d5a42d984b..dd73635695 100644 --- a/cw_bitcoin/lib/bitcoin_amount_format.dart +++ b/cw_bitcoin/lib/bitcoin_amount_format.dart @@ -7,8 +7,8 @@ final bitcoinAmountFormat = NumberFormat() ..maximumFractionDigits = bitcoinAmountLength ..minimumFractionDigits = 1; -String bitcoinAmountToString({required int amount}) => bitcoinAmountFormat.format( - cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider)); +String bitcoinAmountToString({required int amount}) => + bitcoinAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider)); double bitcoinAmountToDouble({required int amount}) => cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider); diff --git a/cw_bitcoin/lib/bitcoin_commit_transaction_exception.dart b/cw_bitcoin/lib/bitcoin_commit_transaction_exception.dart index 7bf488f3f1..ffadb65ae3 100644 --- a/cw_bitcoin/lib/bitcoin_commit_transaction_exception.dart +++ b/cw_bitcoin/lib/bitcoin_commit_transaction_exception.dart @@ -5,4 +5,3 @@ class BitcoinCommitTransactionException implements Exception { @override String toString() => errorMessage; } - diff --git a/cw_bitcoin/lib/bitcoin_transaction_priority.dart b/cw_bitcoin/lib/bitcoin_transaction_priority.dart index 46e4ca8e43..e38cbce305 100644 --- a/cw_bitcoin/lib/bitcoin_transaction_priority.dart +++ b/cw_bitcoin/lib/bitcoin_transaction_priority.dart @@ -2,7 +2,8 @@ import 'package:cw_core/transaction_priority.dart'; import 'package:flutter/foundation.dart'; class BitcoinTransactionPriority extends TransactionPriority { - const BitcoinTransactionPriority({required String title, required int raw, String? description, String? hint}) + const BitcoinTransactionPriority( + {required String title, required int raw, String? description, String? hint}) : super(title: title, raw: raw, description: description, hint: hint); static const List all = [fast, medium, slow, custom]; @@ -10,10 +11,10 @@ class BitcoinTransactionPriority extends TransactionPriority { BitcoinTransactionPriority(title: 'Slow', description: "2 sat/byte", hint: "~ 24 h", raw: 0); static const BitcoinTransactionPriority medium = BitcoinTransactionPriority(title: 'Medium', description: "3 sat/byte", hint: "~ 1 h", raw: 1); - static const BitcoinTransactionPriority fast = - BitcoinTransactionPriority(title: 'Fast', description: "4 sat/byte", hint: "~ 30 min", raw: 2); + static const BitcoinTransactionPriority fast = BitcoinTransactionPriority( + title: 'Fast', description: "4 sat/byte", hint: "~ 30 min", raw: 2); static const BitcoinTransactionPriority custom = - BitcoinTransactionPriority(title: 'Custom', raw: 3); + BitcoinTransactionPriority(title: 'Custom', raw: 3); static BitcoinTransactionPriority deserialize({required int raw}) { switch (raw) { @@ -116,19 +117,19 @@ class LitecoinTransactionPriority extends BitcoinTransactionPriority { return label; } - } + class BitcoinCashTransactionPriority extends BitcoinTransactionPriority { const BitcoinCashTransactionPriority({required String title, required int raw}) : super(title: title, raw: raw); static const List all = [fast, medium, slow]; static const BitcoinCashTransactionPriority slow = - BitcoinCashTransactionPriority(title: 'Slow', raw: 0); + BitcoinCashTransactionPriority(title: 'Slow', raw: 0); static const BitcoinCashTransactionPriority medium = - BitcoinCashTransactionPriority(title: 'Medium', raw: 1); + BitcoinCashTransactionPriority(title: 'Medium', raw: 1); static const BitcoinCashTransactionPriority fast = - BitcoinCashTransactionPriority(title: 'Fast', raw: 2); + BitcoinCashTransactionPriority(title: 'Fast', raw: 2); static BitcoinCashTransactionPriority deserialize({required int raw}) { switch (raw) { @@ -170,4 +171,3 @@ class BitcoinCashTransactionPriority extends BitcoinTransactionPriority { return label; } } - diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index a0d88ce9ac..7e76132a88 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -140,7 +140,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { autorun((_) { this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; }); - + reaction((_) => this.useLightning, (bool useLightning) { if (useLightning && LightningWallet.isAvailable) { if (mnemonic != null) { @@ -306,28 +306,28 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { } return BitcoinWallet( - mnemonic: mnemonic, - xpub: keysData.xPub != null ? convertZpubToXpub(keysData.xPub!) : null, - password: password, - passphrase: passphrase, - walletInfo: walletInfo, - derivationInfo: derivationInfo, - unspentCoinsInfo: unspentCoinsInfo, - initialAddresses: snp?.addresses, - initialSilentAddresses: snp?.silentAddresses, - initialSilentAddressIndex: snp?.silentAddressIndex ?? 0, - initialBalance: snp?.balance, - initialLightningBalance: snp?.lightningBalance, - encryptionFileUtils: encryptionFileUtils, - seedBytes: seedBytes, - initialRegularAddressIndex: snp?.regularAddressIndex, - initialChangeAddressIndex: snp?.changeAddressIndex, - addressPageType: snp?.addressPageType, - networkParam: network, - alwaysScan: snp?.alwaysScan, - useLightning: snp?.useLightning, - cachedLightningAddress: snp?.cachedLightningAddress, - payjoinBox: payjoinBox, + mnemonic: mnemonic, + xpub: keysData.xPub != null ? convertZpubToXpub(keysData.xPub!) : null, + password: password, + passphrase: passphrase, + walletInfo: walletInfo, + derivationInfo: derivationInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: snp?.addresses, + initialSilentAddresses: snp?.silentAddresses, + initialSilentAddressIndex: snp?.silentAddressIndex ?? 0, + initialBalance: snp?.balance, + initialLightningBalance: snp?.lightningBalance, + encryptionFileUtils: encryptionFileUtils, + seedBytes: seedBytes, + initialRegularAddressIndex: snp?.regularAddressIndex, + initialChangeAddressIndex: snp?.changeAddressIndex, + addressPageType: snp?.addressPageType, + networkParam: network, + alwaysScan: snp?.alwaysScan, + useLightning: snp?.useLightning, + cachedLightningAddress: snp?.cachedLightningAddress, + payjoinBox: payjoinBox, ); } @@ -346,12 +346,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { } try { - final lBalance = await lightningWallet!.getBalance(); + final lBalance = await lightningWallet!.getBalance(); - this.balance[CryptoCurrency.btcln] = ElectrumBalance( - confirmed: lBalance, - unconfirmed: Money.zero(CryptoCurrency.btcln), - frozen: Money.zero(CryptoCurrency.btcln)); + this.balance[CryptoCurrency.btcln] = ElectrumBalance( + confirmed: lBalance, + unconfirmed: Money.zero(CryptoCurrency.btcln), + frozen: Money.zero(CryptoCurrency.btcln)); } catch (e) { printV("Error fetching lightning balance: $e"); } @@ -494,7 +494,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { @override Future createTransaction(Object credentials) async { credentials = credentials as BitcoinTransactionCredentials; - final lnAddr = credentials.outputs.first.isParsedAddress ? credentials.outputs.first.extractedAddress! : credentials.outputs.first.address; + final lnAddr = credentials.outputs.first.isParsedAddress + ? credentials.outputs.first.extractedAddress! + : credentials.outputs.first.address; final isLNCompatible = await lightningWallet?.isCompatible(lnAddr); if ((credentials.coinTypeToSpendFrom == UnspentCoinType.lightning && lightningWallet != null) || @@ -506,12 +508,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { amount = credentials.outputs.first.cryptoAmount; } - return lightningWallet!.createTransaction( - lnAddr, - amount.amount > BigInt.zero ? amount.amount : null, - credentials.priority, - credentials.outputs.first.sendAll,); + lnAddr, + amount.amount > BigInt.zero ? amount.amount : null, + credentials.priority, + credentials.outputs.first.sendAll, + ); } final tx = (await super.createTransaction(credentials)) as PendingBitcoinTransaction; diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index 6abce9689a..0bc7f8c9c8 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -137,7 +137,8 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S } @override - bool containsAddress(String address) => super.containsAddress(address) || address == lightningAddress; + bool containsAddress(String address) => + super.containsAddress(address) || address == lightningAddress; @override String get addressForBuy { @@ -153,12 +154,9 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S @override String get addressForExchange { - final current = getFreshAddress(); final availableReceiveAddresses = receiveAddresses.where((element) => - !element.isUsed && - !element.isHidden && - !hiddenAddresses.contains(element.address)); + !element.isUsed && !element.isHidden && !hiddenAddresses.contains(element.address)); final bool isSilentPaymentsPage = addressPageType == SilentPaymentsAddresType.p2sp; final bool isLightningPage = addressPageType == LightningAddressType.p2l; diff --git a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart index 479cf64164..658969c3e1 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart @@ -59,27 +59,27 @@ class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials { } class BitcoinWalletFromKeysCredentials extends WalletCredentials { - BitcoinWalletFromKeysCredentials({ - required String name, - required String password, - required this.xpub, - WalletInfo? walletInfo, - super.hardwareWalletType - }) : super(name: name, password: password, walletInfo: walletInfo); + BitcoinWalletFromKeysCredentials( + {required String name, + required String password, + required this.xpub, + WalletInfo? walletInfo, + super.hardwareWalletType}) + : super(name: name, password: password, walletInfo: walletInfo); final String xpub; } class LitecoinWalletFromKeysCredentials extends WalletCredentials { - LitecoinWalletFromKeysCredentials({ - required String name, - required String password, - required this.xpub, - required this.scanSecret, - required this.spendPubkey, - WalletInfo? walletInfo, - super.hardwareWalletType - }) : super(name: name, password: password, walletInfo: walletInfo); + LitecoinWalletFromKeysCredentials( + {required String name, + required String password, + required this.xpub, + required this.scanSecret, + required this.spendPubkey, + WalletInfo? walletInfo, + super.hardwareWalletType}) + : super(name: name, password: password, walletInfo: walletInfo); final String xpub; final String scanSecret; diff --git a/cw_bitcoin/lib/bitcoin_wallet_keys.dart b/cw_bitcoin/lib/bitcoin_wallet_keys.dart index 4ed0da49cc..9a9bdcba46 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_keys.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_keys.dart @@ -1,15 +1,12 @@ class BitcoinWalletKeys { - const BitcoinWalletKeys({required this.wif, required this.privateKey, required this.publicKey, required this.xpub}); + const BitcoinWalletKeys( + {required this.wif, required this.privateKey, required this.publicKey, required this.xpub}); final String wif; final String privateKey; final String publicKey; final String xpub; - Map toJson() => { - 'wif': wif, - 'privateKey': privateKey, - 'publicKey': publicKey, - 'xpub': xpub - }; -} \ No newline at end of file + Map toJson() => + {'wif': wif, 'privateKey': privateKey, 'publicKey': publicKey, 'xpub': xpub}; +} diff --git a/cw_bitcoin/lib/bitcoin_wallet_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart index 8a034d8cfe..836b006d26 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart @@ -21,8 +21,7 @@ class BitcoinWalletService extends WalletService< BitcoinRestoreWalletFromSeedCredentials, BitcoinWalletFromKeysCredentials, BitcoinRestoreWalletFromHardware> { - BitcoinWalletService(this.unspentCoinsInfoSource, - this.payjoinSessionSource, this.isDirect); + BitcoinWalletService(this.unspentCoinsInfoSource, this.payjoinSessionSource, this.isDirect); final Box unspentCoinsInfoSource; final Box payjoinSessionSource; @@ -38,9 +37,12 @@ class BitcoinWalletService extends WalletService< final String mnemonic; final derivationInfo = await credentials.walletInfo!.getDerivationInfo(); - derivationInfo.derivationType = credentials.derivationInfo?.derivationType ?? derivationInfo.derivationType; - derivationInfo.derivationPath = credentials.derivationInfo?.derivationPath ?? derivationInfo.derivationPath; - derivationInfo.description = credentials.derivationInfo?.description ?? derivationInfo.description; + derivationInfo.derivationType = + credentials.derivationInfo?.derivationType ?? derivationInfo.derivationType; + derivationInfo.derivationPath = + credentials.derivationInfo?.derivationPath ?? derivationInfo.derivationPath; + derivationInfo.description = + credentials.derivationInfo?.description ?? derivationInfo.description; derivationInfo.scriptType = credentials.derivationInfo?.scriptType ?? derivationInfo.scriptType; await derivationInfo.save(); switch (derivationInfo.derivationType) { @@ -119,8 +121,9 @@ class BitcoinWalletService extends WalletService< } await WalletInfo.delete(walletInfo); - final unspentCoinsToDelete = unspentCoinsInfoSource.values.where( - (unspentCoin) => unspentCoin.walletId == walletInfo.id).toList(); + final unspentCoinsToDelete = unspentCoinsInfoSource.values + .where((unspentCoin) => unspentCoin.walletId == walletInfo.id) + .toList(); final keysToDelete = unspentCoinsToDelete.map((unspentCoin) => unspentCoin.key).toList(); @@ -135,11 +138,10 @@ class BitcoinWalletService extends WalletService< final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet; credentials.walletInfo?.network = network.value; final derivationInfo = await credentials.walletInfo!.getDerivationInfo(); - derivationInfo.derivationPath = - credentials.hwAccountData.derivationPath; - + derivationInfo.derivationPath = credentials.hwAccountData.derivationPath; + final xpub = convertAnyToXpub(credentials.hwAccountData.xpub!); - + await credentials.walletInfo!.save(); final wallet = await BitcoinWallet( password: credentials.password!, diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 052d18ef0f..44aa096c4e 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -304,9 +304,9 @@ class ElectrumClient { }); Future>>> getBatchHistory( - List scriptHashes, { - int timeout = 10000, - }) async { + List scriptHashes, { + int timeout = 10000, + }) async { final paramsList = scriptHashes.map((h) => [h]).toList(growable: false); final batchResults = await callBatchWithTimeout( @@ -342,9 +342,9 @@ class ElectrumClient { } Future>>> getBatchUnspent( - List scriptHashes, { - int timeout = 10000, - }) async { + List scriptHashes, { + int timeout = 10000, + }) async { final paramsList = scriptHashes.map((h) => [h]).toList(growable: false); final batchResults = await callBatchWithTimeout( @@ -380,9 +380,9 @@ class ElectrumClient { } Future>> getBatchBalance( - List scriptHashes, { - int timeout = 10000, - }) async { + List scriptHashes, { + int timeout = 10000, + }) async { final paramsList = scriptHashes.map((h) => [h]).toList(growable: false); final batchResults = await callBatchWithTimeout( @@ -416,9 +416,9 @@ class ElectrumClient { } Future>> getBatchTransactionVerbose( - List hashes, { - int timeout = 10000, - }) async { + List hashes, { + int timeout = 10000, + }) async { final result = >{}; if (hashes.isEmpty) return result; @@ -443,9 +443,9 @@ class ElectrumClient { } Future> getBatchTransactionHex( - List hashes, { - int timeout = 10000, - }) async { + List hashes, { + int timeout = 10000, + }) async { final result = {}; if (hashes.isEmpty) return result; @@ -483,12 +483,8 @@ class ElectrumClient { // Build the Batch Array final List> batchPayload = []; for (int i = 0; i < paramsList.length; i++) { - batchPayload.add({ - "jsonrpc": "2.0", - "method": method, - "params": paramsList[i], - "id": "$batchBaseId-$i" - }); + batchPayload.add( + {"jsonrpc": "2.0", "method": method, "params": paramsList[i], "id": "$batchBaseId-$i"}); } // Register the task @@ -775,7 +771,6 @@ class ElectrumClient { } void _handleResponse(dynamic response) { - // Handle batch response if (response is List) { if (response.isEmpty) return; diff --git a/cw_bitcoin/lib/electrum_transaction_history.dart b/cw_bitcoin/lib/electrum_transaction_history.dart index 6457832ec9..5de131c77c 100644 --- a/cw_bitcoin/lib/electrum_transaction_history.dart +++ b/cw_bitcoin/lib/electrum_transaction_history.dart @@ -1,6 +1,5 @@ import 'dart:convert'; - import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/pathForWallet.dart'; diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index a58829e009..48c006ae24 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -184,15 +184,13 @@ class ElectrumTransactionInfo extends TransactionInfo { // MWEB HogEx final isHogExTx = (BtcTransaction tx) { - if (tx.inputs.isEmpty || tx.inputs.first.txIndex > 0 || tx.outputs.isEmpty) - return false; + if (tx.inputs.isEmpty || tx.inputs.first.txIndex > 0 || tx.outputs.isEmpty) return false; final b = tx.outputs.first.scriptPubKey.toBytes(); return b.length == 34 && b[0] == 88 && b[1] == 32; }; final firstInput = bundle.ins.isNotEmpty ? bundle.ins.first : null; - final isHogEx = firstInput != null && - isHogExTx(bundle.originalTransaction) && - isHogExTx(firstInput); + final isHogEx = + firstInput != null && isHogExTx(bundle.originalTransaction) && isHogExTx(firstInput); final fee = hasMissingInputTx ? null : inputAmount - totalOutAmount; final walletCurrency = walletTypeToCryptoCurrency(type); diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index ccd41d010f..bc7d45ec44 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -156,7 +156,6 @@ abstract class ElectrumWalletBase sideHdByType[type] = sideHd; } } - } int _purposeForType(BitcoinAddressType type) { @@ -195,7 +194,6 @@ abstract class ElectrumWalletBase /// For LEGACY addresses, returns the wallet's legacy derivation base (derivationInfo.derivationPath) /// which is already the account path used historically (e.g. m/0' or m/84'/0'/0'). String _accountDerivationPathForRecord(BaseBitcoinAddressRecord record) { - if (derivationInfo.derivationType == DerivationType.electrum) { return derivationInfo.derivationPath ?? electrum_path; // m/0' } @@ -807,7 +805,7 @@ abstract class ElectrumWalletBase node!.isElectrs = true; // TODO figure out why condition was needed // if (node!.isInBox) { - node!.save(); + node!.save(); // } return node!.isElectrs!; } @@ -873,7 +871,8 @@ abstract class ElectrumWalletBase BigInt get networkDustAmount => BigInt.from(546); - bool _isBelowDust(BigInt amount) => amount <= networkDustAmount && network != BitcoinNetwork.testnet; + bool _isBelowDust(BigInt amount) => + amount <= networkDustAmount && network != BitcoinNetwork.testnet; UtxoDetails _createUTXOS({ required bool sendAll, @@ -961,8 +960,7 @@ abstract class ElectrumWalletBase final baseDerivationPath = _accountDerivationPathForRecord(utx.bitcoinAddressRecord); - final derivationPath = - "${_hardenedDerivationPath(baseDerivationPath)}" + final derivationPath = "${_hardenedDerivationPath(baseDerivationPath)}" "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}" "/${utx.bitcoinAddressRecord.index}"; publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath); @@ -1139,7 +1137,8 @@ abstract class ElectrumWalletBase utxoDetails.utxos.length == utxoDetails.availableInputs.length - utxoDetails.unconfirmedCoins.length; - final amountLeftForChangeAndFee = utxoDetails.allInputsAmount - credentialsAmount.amount.toInt(); + final amountLeftForChangeAndFee = + utxoDetails.allInputsAmount - credentialsAmount.amount.toInt(); if (amountLeftForChangeAndFee <= 0) { if (!spendingAllCoins) { @@ -1175,11 +1174,9 @@ abstract class ElectrumWalletBase isChange: true, )); - // Must match the address' account root (purpose/coinType) and legacy derivation when applicable. final changeBaseDerivationPath = _accountDerivationPathForRecord(changeAddress); - final changeDerivationPath = - "${_hardenedDerivationPath(changeBaseDerivationPath)}" + final changeDerivationPath = "${_hardenedDerivationPath(changeBaseDerivationPath)}" "/${changeAddress.isHidden ? "1" : "0"}" "/${changeAddress.index}"; utxoDetails.publicKeys[address.pubKeyHash()] = @@ -1242,7 +1239,8 @@ abstract class ElectrumWalletBase inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, vinOutpoints: utxoDetails.vinOutpoints, ); - final leftover = utxoDetails.allInputsAmount - credentialsAmount.amount.toInt() - feeNoChange; + final leftover = + utxoDetails.allInputsAmount - credentialsAmount.amount.toInt() - feeNoChange; if (leftover >= 0) { final finalFee = feeNoChange + leftover; // absorb tiny remainder @@ -1827,16 +1825,15 @@ abstract class ElectrumWalletBase } Future?>> _fetchUnspentsRegular( - List addresses, - ) async { + List addresses, + ) async { final addressFutures = addresses.map((address) => fetchUnspent(address)).toList(); return Future.wait(addressFutures); } - Future?>> _fetchUnspentsBatch( - List addresses, - ) async { + List addresses, + ) async { final byScriptHash = { for (final address in addresses) address.getScriptHash(network): address, }; @@ -1845,7 +1842,7 @@ abstract class ElectrumWalletBase try { final unspentByScriptHash = - await _processChunksToMap>>( + await _processChunksToMap>>( items: scriptHashes, chunkSize: addressHistoryChunkSize, processChunk: _getListUnspentBatch, @@ -2131,10 +2128,7 @@ abstract class ElectrumWalletBase final hd = _hdFor(record: addressRecord); - final privkey = generateECPrivate( - hd: hd, - index: addressRecord.index, - network: network); + final privkey = generateECPrivate(hd: hd, index: addressRecord.index, network: network); privateKeys.add(privkey); @@ -2197,8 +2191,7 @@ abstract class ElectrumWalletBase final deduction = (outputAmount - networkDustAmount >= remainingFee) ? remainingFee : outputAmount - networkDustAmount; - outputs[i] = BitcoinOutput( - address: output.address, value: outputAmount - deduction); + outputs[i] = BitcoinOutput(address: output.address, value: outputAmount - deduction); remainingFee -= deduction; if (remainingFee <= BigInt.zero) break; @@ -2464,7 +2457,8 @@ abstract class ElectrumWalletBase @override Future> fetchTransactions() async { try { - final Map historiesWithDetails = {};; + final Map historiesWithDetails = {}; + ; printV('[BATCH_TEST] Fetching transactions with batch: $shouldUseBatchFetching'); @@ -2473,19 +2467,16 @@ abstract class ElectrumWalletBase ? fetchTransactionsForAddressTypeBatch(historiesWithDetails, type) : fetchTransactionsForAddressType(historiesWithDetails, type))); } else if (type == WalletType.bitcoinCash) { - await Future.wait(BITCOIN_CASH_ADDRESS_TYPES - .map((type) => shouldUseBatchFetching + await Future.wait(BITCOIN_CASH_ADDRESS_TYPES.map((type) => shouldUseBatchFetching ? fetchTransactionsForAddressTypeBatch(historiesWithDetails, type) : fetchTransactionsForAddressType(historiesWithDetails, type))); } else if (type == WalletType.litecoin) { - await Future.wait(LITECOIN_ADDRESS_TYPES - .where((type) => type != SegwitAddresType.mweb) - .map((type) => shouldUseBatchFetching - ? fetchTransactionsForAddressTypeBatch(historiesWithDetails, type) - : fetchTransactionsForAddressType(historiesWithDetails, type))); + await Future.wait(LITECOIN_ADDRESS_TYPES.where((type) => type != SegwitAddresType.mweb).map( + (type) => shouldUseBatchFetching + ? fetchTransactionsForAddressTypeBatch(historiesWithDetails, type) + : fetchTransactionsForAddressType(historiesWithDetails, type))); } else if (type == WalletType.dogecoin) { - await Future.wait(DOGECOIN_ADDRESS_TYPES - .map((type) => shouldUseBatchFetching + await Future.wait(DOGECOIN_ADDRESS_TYPES.map((type) => shouldUseBatchFetching ? fetchTransactionsForAddressTypeBatch(historiesWithDetails, type) : fetchTransactionsForAddressType(historiesWithDetails, type))); } @@ -2633,8 +2624,7 @@ abstract class ElectrumWalletBase } Future fetchTransactionsForAddressTypeBatch( - Map historiesWithDetails, - BitcoinAddressType type) async { + Map historiesWithDetails, BitcoinAddressType type) async { final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type).toList(); final receiveAddresses = addressesByType.where((addr) => !addr.isHidden).toList(); @@ -2676,14 +2666,13 @@ abstract class ElectrumWalletBase ); } - Future fetchTransactionsForAddressesBranchBatch( - Map historiesWithDetails, - BitcoinAddressType type, - List branchAddresses, { - required bool isHidden, - required bool isLegacyDerivation, - }) async { + Map historiesWithDetails, + BitcoinAddressType type, + List branchAddresses, { + required bool isHidden, + required bool isLegacyDerivation, + }) async { if (branchAddresses.isEmpty) return; final tip = await getCurrentChainTip(); @@ -2714,7 +2703,6 @@ abstract class ElectrumWalletBase if (!shouldDiscover) return; - final newAddresses = await walletAddresses.discoverAddressesBatch( currentBranch, isHidden, @@ -2756,9 +2744,7 @@ abstract class ElectrumWalletBase } Future> _fetchBatchAddressHistory( - List addressRecords, - int? currentHeight, - int historyChunkSize) async { + List addressRecords, int? currentHeight, int historyChunkSize) async { String lastTxId = ''; bool didUpdateHistory = false; @@ -2769,11 +2755,8 @@ abstract class ElectrumWalletBase final scriptHashes = addressRecords.map((a) => a.getScriptHash(network)).toList(); final historyByScriptHash = - await _processChunksToMap>>( - items: scriptHashes, - chunkSize: historyChunkSize, - processChunk: _getHistoryBatch - ); + await _processChunksToMap>>( + items: scriptHashes, chunkSize: historyChunkSize, processChunk: _getHistoryBatch); // Map scriptHash -> addressRecord final byScriptHash = {}; @@ -2869,8 +2852,7 @@ abstract class ElectrumWalletBase .toList(growable: false); final heightsByHash = { - for (final e in chunkHistory) - (e['tx_hash'] as String): (e['height'] as int?), + for (final e in chunkHistory) (e['tx_hash'] as String): (e['height'] as int?), }; final infosByHash = await fetchTransactionInfoBatch( @@ -2909,40 +2891,35 @@ abstract class ElectrumWalletBase } } - Future>> _getTransactionVerboseBatch( - List hashes) { + Future>> _getTransactionVerboseBatch(List hashes) { return electrumClient.getBatchTransactionVerbose( hashes, timeout: transactionBatchTimeoutMs, ); } - Future> _getTransactionHexBatch( - List hashes) { + Future> _getTransactionHexBatch(List hashes) { return electrumClient.getBatchTransactionHex( hashes, timeout: transactionBatchTimeoutMs, ); } - Future>>> _getHistoryBatch( - List scriptHashes) { + Future>>> _getHistoryBatch(List scriptHashes) { return electrumClient.getBatchHistory( scriptHashes, timeout: transactionBatchTimeoutMs, ); } - Future>>> _getListUnspentBatch( - List scriptHashes) { + Future>>> _getListUnspentBatch(List scriptHashes) { return electrumClient.getBatchUnspent( scriptHashes, timeout: transactionBatchTimeoutMs, ); } - Future>> _getBalanceBatch( - List scriptHashes) { + Future>> _getBalanceBatch(List scriptHashes) { return electrumClient.getBatchBalance( scriptHashes, timeout: transactionBatchTimeoutMs, @@ -2956,8 +2933,7 @@ abstract class ElectrumWalletBase Duration retryDelay = const Duration(seconds: 2), }) async { final result = {}; - final uniqueHashes = - hashes.map((h) => h.trim()).where((h) => h.isNotEmpty).toSet().toList(); + final uniqueHashes = hashes.map((h) => h.trim()).where((h) => h.isNotEmpty).toSet().toList(); if (uniqueHashes.isEmpty) return result; @@ -2990,9 +2966,8 @@ abstract class ElectrumWalletBase required Map? heightsByHash, }) async { for (var i = 0; i < txIds.length; i += transactionChunkSize) { - final end = (i + transactionChunkSize < txIds.length) - ? i + transactionChunkSize - : txIds.length; + final end = + (i + transactionChunkSize < txIds.length) ? i + transactionChunkSize : txIds.length; final chunk = txIds.sublist(i, end); final bundlesByHash = await getTransactionExpandedBatch( @@ -3024,9 +2999,8 @@ abstract class ElectrumWalletBase } } - Future> getTransactionExpandedBatch({ - required List hashes, - Map? heightsByHash}) async { + Future> getTransactionExpandedBatch( + {required List hashes, Map? heightsByHash}) async { final bundles = {}; if (hashes.isEmpty) return bundles; @@ -3062,8 +3036,8 @@ abstract class ElectrumWalletBase Future>> _fetchTransactionVerboseBatch( List txIds) async { - - final verboseTransactionByHash = await _processChunksToMap>( + final verboseTransactionByHash = + await _processChunksToMap>( items: txIds, chunkSize: transactionChunkSize, processChunk: _getTransactionVerboseBatch, @@ -3100,8 +3074,8 @@ abstract class ElectrumWalletBase } Map _parseTransactions( - Map> verboseByHash, - ) { + Map> verboseByHash, + ) { final result = {}; for (final entry in verboseByHash.entries) { @@ -3116,10 +3090,9 @@ abstract class ElectrumWalletBase return result; } - Map> _collectInputTxIdsByHash( - Map originalByHash, - ) { + Map originalByHash, + ) { final inputTxIdsByHash = >{}; for (final entry in originalByHash.entries) { @@ -3201,8 +3174,8 @@ abstract class ElectrumWalletBase } Future> _fetchBlockTimestampsFromMempoolByHeights( - Set heights, - ) async { + Set heights, + ) async { final out = {}; if (heights.isEmpty) return out; if (!(await checkIfMempoolAPIIsEnabled())) return out; @@ -3212,10 +3185,10 @@ abstract class ElectrumWalletBase try { final blockHashResp = await ProxyWrapper() .get( - clearnetUri: Uri.parse( - 'https://mempool.cakewallet.com/api/v1/block-height/$h', - ), - ) + clearnetUri: Uri.parse( + 'https://mempool.cakewallet.com/api/v1/block-height/$h', + ), + ) .timeout(const Duration(seconds: 15)); if (blockHashResp.statusCode != 200 || blockHashResp.body.isEmpty) return; @@ -3225,10 +3198,10 @@ abstract class ElectrumWalletBase final blockResp = await ProxyWrapper() .get( - clearnetUri: Uri.parse( - 'https://mempool.cakewallet.com/api/v1/block/$blockHash', - ), - ) + clearnetUri: Uri.parse( + 'https://mempool.cakewallet.com/api/v1/block/$blockHash', + ), + ) .timeout(const Duration(seconds: 15)); if (blockResp.statusCode != 200 || blockResp.body.isEmpty) return; @@ -3276,7 +3249,6 @@ abstract class ElectrumWalletBase return result; } - Future updateTransactions() async { printV("updateTransactions() called!"); try { @@ -3368,8 +3340,7 @@ abstract class ElectrumWalletBase } try { - final balancesByScriptHash = - await _processChunksToMap>( + final balancesByScriptHash = await _processChunksToMap>( items: scriptHashes, chunkSize: addressHistoryChunkSize, processChunk: _getBalanceBatch, @@ -3415,7 +3386,8 @@ abstract class ElectrumWalletBase ? await fetchBalancesBatch(addresses) : await fetchBalancesRegular(addresses); - printV('Fetched balances for ${addresses.length} addresses. Batch fetching: $shouldUseBatchFetching'); + printV( + 'Fetched balances for ${addresses.length} addresses. Batch fetching: $shouldUseBatchFetching'); var totalFrozen = 0; var totalConfirmed = 0; @@ -3544,17 +3516,14 @@ abstract class ElectrumWalletBase // that matches this received transaction, mark it as being from a peg out: for (final tx2 in transactionHistory.transactions.values) { final heightDiff = ((tx2.height ?? 0) - (tx.height ?? 0)).abs(); - // this isn't a perfect matching algorithm since we don't have the right input/output information from these transaction models (the addresses are in different formats), but this should be more than good enough for now as it's extremely unlikely a user receives the EXACT same amount from 2 different sources and one of them is a peg out and the other isn't WITHIN 5 blocks of each other - if (tx2.additionalInfo["isPegOut"] == true && - tx2.amount == tx.amount && - heightDiff <= 5) { + // this isn't a perfect matching algorithm since we don't have the right input/output information from these transaction models (the addresses are in different formats), but this should be more than good enough for now as it's extremely unlikely a user receives the EXACT same amount from 2 different sources and one of them is a peg out and the other isn't WITHIN 5 blocks of each other + if (tx2.additionalInfo["isPegOut"] == true && tx2.amount == tx.amount && heightDiff <= 5) { tx.additionalInfo["fromPegOut"] = true; } } } Future checkIfBatchSupported() async { - if (_isBatchSupported != null) { printV('[BATCH_TEST] Already checked: $_isBatchSupported'); return; @@ -3580,9 +3549,7 @@ abstract class ElectrumWalletBase ); final hasError = result.any((item) => - item is Map && - item.containsKey('error') && - item['error'] != null); + item is Map && item.containsKey('error') && item['error'] != null); if (hasError) { _isBatchSupported = false; diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 239fa491d8..239977ecbd 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -195,13 +195,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } if (addressPageType == LightningAddressType.p2l) { - return lightningAddress ?? - "Error: Unable to fetch your Lightning address, please check your network connection."; + return lightningAddress ?? + "Error: Unable to fetch your Lightning address, please check your network connection."; } - final typeMatchingAddressesAll = _addresses - .where((addr) => !addr.isHidden && _isAddressPageTypeMatch(addr)) - .toList(); + final typeMatchingAddressesAll = + _addresses.where((addr) => !addr.isHidden && _isAddressPageTypeMatch(addr)).toList(); // Prefer standard derivation addresses for the current/active address, // but keep legacy addresses present in the overall address lists. @@ -210,8 +209,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { ...typeMatchingAddressesAll.where((a) => a.isLegacyDerivation), ]; - final typeMatchingReceiveAddressesAll = - typeMatchingAddressesAll.where((addr) => !addr.isUsed && !hiddenAddresses.contains(addr.address)).toList(); + final typeMatchingReceiveAddressesAll = typeMatchingAddressesAll + .where((addr) => !addr.isUsed && !hiddenAddresses.contains(addr.address)) + .toList(); final typeMatchingReceiveAddresses = [ ...typeMatchingReceiveAddressesAll.where((a) => !a.isLegacyDerivation), ...typeMatchingReceiveAddressesAll.where((a) => a.isLegacyDerivation), @@ -354,34 +354,35 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return acc; }); - @override - Future init() async { - if (walletInfo.type == WalletType.bitcoinCash) { - await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); - } else if (walletInfo.type == WalletType.litecoin) { - await _generateInitialAddresses(type: SegwitAddresType.p2wpkh); - if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) { - await _generateInitialAddresses(type: SegwitAddresType.mweb); - } - } else if (walletInfo.type == WalletType.dogecoin) { + @override + Future init() async { + if (walletInfo.type == WalletType.bitcoinCash) { + await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); + } else if (walletInfo.type == WalletType.litecoin) { + await _generateInitialAddresses(type: SegwitAddresType.p2wpkh); + if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) { + await _generateInitialAddresses(type: SegwitAddresType.mweb); + } + } else if (walletInfo.type == WalletType.dogecoin) { + await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); + } else if (walletInfo.type == WalletType.bitcoin) { + await _generateInitialAddresses(isLegacyDerivation: true); + await _generateInitialAddresses(); + if (!isHardwareWallet) { + await _generateInitialAddresses(type: P2pkhAddressType.p2pkh, isLegacyDerivation: true); await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); - } else if (walletInfo.type == WalletType.bitcoin) { - await _generateInitialAddresses(isLegacyDerivation: true); - await _generateInitialAddresses(); - if (!isHardwareWallet) { - await _generateInitialAddresses(type: P2pkhAddressType.p2pkh, isLegacyDerivation: true); - await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); - await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh, isLegacyDerivation: true); - await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh); + await _generateInitialAddresses( + type: P2shAddressType.p2wpkhInP2sh, isLegacyDerivation: true); + await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh); - await _generateInitialAddresses(type: SegwitAddresType.p2tr, isLegacyDerivation: true); - await _generateInitialAddresses(type: SegwitAddresType.p2tr); + await _generateInitialAddresses(type: SegwitAddresType.p2tr, isLegacyDerivation: true); + await _generateInitialAddresses(type: SegwitAddresType.p2tr); - await _generateInitialAddresses(type: SegwitAddresType.p2wsh, isLegacyDerivation: true); - await _generateInitialAddresses(type: SegwitAddresType.p2wsh); - } + await _generateInitialAddresses(type: SegwitAddresType.p2wsh, isLegacyDerivation: true); + await _generateInitialAddresses(type: SegwitAddresType.p2wsh); } + } updateAddressesByMatch(); updateReceiveAddresses(); @@ -680,9 +681,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action void updateReceiveAddresses() { receiveAddresses.removeRange(0, receiveAddresses.length); - final newAddresses = _addresses.where((addressRecord) => - !addressRecord.isHidden && - !addressRecord.isUsed); + final newAddresses = + _addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed); receiveAddresses.addAll(newAddresses); } @@ -699,12 +699,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action Future discoverAddresses( - List addressList, - bool isHidden, - Future Function(BitcoinAddressRecord) getAddressHistory, { - BitcoinAddressType type = SegwitAddresType.p2wpkh, - required bool isLegacyDerivation, - }) async { + List addressList, + bool isHidden, + Future Function(BitcoinAddressRecord) getAddressHistory, { + BitcoinAddressType type = SegwitAddresType.p2wpkh, + required bool isLegacyDerivation, + }) async { final newAddresses = await _createNewAddresses( gap, startIndex: addressList.length, @@ -716,8 +716,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { addAddresses(newAddresses); addressList.addAll(newAddresses); - final addressesWithHistory = - await Future.wait(newAddresses.map(getAddressHistory)); + final addressesWithHistory = await Future.wait(newAddresses.map(getAddressHistory)); final isLastAddressUsed = addressesWithHistory.last != null; if (isLastAddressUsed) { @@ -733,12 +732,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action Future> discoverAddressesBatch( - List addressList, - bool isHidden, - Future> Function(List) getUsedAddresses, { - BitcoinAddressType type = SegwitAddresType.p2wpkh, - required bool isLegacyDerivation, - }) async { + List addressList, + bool isHidden, + Future> Function(List) getUsedAddresses, { + BitcoinAddressType type = SegwitAddresType.p2wpkh, + required bool isLegacyDerivation, + }) async { final newAddresses = await _createNewAddresses( gap, startIndex: addressList.length, @@ -750,8 +749,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final usedAddresses = await getUsedAddresses(newAddresses); - final hasUsedAddressInGap = newAddresses.any( - (addressRecord) => usedAddresses.contains(addressRecord.address)); + final hasUsedAddressInGap = + newAddresses.any((addressRecord) => usedAddresses.contains(addressRecord.address)); if (!hasUsedAddressInGap) { return newAddresses; @@ -770,64 +769,69 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return [...newAddresses, ...moreNewAddresses]; } - Future _generateInitialAddresses( - {BitcoinAddressType type = SegwitAddresType.p2wpkh, - bool isLegacyDerivation = false }) async { - - // Legacy derivation produces the same addresses as standard for these types. - // Don't generate a legacy set to avoid duplicates. - if (isLegacyDerivation && (type == SegwitAddresType.p2wpkh || type == SegwitAddresType.p2wsh)) { - return; - } + {BitcoinAddressType type = SegwitAddresType.p2wpkh, bool isLegacyDerivation = false}) async { + // Legacy derivation produces the same addresses as standard for these types. + // Don't generate a legacy set to avoid duplicates. + if (isLegacyDerivation && (type == SegwitAddresType.p2wpkh || type == SegwitAddresType.p2wsh)) { + return; + } - var countOfReceiveAddresses = 0; - var countOfHiddenAddresses = 0; + var countOfReceiveAddresses = 0; + var countOfHiddenAddresses = 0; - _addresses.forEach((addr) { - if (addr.type == type && addr.isLegacyDerivation == isLegacyDerivation) { - if (addr.isHidden) { - countOfHiddenAddresses += 1; - } else { - countOfReceiveAddresses += 1; - } + _addresses.forEach((addr) { + if (addr.type == type && addr.isLegacyDerivation == isLegacyDerivation) { + if (addr.isHidden) { + countOfHiddenAddresses += 1; + } else { + countOfReceiveAddresses += 1; } - }); - - if (countOfReceiveAddresses < defaultReceiveAddressesCount) { - final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses; - final newAddresses = await _createNewAddresses(addressesCount, - startIndex: countOfReceiveAddresses, isHidden: false, type: type, isLegacyDerivation: isLegacyDerivation); - addAddresses(newAddresses); } + }); - if (countOfHiddenAddresses < defaultChangeAddressesCount) { - final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses; - final newAddresses = await _createNewAddresses(addressesCount, - startIndex: countOfHiddenAddresses, isHidden: true, type: type, isLegacyDerivation: isLegacyDerivation); - addAddresses(newAddresses); - } + if (countOfReceiveAddresses < defaultReceiveAddressesCount) { + final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses; + final newAddresses = await _createNewAddresses(addressesCount, + startIndex: countOfReceiveAddresses, + isHidden: false, + type: type, + isLegacyDerivation: isLegacyDerivation); + addAddresses(newAddresses); } - Future> _createNewAddresses(int count, - {int startIndex = 0, bool isHidden = false, BitcoinAddressType? type, bool isLegacyDerivation = false}) async { - final list = []; - - for (var i = startIndex; i < count + startIndex; i++) { - - final addrType = type ?? addressPageType; - final hd = _hdFor(isHidden: isHidden, type: addrType, isLegacyDerivation: isLegacyDerivation); + if (countOfHiddenAddresses < defaultChangeAddressesCount) { + final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses; + final newAddresses = await _createNewAddresses(addressesCount, + startIndex: countOfHiddenAddresses, + isHidden: true, + type: type, + isLegacyDerivation: isLegacyDerivation); + addAddresses(newAddresses); + } + } - final address = BitcoinAddressRecord( - await getAddressAsync(index: i, hd: hd, addressType: addrType), - index: i, - isHidden: isHidden, - isLegacyDerivation: isLegacyDerivation, - type: addrType, - network: network, - ); - list.add(address); - } + Future> _createNewAddresses(int count, + {int startIndex = 0, + bool isHidden = false, + BitcoinAddressType? type, + bool isLegacyDerivation = false}) async { + final list = []; + + for (var i = startIndex; i < count + startIndex; i++) { + final addrType = type ?? addressPageType; + final hd = _hdFor(isHidden: isHidden, type: addrType, isLegacyDerivation: isLegacyDerivation); + + final address = BitcoinAddressRecord( + await getAddressAsync(index: i, hd: hd, addressType: addrType), + index: i, + isHidden: isHidden, + isLegacyDerivation: isLegacyDerivation, + type: addrType, + network: network, + ); + list.add(address); + } return list; } @@ -868,8 +872,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return; } - final mainHd = _hdFor(isHidden: false, type: element.type, isLegacyDerivation: element.isLegacyDerivation); - final sideHd = _hdFor(isHidden: true, type: element.type, isLegacyDerivation: element.isLegacyDerivation); + final mainHd = _hdFor( + isHidden: false, type: element.type, isLegacyDerivation: element.isLegacyDerivation); + final sideHd = _hdFor( + isHidden: true, type: element.type, isLegacyDerivation: element.isLegacyDerivation); if (!element.isHidden && element.address != await getAddressAsync(index: element.index, hd: mainHd, addressType: element.type)) { @@ -897,7 +903,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return _isAddressByType(addressRecord, addressPageType); } - bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type; + bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type; bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => !addr.isHidden && !addr.isUsed && addr.type == type; @@ -907,9 +913,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final addressRecord = silentAddresses.firstWhere((addressRecord) => addressRecord.type == SilentPaymentsAddresType.p2sp && addressRecord.address == address); - silentAddresses.remove(addressRecord); - updateAddressesByMatch(); - } + silentAddresses.remove(addressRecord); + updateAddressesByMatch(); + } Bip32Slip10Secp256k1 _hdFor({ required bool isHidden, @@ -923,7 +929,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { if (hd == null) throw Exception("HD not found for type $type"); return hd; } - + @action Future setLightningAddress(String walletName, {String newAddress = ""}) async { if (lightningWallet == null) return; diff --git a/cw_bitcoin/lib/exceptions.dart b/cw_bitcoin/lib/exceptions.dart index 9bdb66eef0..d43ce823a7 100644 --- a/cw_bitcoin/lib/exceptions.dart +++ b/cw_bitcoin/lib/exceptions.dart @@ -30,7 +30,7 @@ class BitcoinTransactionCommitFailed extends TransactionCommitFailed { @override String toString() { - return errorMessage??"unknown error"; + return errorMessage ?? "unknown error"; } } diff --git a/cw_bitcoin/lib/hardware/bitbox_service.dart b/cw_bitcoin/lib/hardware/bitbox_service.dart index 3f47c5c621..a71dcacbc2 100644 --- a/cw_bitcoin/lib/hardware/bitbox_service.dart +++ b/cw_bitcoin/lib/hardware/bitbox_service.dart @@ -67,7 +67,8 @@ class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWal Future getMasterFingerprint() => manager.getMasterFingerprint(); } -class LitecoinBitboxService extends HardwareWalletService with BitcoinHardwareWalletService, LitecoinHardwareWalletService { +class LitecoinBitboxService extends HardwareWalletService + with BitcoinHardwareWalletService, LitecoinHardwareWalletService { LitecoinBitboxService(this.manager); final BitboxManager manager; diff --git a/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart b/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart index 9ede5714e3..aa73fc106e 100644 --- a/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart +++ b/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart @@ -12,7 +12,8 @@ import 'package:cw_core/hardware/hardware_wallet_service.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:ledger_litecoin/ledger_litecoin.dart'; -class LitecoinLedgerService extends HardwareWalletService with BitcoinHardwareWalletService, LitecoinHardwareWalletService { +class LitecoinLedgerService extends HardwareWalletService + with BitcoinHardwareWalletService, LitecoinHardwareWalletService { LitecoinLedgerService(this.ledgerConnection) : litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection); @@ -53,7 +54,6 @@ class LitecoinLedgerService extends HardwareWalletService with BitcoinHardwareWa required List inputs, required Map publicKeys, }) { - final readyInputs = []; for (final utxo in inputs) { final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; @@ -77,7 +77,7 @@ class LitecoinLedgerService extends HardwareWalletService with BitcoinHardwareWa inputs: readyInputs, outputs: outputs .map((e) => TransactionOutput.fromBigInt((e as BitcoinOutput).value, - Uint8List.fromList(e.address.toScriptPubKey().toBytes()))) + Uint8List.fromList(e.address.toScriptPubKey().toBytes()))) .toList(), changePath: changePath, sigHashType: 0x01, diff --git a/cw_bitcoin/lib/lightning/lightning_wallet.dart b/cw_bitcoin/lib/lightning/lightning_wallet.dart index 42dda7cb22..ea9399c6ab 100644 --- a/cw_bitcoin/lib/lightning/lightning_wallet.dart +++ b/cw_bitcoin/lib/lightning/lightning_wallet.dart @@ -90,8 +90,7 @@ class LightningWallet { lnurlDomain: lnurlDomain, apiKey: apiKey, privateEnabledDefault: true, - maxDepositClaimFee: MaxFee.rate(satPerVbyte: BigInt.from(5)) - ); + maxDepositClaimFee: MaxFee.rate(satPerVbyte: BigInt.from(5))); final connectRequest = ConnectRequest( config: config, @@ -105,8 +104,7 @@ class LightningWallet { _logStream ??= initLogging().asBroadcastStream(); try { - final logFile = File("$appPath/lightning.log") - ..createSync(); + final logFile = File("$appPath/lightning.log")..createSync(); _subscribeToLogStream(logFile); } catch (e) { printV(e); @@ -198,15 +196,17 @@ class LightningWallet { } } - Future createTransaction( - String address, BigInt? amountSats, BitcoinTransactionPriority? priority, bool feesIncluded) async { + Future createTransaction(String address, BigInt? amountSats, + BitcoinTransactionPriority? priority, bool feesIncluded) async { final inputType = await sdk.parse(input: address); final feePolicy = feesIncluded ? FeePolicy.feesIncluded : FeePolicy.feesExcluded; if (inputType is InputType_Bolt11Invoice) { final request = PrepareSendPaymentRequest( - paymentRequest: inputType.field0.invoice.bolt11, amount: amountSats, feePolicy: feePolicy); + paymentRequest: inputType.field0.invoice.bolt11, + amount: amountSats, + feePolicy: feePolicy); final prepareResponse = await sdk.prepareSendPayment(request: request); final paymentMethod = prepareResponse.paymentMethod; @@ -392,14 +392,16 @@ class LightningWallet { return _getElectrumTransactionInfoFromPayment(response.payment); } - Future refundDeposit(String txId, int vout, String destinationAddress, - BigInt feeRate) async { - final response = await sdk.refundDeposit(request: RefundDepositRequest( - txid: txId, - vout: vout, - destinationAddress: destinationAddress, - fee: Fee.rate(satPerVbyte: feeRate), - ),); + Future refundDeposit( + String txId, int vout, String destinationAddress, BigInt feeRate) async { + final response = await sdk.refundDeposit( + request: RefundDepositRequest( + txid: txId, + vout: vout, + destinationAddress: destinationAddress, + fee: Fee.rate(satPerVbyte: feeRate), + ), + ); return response.txHex; } diff --git a/cw_bitcoin/lib/lightning/pending_lightning_transaction.dart b/cw_bitcoin/lib/lightning/pending_lightning_transaction.dart index d2bcabb1fa..237a6b824d 100644 --- a/cw_bitcoin/lib/lightning/pending_lightning_transaction.dart +++ b/cw_bitcoin/lib/lightning/pending_lightning_transaction.dart @@ -10,10 +10,9 @@ class PendingLightningTransaction with PendingTransaction { required this.commitOverride, }); - final bool isSendAll; Future Function() commitOverride; - final List _listeners =[]; + final List _listeners = []; @override String id; diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 2f9ae0b3c4..23959374c4 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -175,7 +175,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { @override bool get hasRescan => true; - final String? scanSecretOverride; final String? spendPubkeyOverride; List get scanSecret => (scanSecretOverride != null && scanSecretOverride?.isNotEmpty == true) @@ -232,8 +231,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { } @override - WalletKeysData get walletKeysData => - WalletKeysData(mnemonic: seed, xPub: xpub, passphrase: passphrase, scanSecret: scanSecretOverride, spendPubkey: spendPubkeyOverride); + WalletKeysData get walletKeysData => WalletKeysData( + mnemonic: seed, + xPub: xpub, + passphrase: passphrase, + scanSecret: scanSecretOverride, + spendPubkey: spendPubkeyOverride); static Future open({ required String name, @@ -1106,15 +1109,18 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { for (final utxo in transaction.utxos) { if (utxo.utxo.scriptType != SegwitAddresType.mweb) { inputs.add(utxo.utxo.toInput()); - txouts.add(TxOut(value: Int64(utxo.utxo.value.toInt()), - pkScript: utxo.ownerDetails.address.toScriptPubKey().toBytes())); + txouts.add(TxOut( + value: Int64(utxo.utxo.value.toInt()), + pkScript: utxo.ownerDetails.address.toScriptPubKey().toBytes())); } } var resp = await CwMweb.psbtCreate(PsbtCreateRequest( - rawTx: inputs.isEmpty ? null : BtcTransaction( - inputs: inputs, - outputs: isMweb ? [] : transaction.outputs, - ).toBytes(), + rawTx: inputs.isEmpty + ? null + : BtcTransaction( + inputs: inputs, + outputs: isMweb ? [] : transaction.outputs, + ).toBytes(), witnessUtxo: txouts, )); for (final utxo in transaction.utxos) { @@ -1127,17 +1133,18 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { )); } } - if (isMweb) for (final output in transaction.outputs) { - var address = addressFromOutputScript(output.scriptPubKey, LitecoinNetwork.mainnet); - if (output.scriptPubKey.getAddressType() == SegwitAddresType.mweb) { - address = SegwitBech32Encoder.encode("ltcmweb", 0, output.scriptPubKey.toBytes()); + if (isMweb) + for (final output in transaction.outputs) { + var address = addressFromOutputScript(output.scriptPubKey, LitecoinNetwork.mainnet); + if (output.scriptPubKey.getAddressType() == SegwitAddresType.mweb) { + address = SegwitBech32Encoder.encode("ltcmweb", 0, output.scriptPubKey.toBytes()); + } + resp = await CwMweb.psbtAddRecipient(PsbtAddRecipientRequest( + psbtB64: resp.psbtB64, + recipient: PsbtRecipient(address: address, value: Int64(output.amount.toInt())), + feeRatePerKb: Int64.parseInt(transaction.feeRate) * 1000, + )); } - resp = await CwMweb.psbtAddRecipient(PsbtAddRecipientRequest( - psbtB64: resp.psbtB64, - recipient: PsbtRecipient(address: address, value: Int64(output.amount.toInt())), - feeRatePerKb: Int64.parseInt(transaction.feeRate) * 1000, - )); - } return base64.decode(resp.psbtB64); } @@ -1203,7 +1210,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { for (final utxo in tx.utxos) { if (utxo.utxo.scriptType == SegwitAddresType.mweb) { hasMwebInput = true; - } else { // check if any of the inputs of this transaction are hog-ex: // this list is only non-mweb inputs: @@ -1256,9 +1262,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { final utxo = unspentCoins .firstWhere((utxo) => utxo.hash == e.value.txId && utxo.vout == e.value.txIndex); final key = generateECPrivate( - hd: utxo.bitcoinAddressRecord.isHidden - ? sideHd - : mainHd, + hd: utxo.bitcoinAddressRecord.isHidden ? sideHd : mainHd, index: utxo.bitcoinAddressRecord.index, network: network); final digest = tx2.getTransactionSegwitDigit( @@ -1284,8 +1288,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { } } - void addTransactionListener(PendingBitcoinTransaction tx, - List inputAddresses, bool isPegIn, bool isPegOut) { + void addTransactionListener( + PendingBitcoinTransaction tx, List inputAddresses, bool isPegIn, bool isPegOut) { tx.addListener((transaction) async { final addresses = {}; transaction.inputAddresses?.addAll(inputAddresses); @@ -1575,7 +1579,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { rawTx: rawTx, ownerDetails: utxo.ownerDetails, ownerDerivationPath: publicKeyAndDerivationPath.derivationPath, - ownerMasterFingerprint:masterFingerprint, + ownerMasterFingerprint: masterFingerprint, ownerPublicKey: publicKeyAndDerivationPath.publicKey, )); } diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart index a4a4b7590f..fe85cd7823 100644 --- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart @@ -59,9 +59,11 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with ? hex.decode(scanSecretOverride!) : mwebHd?.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw ?? List.filled(32, 0); - List get spendPubkey => (spendPubkeyOverride != null && spendPubkeyOverride?.isNotEmpty == true) - ? hex.decode(spendPubkeyOverride!) - : mwebHd?.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed ?? List.filled(32, 0); + List get spendPubkey => + (spendPubkeyOverride != null && spendPubkeyOverride?.isNotEmpty == true) + ? hex.decode(spendPubkeyOverride!) + : mwebHd?.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed ?? + List.filled(32, 0); @override Future init() async { @@ -81,7 +83,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with return null; } if ((scanSecret.length < 1 || scanSecret.reduce((a, b) => a + b) == 0) && - (spendPubkey.length < 1 || spendPubkey.reduce((a, b) => a + b) == 0)) { + (spendPubkey.length < 1 || spendPubkey.reduce((a, b) => a + b) == 0)) { return null; } @@ -230,12 +232,16 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with // don't use mweb addresses for exchange refund address: final current = getFreshAddress(); - final bool isMweb = receiveAddresses - .any((e) => e.address == current && e.type == SegwitAddresType.mweb); + 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)) + .where((e) => + e.type == SegwitAddresType.p2wpkh && + !e.isUsed && + !e.isHidden && + !hiddenAddresses.contains(e.address)) .map((e) => e.address) .toList(); diff --git a/cw_bitcoin/lib/litecoin_wallet_service.dart b/cw_bitcoin/lib/litecoin_wallet_service.dart index 0156ee6724..09ab924d1f 100644 --- a/cw_bitcoin/lib/litecoin_wallet_service.dart +++ b/cw_bitcoin/lib/litecoin_wallet_service.dart @@ -48,7 +48,8 @@ class LitecoinWalletService extends WalletService< password: credentials.password!, passphrase: credentials.passphrase, walletInfo: credentials.walletInfo!, - derivationInfo: credentials.derivationInfo ?? (await credentials.walletInfo!.getDerivationInfo()), + derivationInfo: + credentials.derivationInfo ?? (await credentials.walletInfo!.getDerivationInfo()), unspentCoinsInfo: unspentCoinsInfoSource, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -64,7 +65,6 @@ class LitecoinWalletService extends WalletService< @override Future openWallet(String name, String password) async { - final walletInfo = await WalletInfo.get(name, getType()); if (walletInfo == null) { throw Exception('Wallet not found'); @@ -125,8 +125,9 @@ class LitecoinWalletService extends WalletService< } } - final unspentCoinsToDelete = unspentCoinsInfoSource.values.where( - (unspentCoin) => unspentCoin.walletId == walletInfo.id).toList(); + final unspentCoinsToDelete = unspentCoinsInfoSource.values + .where((unspentCoin) => unspentCoin.walletId == walletInfo.id) + .toList(); final keysToDelete = unspentCoinsToDelete.map((unspentCoin) => unspentCoin.key).toList(); @@ -150,8 +151,7 @@ class LitecoinWalletService extends WalletService< final network = isTestnet == true ? LitecoinNetwork.testnet : LitecoinNetwork.mainnet; credentials.walletInfo?.network = network.value; final derivationInfo = await credentials.walletInfo!.getDerivationInfo(); - derivationInfo.derivationPath = - credentials.hwAccountData.derivationPath; + derivationInfo.derivationPath = credentials.hwAccountData.derivationPath; await derivationInfo.save(); credentials.walletInfo!.save(); @@ -170,7 +170,7 @@ class LitecoinWalletService extends WalletService< @override Future restoreFromKeys(LitecoinWalletFromKeysCredentials credentials, - {bool? isTestnet}) async { + {bool? isTestnet}) async { final network = isTestnet == true ? LitecoinNetwork.testnet : LitecoinNetwork.mainnet; credentials.walletInfo?.network = network.value; @@ -202,7 +202,8 @@ class LitecoinWalletService extends WalletService< passphrase: credentials.passphrase, mnemonic: credentials.mnemonic, walletInfo: credentials.walletInfo!, - derivationInfo: credentials.derivationInfo ?? (await credentials.walletInfo!.getDerivationInfo()), + derivationInfo: + credentials.derivationInfo ?? (await credentials.walletInfo!.getDerivationInfo()), unspentCoinsInfo: unspentCoinsInfoSource, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); diff --git a/cw_bitcoin/lib/payjoin/manager.dart b/cw_bitcoin/lib/payjoin/manager.dart index 4fa2e2a63a..efa120f997 100644 --- a/cw_bitcoin/lib/payjoin/manager.dart +++ b/cw_bitcoin/lib/payjoin/manager.dart @@ -108,16 +108,14 @@ class PayjoinManager { Future initSender( String pjUriString, String originalPsbt, int networkFeesSatPerVb) async { try { - final pjUri = - (await PayjoinUri.Uri.fromStr(pjUriString)).checkPjSupported(); + final pjUri = (await PayjoinUri.Uri.fromStr(pjUriString)).checkPjSupported(); final minFeeRateSatPerKwu = BigInt.from(networkFeesSatPerVb * 250); final senderBuilder = await SenderBuilder.fromPsbtAndUri( psbtBase64: originalPsbt, pjUri: pjUri, ); final persister = PayjoinSenderPersister.impl(); - final newSender = - await senderBuilder.buildRecommended(minFeeRate: minFeeRateSatPerKwu); + final newSender = await senderBuilder.buildRecommended(minFeeRate: minFeeRateSatPerKwu); final senderToken = await newSender.persist(persister: persister); return Sender.load(token: senderToken, persister: persister); @@ -133,8 +131,7 @@ class PayjoinManager { bool isTestnet = false, }) async { final pjUri = Uri.parse(pjUrl).queryParameters['pj']!; - await _payjoinStorage.insertSenderSession( - sender, pjUri, _wallet.id, amount); + await _payjoinStorage.insertSenderSession(sender, pjUri, _wallet.id, amount); return _spawnSender(isTestnet: isTestnet, sender: sender, pjUri: pjUri); } @@ -207,8 +204,7 @@ class PayjoinManager { return completer.future; } - Future getUnusedReceiver(String address, - [bool isTestnet = false]) async { + Future getUnusedReceiver(String address, [bool isTestnet = false]) async { final session = _payjoinStorage.getUnusedActiveReceiverSession(_wallet.id); if (session != null) { @@ -220,7 +216,8 @@ class PayjoinManager { return initReceiver(address); } - Future initReceiver(String address, [bool isTestnet = false, int retryCount = 0]) async { + Future initReceiver(String address, + [bool isTestnet = false, int retryCount = 0]) async { if (retryCount > 0) writePayjoinLog("Retrying initReceiver ${retryCount + 1} attempt"); try { @@ -245,7 +242,6 @@ class PayjoinManager { } catch (e) { writePayjoinLog(e.toString()); if (e.toString().contains("error sending request for url") && retryCount < 5) { - return initReceiver(address, isTestnet, ++retryCount); } else { rethrow; @@ -273,13 +269,11 @@ class PayjoinManager { rawAmount = getOutputAmountFromTx(tx, _wallet); break; case PayjoinReceiverRequestTypes.checkIsOwned: - (_wallet.walletAddresses as BitcoinWalletAddresses) - .newPayjoinReceiver(); + (_wallet.walletAddresses as BitcoinWalletAddresses).newPayjoinReceiver(); _payjoinStorage.markReceiverSessionInProgress(receiver.id()); final inputScript = message['input_script'] as Uint8List; - final isOwned = - _wallet.isMine(Script.fromRaw(byteData: inputScript)); + final isOwned = _wallet.isMine(Script.fromRaw(byteData: inputScript)); mainToIsolateSendPort?.send({ 'requestId': message['requestId'], 'result': isOwned, @@ -288,8 +282,7 @@ class PayjoinManager { case PayjoinReceiverRequestTypes.checkIsReceiverOutput: final outputScript = message['output_script'] as Uint8List; - final isReceiverOutput = - _wallet.isMine(Script.fromRaw(byteData: outputScript)); + final isReceiverOutput = _wallet.isMine(Script.fromRaw(byteData: outputScript)); mainToIsolateSendPort?.send({ 'requestId': message['requestId'], 'result': isReceiverOutput, @@ -310,7 +303,8 @@ class PayjoinManager { case PayjoinReceiverRequestTypes.processPsbt: final psbt = message['psbt'] as String; - writePayjoinLog("Receiver(${receiver.id()}) PayjoinReceiverRequestTypes.processPsbt: $psbt"); + writePayjoinLog( + "Receiver(${receiver.id()}) PayjoinReceiverRequestTypes.processPsbt: $psbt"); final signedPsbt = await _wallet.signPsbt(psbt, utxos); mainToIsolateSendPort?.send({ @@ -322,7 +316,8 @@ class PayjoinManager { case PayjoinReceiverRequestTypes.proposalSent: _cleanupSession(receiver.id()); final psbt = message['psbt'] as String; - writePayjoinLog("Receiver(${receiver.id()}) PayjoinReceiverRequestTypes.proposalSent: $psbt"); + writePayjoinLog( + "Receiver(${receiver.id()}) PayjoinReceiverRequestTypes.proposalSent: $psbt"); await _payjoinStorage.markReceiverSessionComplete( receiver.id(), getTxIdFromPsbtV0(psbt), rawAmount); diff --git a/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart b/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart index 641399504c..f1980e39e8 100644 --- a/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart +++ b/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart @@ -47,8 +47,7 @@ class PayjoinReceiverWorker { try { final receiver = Receiver.fromJson(json: receiverJson); - final uncheckedProposal = - await worker.receiveUncheckedProposal(receiver); + final uncheckedProposal = await worker.receiveUncheckedProposal(receiver); final originalTx = await uncheckedProposal.extractTxToScheduleBroadcast(); sendPort.send({ @@ -112,8 +111,7 @@ class PayjoinReceiverWorker { final httpRequest = await client.post(url, headers: {'Content-Type': request.contentType}, body: request.body); - final proposal = await session.processRes( - body: httpRequest.bodyBytes, ctx: extractReq.$2); + final proposal = await session.processRes(body: httpRequest.bodyBytes, ctx: extractReq.$2); if (proposal != null) return proposal; sleep(Duration(seconds: 2)); } @@ -140,8 +138,7 @@ class PayjoinReceiverWorker { return await finalProposal.psbt(); } - Future processPayjoinProposal( - UncheckedProposal proposal) async { + Future processPayjoinProposal(UncheckedProposal proposal) async { await proposal.extractTxToScheduleBroadcast(); // TODO Handle this. send to the main port on a timer? @@ -174,20 +171,17 @@ class PayjoinReceiverWorker { ); final pj5 = await pj4.commitOutputs(); - final listUnspent = - await _sendRequest(PayjoinReceiverRequestTypes.getCandidateInputs); + final listUnspent = await _sendRequest(PayjoinReceiverRequestTypes.getCandidateInputs); final unspent = listUnspent as List; if (unspent.isEmpty) throw RecoverableError('No unspent outputs available'); - final candidateInputs = - await Future.wait(unspent.map(_inputPairFromUtxo)); + final candidateInputs = await Future.wait(unspent.map(_inputPairFromUtxo)); // Prefer a UTXO that avoids the Unnecessary Input Heuristic (UIH2); // fall back to the first candidate if none preserves privacy. InputPair selectedUtxo = candidateInputs.first; try { - selectedUtxo = - await pj5.tryPreservingPrivacy(candidateInputs: candidateInputs); + selectedUtxo = await pj5.tryPreservingPrivacy(candidateInputs: candidateInputs); } catch (_) {} final pj6 = await pj5.contributeInputs(replacementInputs: [selectedUtxo]); @@ -196,8 +190,8 @@ class PayjoinReceiverWorker { // Finalize proposal final payjoinProposal = await pj7.finalizeProposal( processPsbt: (String psbt) async { - final result = await _sendRequest( - PayjoinReceiverRequestTypes.processPsbt, {'psbt': psbt}); + final result = + await _sendRequest(PayjoinReceiverRequestTypes.processPsbt, {'psbt': psbt}); return result as String; }, // TODO set maxFeeRateSatPerVb @@ -213,15 +207,12 @@ class PayjoinReceiverWorker { Future _inputPairFromUtxo(UtxoWithPrivateKey utxo) async { final txout = TxOut( value: utxo.utxo.value, - scriptPubkey: Uint8List.fromList( - utxo.ownerDetails.address.toScriptPubKey().toBytes()), + scriptPubkey: Uint8List.fromList(utxo.ownerDetails.address.toScriptPubKey().toBytes()), ); - final psbtin = - PsbtInput(witnessUtxo: txout, redeemScript: null, witnessScript: null); + final psbtin = PsbtInput(witnessUtxo: txout, redeemScript: null, witnessScript: null); - final previousOutput = - OutPoint(txid: utxo.utxo.txHash, vout: utxo.utxo.vout); + final previousOutput = OutPoint(txid: utxo.utxo.txHash, vout: utxo.utxo.vout); final txin = TxIn( previousOutput: previousOutput, diff --git a/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart b/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart index 75f58a4e36..0d46d1f62a 100644 --- a/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart +++ b/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart @@ -46,11 +46,11 @@ class PayjoinSenderWorker { sendPort.send(e); } } + final client = ProxyWrapper().getHttpIOClient(); /// Run a payjoin sender (V2 protocol first, fallback to V1). Future runSender(Sender sender) async { - try { return await _runSenderV2(sender); } catch (e) { @@ -70,13 +70,11 @@ class PayjoinSenderWorker { Future _runSenderV2(Sender sender) async { try { final postRequest = await sender.extractV2( - ohttpProxyUrl: - await pj_uri.Url.fromStr(PayjoinManager.randomOhttpRelayUrl()), + ohttpProxyUrl: await pj_uri.Url.fromStr(PayjoinManager.randomOhttpRelayUrl()), ); final postResult = await _postRequest(postRequest.$1); - final getContext = - await postRequest.$2.processResponse(response: postResult); + final getContext = await postRequest.$2.processResponse(response: postResult); sendPort.send({'type': PayjoinSenderRequestTypes.requestPosted, "pj": pjUrl}); diff --git a/cw_bitcoin/lib/payjoin/storage.dart b/cw_bitcoin/lib/payjoin/storage.dart index 5fb9d57161..e4132ebfa9 100644 --- a/cw_bitcoin/lib/payjoin/storage.dart +++ b/cw_bitcoin/lib/payjoin/storage.dart @@ -23,16 +23,14 @@ class PayjoinStorage { ), ); - PayjoinSession? getUnusedActiveReceiverSession(String walletId) => - _payjoinSessionSources.values - .where((session) => - session.walletId == walletId && - session.status == PayjoinSessionStatus.created.name && - !session.isSenderSession) - .firstOrNull; - - Future markReceiverSessionComplete( - String sessionId, String txId, String amount) async { + PayjoinSession? getUnusedActiveReceiverSession(String walletId) => _payjoinSessionSources.values + .where((session) => + session.walletId == walletId && + session.status == PayjoinSessionStatus.created.name && + !session.isSenderSession) + .firstOrNull; + + Future markReceiverSessionComplete(String sessionId, String txId, String amount) async { final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!; session.status = PayjoinSessionStatus.success.name; @@ -41,8 +39,7 @@ class PayjoinStorage { await session.save(); } - Future markReceiverSessionUnrecoverable( - String sessionId, String reason) async { + Future markReceiverSessionUnrecoverable(String sessionId, String reason) async { final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!; session.status = PayjoinSessionStatus.unrecoverable.name; @@ -92,13 +89,10 @@ class PayjoinStorage { await session.save(); } - List readAllOpenSessions(String walletId) => - _payjoinSessionSources.values - .where((session) => - session.walletId == walletId && - ![ - PayjoinSessionStatus.success.name, - PayjoinSessionStatus.unrecoverable.name - ].contains(session.status)) - .toList(); + List readAllOpenSessions(String walletId) => _payjoinSessionSources.values + .where((session) => + session.walletId == walletId && + ![PayjoinSessionStatus.success.name, PayjoinSessionStatus.unrecoverable.name] + .contains(session.status)) + .toList(); } diff --git a/cw_bitcoin/lib/psbt/signer.dart b/cw_bitcoin/lib/psbt/signer.dart index c46517e665..fbd00b8427 100644 --- a/cw_bitcoin/lib/psbt/signer.dart +++ b/cw_bitcoin/lib/psbt/signer.dart @@ -40,8 +40,7 @@ extension PsbtSigner on PsbtV2 { return tx.buffer(); } - Future signWithUTXO( - List utxos, UTXOSignerCallBack signer, + Future signWithUTXO(List utxos, UTXOSignerCallBack signer, [UTXOGetterCallBack? getTaprootPair]) async { final raw = BytesUtils.toHexString(extractUnsignedTX(getSegwit: false)); final tx = BtcTransaction.fromRaw(raw); @@ -53,8 +52,8 @@ extension PsbtSigner on PsbtV2 { if (utxos.any((e) => e.utxo.isP2tr())) { for (final input in tx.inputs) { - final utxo = utxos.firstWhereOrNull( - (u) => u.utxo.txHash == input.txId && u.utxo.vout == input.txIndex); + final utxo = utxos + .firstWhereOrNull((u) => u.utxo.txHash == input.txId && u.utxo.vout == input.txIndex); if (utxo == null) { final trPair = await getTaprootPair!.call(input.txId, input.txIndex); @@ -81,8 +80,8 @@ extension PsbtSigner on PsbtV2 { : BitcoinOpCodeConst.SIGHASH_ALL; /// We generate transaction digest for current input - final digest = _generateTransactionDigest( - script, i, utxo.utxo, tx, taprootAmounts, taprootScripts); + final digest = + _generateTransactionDigest(script, i, utxo.utxo, tx, taprootAmounts, taprootScripts); /// now we need sign the transaction digest final sig = signer(digest, utxo, utxo.privateKey, sighash); @@ -90,21 +89,14 @@ extension PsbtSigner on PsbtV2 { if (utxo.utxo.isP2tr()) { setInputTapKeySig(i, Uint8List.fromList(BytesUtils.fromHexString(sig))); } else { - setInputPartialSig( - i, - Uint8List.fromList(BytesUtils.fromHexString(utxo.public().toHex())), + setInputPartialSig(i, Uint8List.fromList(BytesUtils.fromHexString(utxo.public().toHex())), Uint8List.fromList(BytesUtils.fromHexString(sig))); } } } - List _generateTransactionDigest( - Script scriptPubKeys, - int input, - BitcoinUtxo utxo, - BtcTransaction transaction, - List taprootAmounts, - List