From 244d20d1b66287b329e9ef8210e8a1836a8d119d Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Wed, 7 Sep 2022 13:43:03 +0200 Subject: [PATCH 01/84] Skip unsupported providers when calculating best rate provider (#500) --- lib/view_model/exchange/exchange_view_model.dart | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index d55d4f9df..8ae653e5a 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -210,6 +210,10 @@ abstract class ExchangeViewModelBase with Store { currentTradeAvailableProviders.clear(); for (var provider in selectedProviders) { + /// if this provider is not valid for the current pair, skip it + if (!providersForCurrentPair().contains(provider)) { + continue; + } provider .calculateAmount( from: receiveCurrency, @@ -232,8 +236,8 @@ abstract class ExchangeViewModelBase with Store { isFixedRateMode: isFixedRateMode, ).then((limits) { /// if the entered amount doesn't exceed the limits of this provider - if ((limits.max ?? double.maxFinite) >= _enteredAmount - && (limits.min ?? 0) <= _enteredAmount) { + if ((limits?.max ?? double.maxFinite) >= _enteredAmount + && (limits?.min ?? 0) <= _enteredAmount) { /// add this provider as its valid for this trade /// will be sorted ascending already since /// we seek the least deposit amount @@ -263,6 +267,10 @@ abstract class ExchangeViewModelBase with Store { currentTradeAvailableProviders.clear(); for (var provider in selectedProviders) { + /// if this provider is not valid for the current pair, skip it + if (!providersForCurrentPair().contains(provider)) { + continue; + } provider .calculateAmount( from: depositCurrency, @@ -286,8 +294,8 @@ abstract class ExchangeViewModelBase with Store { ).then((limits) { /// if the entered amount doesn't exceed the limits of this provider - if ((limits.max ?? double.maxFinite) >= _enteredAmount - && (limits.min ?? 0) <= _enteredAmount) { + if ((limits?.max ?? double.maxFinite) >= _enteredAmount + && (limits?.min ?? 0) <= _enteredAmount) { /// add this provider as its valid for this trade /// subtract from maxFinite so the provider /// with the largest amount would be sorted ascending From bed815e370fee247169620ebe29d3570a17d2e27 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Wed, 7 Sep 2022 14:58:37 +0300 Subject: [PATCH 02/84] update ui bug (#497) --- lib/src/screens/ionia/cards/ionia_account_cards_page.dart | 1 + lib/src/screens/ionia/cards/ionia_account_page.dart | 7 +++++-- lib/src/screens/ionia/cards/ionia_buy_gift_card.dart | 2 -- lib/src/screens/ionia/widgets/card_item.dart | 4 +++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/src/screens/ionia/cards/ionia_account_cards_page.dart b/lib/src/screens/ionia/cards/ionia_account_cards_page.dart index bcce3bf13..5e566b8c1 100644 --- a/lib/src/screens/ionia/cards/ionia_account_cards_page.dart +++ b/lib/src/screens/ionia/cards/ionia_account_cards_page.dart @@ -179,6 +179,7 @@ class _IoniaCardListView extends StatelessWidget { title: merchant.legalName, backgroundColor: Theme.of(context).accentTextTheme.display4.backgroundColor.withOpacity(0.1), discount: 0, + hideBorder: true, discountBackground: AssetImage('assets/images/red_badge_discount.png'), titleColor: Theme.of(context).accentTextTheme.display4.backgroundColor, subtitleColor: Theme.of(context).hintColor, diff --git a/lib/src/screens/ionia/cards/ionia_account_page.dart b/lib/src/screens/ionia/cards/ionia_account_page.dart index 817f966fa..5d985fd25 100644 --- a/lib/src/screens/ionia/cards/ionia_account_page.dart +++ b/lib/src/screens/ionia/cards/ionia_account_page.dart @@ -169,8 +169,11 @@ class _GradiantContainer extends StatelessWidget { borderRadius: BorderRadius.circular(15), gradient: LinearGradient( colors: [ - Theme.of(context).scaffoldBackgroundColor, - Theme.of(context).accentColor, + Theme.of(context) + .primaryTextTheme + .subhead + .decorationColor, + Theme.of(context).primaryTextTheme.subhead.color, ], begin: Alignment.topRight, end: Alignment.bottomLeft, diff --git a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart index 015ded0e8..0cd8354ef 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart @@ -1,4 +1,3 @@ -import 'package:cake_wallet/ionia/ionia_merchant.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/card_item.dart'; @@ -8,7 +7,6 @@ import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart'; -import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; diff --git a/lib/src/screens/ionia/widgets/card_item.dart b/lib/src/screens/ionia/widgets/card_item.dart index a180c2bdd..6c5d3a639 100644 --- a/lib/src/screens/ionia/widgets/card_item.dart +++ b/lib/src/screens/ionia/widgets/card_item.dart @@ -8,6 +8,7 @@ class CardItem extends StatelessWidget { @required this.backgroundColor, @required this.titleColor, @required this.subtitleColor, + this.hideBorder = false, this.discountBackground, this.onTap, this.logoUrl, @@ -21,6 +22,7 @@ class CardItem extends StatelessWidget { final String logoUrl; final double discount; final bool isAmount; + final bool hideBorder; final Color backgroundColor; final Color titleColor; final Color subtitleColor; @@ -38,7 +40,7 @@ class CardItem extends StatelessWidget { decoration: BoxDecoration( color: backgroundColor, borderRadius: BorderRadius.circular(20), - border: Border.all( + border: hideBorder ? Border.symmetric(horizontal: BorderSide.none, vertical: BorderSide.none) : Border.all( color: Colors.white.withOpacity(0.20), ), ), From f59c2fbd2be9828c11d6931ee6a4ce8e07f3334f Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 7 Sep 2022 15:10:21 +0300 Subject: [PATCH 03/84] add new coins to exchange (#499) * add new coins APE, AVAXC, BTT, BTTBSC, DOGE, FIRO, USDTTRC20, HBAR, SC, SOL, USDC, USDCSOL, ZEN, XVG * remove UST icon * remove xhv from excludeDepositCurrencies * remove xhv from excludeReceiveCurrencies --- assets/images/ape_icon.png | Bin 0 -> 29337 bytes assets/images/avaxc_icon.png | Bin 0 -> 4184 bytes assets/images/btt_icon.png | Bin 0 -> 10730 bytes assets/images/bttbsc_icon.png | Bin 0 -> 10730 bytes assets/images/doge_icon.png | Bin 0 -> 17195 bytes assets/images/firo_icon.png | Bin 0 -> 5463 bytes assets/images/hbar_icon.png | Bin 0 -> 3683 bytes assets/images/nano_icon.png | Bin 0 -> 4979 bytes assets/images/sc_icon.png | Bin 0 -> 5519 bytes assets/images/sol_icon.png | Bin 0 -> 6754 bytes assets/images/usdc_icon.png | Bin 0 -> 6923 bytes assets/images/usdcsol_icon.png | Bin 0 -> 6923 bytes assets/images/usdttrc20_icon.png | Bin 0 -> 3177 bytes assets/images/xvg_icon.png | Bin 0 -> 8083 bytes assets/images/zaddr_icon.png | Bin 16312 -> 6158 bytes assets/images/zec_icon.png | Bin 16312 -> 6158 bytes assets/images/zen_icon.png | Bin 0 -> 10640 bytes cw_core/lib/crypto_currency.dart | 109 ++++++++++++++++-- .../changenow_exchange_provider.dart | 6 +- .../widgets/currency_picker_item_widget.dart | 1 - .../exchange/exchange_view_model.dart | 5 +- 21 files changed, 105 insertions(+), 16 deletions(-) create mode 100644 assets/images/ape_icon.png create mode 100644 assets/images/avaxc_icon.png create mode 100644 assets/images/btt_icon.png create mode 100644 assets/images/bttbsc_icon.png create mode 100644 assets/images/doge_icon.png create mode 100644 assets/images/firo_icon.png create mode 100644 assets/images/hbar_icon.png create mode 100644 assets/images/nano_icon.png create mode 100644 assets/images/sc_icon.png create mode 100644 assets/images/sol_icon.png create mode 100644 assets/images/usdc_icon.png create mode 100644 assets/images/usdcsol_icon.png create mode 100644 assets/images/usdttrc20_icon.png create mode 100644 assets/images/xvg_icon.png create mode 100644 assets/images/zen_icon.png diff --git a/assets/images/ape_icon.png b/assets/images/ape_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b9c3d9c3464b73822e070a50f447b6cefdf5a38a GIT binary patch literal 29337 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 literal 0 HcmV?d00001 diff --git a/assets/images/firo_icon.png b/assets/images/firo_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..076d8421b261b85cfc30b8578dd42a007449a9cc GIT binary patch literal 5463 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 M>ay8c>KK~%FDusbs{jB1 literal 0 HcmV?d00001 diff --git a/assets/images/nano_icon.png b/assets/images/nano_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8937b8ed293a23d127cee5c71b30d1731d3f6707 GIT binary patch literal 4979 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 literal 0 HcmV?d00001 diff --git a/assets/images/sc_icon.png b/assets/images/sc_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5ff4b2e21bf60ea4dd0cc4328a624c029d252f81 GIT binary patch literal 5519 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 Ef}prjkN^Mx literal 0 HcmV?d00001 diff --git a/assets/images/usdc_icon.png b/assets/images/usdc_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..283533b42a38d4fd8b1465233d9afb073a67189f GIT binary patch literal 6923 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

T`POuqVfY6Bj06`o1HX#d-jO0O#xsU+3 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 literal 0 HcmV?d00001 diff --git a/assets/images/zaddr_icon.png b/assets/images/zaddr_icon.png index 139acf84c54f2e3622db5346db88043071dea2c2..095ad2c560c70f6a043c2db8f652574783f86254 100644 GIT binary patch literal 6158 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 Zso}Sq{Fyg?a{MECqG6z3{@5<;e*gqh6AJ(U diff --git a/assets/images/zec_icon.png b/assets/images/zec_icon.png index 139acf84c54f2e3622db5346db88043071dea2c2..095ad2c560c70f6a043c2db8f652574783f86254 100644 GIT binary patch literal 6158 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 Zso}Sq{Fyg?a{MECqG6z3{@5<;e*gqh6AJ(U diff --git a/assets/images/zen_icon.png b/assets/images/zen_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2060a0d22fc8073c9863de92144fd315ca46fbd5 GIT binary patch literal 10640 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 with Serializable { CryptoCurrency.eos, CryptoCurrency.eth, CryptoCurrency.ltc, + CryptoCurrency.nano, CryptoCurrency.trx, CryptoCurrency.usdt, CryptoCurrency.usdterc20, CryptoCurrency.xlm, CryptoCurrency.xrp, CryptoCurrency.xhv, - //CryptoCurrency.zaddr, - //CryptoCurrency.zec + CryptoCurrency.ape, + CryptoCurrency.avaxc, + CryptoCurrency.btt, + CryptoCurrency.bttbsc, + CryptoCurrency.doge, + CryptoCurrency.firo, + CryptoCurrency.usdttrc20, + CryptoCurrency.hbar, + CryptoCurrency.sc, + CryptoCurrency.sol, + CryptoCurrency.usdc, + CryptoCurrency.usdcsol, + CryptoCurrency.zaddr, + CryptoCurrency.zec, + CryptoCurrency.zen, + CryptoCurrency.xvg, ]; - static const xmr = CryptoCurrency(title: 'XMR', iconPath: 'assets/images/monero_icon.png', name: 'Monero', raw: 0); + + static const xmr = CryptoCurrency(title: 'XMR', iconPath: 'assets/images/monero_icon.png', name: 'Monero', raw: 0); static const ada = CryptoCurrency(title: 'ADA', iconPath: 'assets/images/ada_icon.png', name: 'Cardano', raw: 1); static const bch = CryptoCurrency(title: 'BCH', iconPath: 'assets/images/bch_icon.png',name: 'Bitcoin Cash', raw: 2); static const bnb = CryptoCurrency(title: 'BNB', iconPath: 'assets/images/bnb_icon.png', tag: 'BSC', name: 'Binance Coin', raw: 3); @@ -41,7 +57,7 @@ class CryptoCurrency extends EnumerableItem with Serializable { static const dash = CryptoCurrency(title: 'DASH', iconPath: 'assets/images/dash_icon.png', name: 'Dash', raw: 6); static const eos = CryptoCurrency(title: 'EOS', iconPath: 'assets/images/eos_icon.png', name: 'EOS', raw: 7); static const eth = CryptoCurrency(title: 'ETH', iconPath: 'assets/images/eth_icon.png', name: 'Ethereum', raw: 8); - static const ltc = CryptoCurrency(title: 'LTC', iconPath: 'assets/images/litecoin-ltc_icon.png', name: 'Litecoin',raw: 9); + static const ltc = CryptoCurrency(title: 'LTC', iconPath: 'assets/images/litecoin-ltc_icon.png', name: 'Litecoin', raw: 9); static const nano = CryptoCurrency(title: 'NANO', raw: 10); static const trx = CryptoCurrency(title: 'TRX', iconPath: 'assets/images/trx_icon.png', name: 'TRON', raw: 11); static const usdt = CryptoCurrency(title: 'USDT', iconPath: 'assets/images/usdt_icon.png', tag: 'OMNI', name: 'USDT', raw: 12); @@ -64,8 +80,25 @@ class CryptoCurrency extends EnumerableItem with Serializable { static const xnzd = CryptoCurrency(title: 'XNZD', tag: 'XHV', raw: 28); static const xusd = CryptoCurrency(title: 'XUSD', tag: 'XHV', raw: 29); - static const zaddr = CryptoCurrency(title: 'ZZEC', tag: 'ZEC', name: 'Shielded Zcash', iconPath: 'assets/images/zaddr_icon.png', raw: 30); - static const zec = CryptoCurrency(title: 'TZEC', tag: 'ZEC', name: 'Transparent Zcash', iconPath: 'assets/images/zec_icon.png', raw: 31); + static const ape = CryptoCurrency(title: 'APE', iconPath: 'assets/images/ape_icon.png', raw: 30); + static const avaxc = CryptoCurrency(title: 'AVAXC', iconPath: 'assets/images/avaxc_icon.png', raw: 31); + static const btt = CryptoCurrency(title: 'BTT', iconPath: 'assets/images/btt_icon.png', raw: 32); + static const bttbsc = CryptoCurrency(title: 'BTTBSC', iconPath: 'assets/images/bttbsc_icon.png', raw: 33); + static const doge = CryptoCurrency(title: 'DOGE', iconPath: 'assets/images/doge_icon.png', raw: 34); + static const firo = CryptoCurrency(title: 'FIRO', iconPath: 'assets/images/firo_icon.png', raw: 35); + static const usdttrc20 = CryptoCurrency(title: 'USDTTRC20', iconPath: 'assets/images/usdttrc20_icon.png', raw: 36); + static const hbar = CryptoCurrency(title: 'HBAR', iconPath: 'assets/images/hbar_icon.png', raw: 37); + static const sc = CryptoCurrency(title: 'SC', iconPath: 'assets/images/sc_icon.png', raw: 38); + static const sol = CryptoCurrency(title: 'SOL', iconPath: 'assets/images/sol_icon.png', raw: 39); + static const usdc = CryptoCurrency(title: 'USDC', iconPath: 'assets/images/usdc_icon.png', raw: 40); + static const usdcsol = CryptoCurrency(title: 'USDCSOL', iconPath: 'assets/images/usdcsol_icon.png', raw: 41); + static const zaddr = CryptoCurrency(title: 'ZZEC', tag: 'ZEC', name: 'Shielded Zcash', iconPath: 'assets/images/zaddr_icon.png', raw: 42); + static const zec = CryptoCurrency(title: 'TZEC', tag: 'ZEC', name: 'Transparent Zcash', iconPath: 'assets/images/zec_icon.png', raw: 43); + static const zen = CryptoCurrency(title: 'ZEN', iconPath: 'assets/images/zen_icon.png', raw: 44); + static const xvg = CryptoCurrency(title: 'XVG', name: 'Verge', iconPath: 'assets/images/xvg_icon.png', raw: 45); + + + static CryptoCurrency deserialize({int raw}) { switch (raw) { @@ -130,9 +163,37 @@ class CryptoCurrency extends EnumerableItem with Serializable { case 29: return CryptoCurrency.xusd; case 30: - return CryptoCurrency.zaddr; + return CryptoCurrency.ape; case 31: + return CryptoCurrency.avaxc; + case 32: + return CryptoCurrency.btt; + case 33: + return CryptoCurrency.bttbsc; + case 34: + return CryptoCurrency.doge; + case 35: + return CryptoCurrency.firo; + case 36: + return CryptoCurrency.usdttrc20; + case 37: + return CryptoCurrency.hbar; + case 38: + return CryptoCurrency.sc; + case 39: + return CryptoCurrency.sol; + case 40: + return CryptoCurrency.usdc; + case 41: + return CryptoCurrency.usdcsol; + case 42: + return CryptoCurrency.zaddr; + case 43: return CryptoCurrency.zec; + case 44: + return CryptoCurrency.zen; + case 45: + return CryptoCurrency.xvg; default: return null; } @@ -164,8 +225,8 @@ class CryptoCurrency extends EnumerableItem with Serializable { return CryptoCurrency.nano; case 'trx': return CryptoCurrency.trx; - case 'usdt': - return CryptoCurrency.usdt; + case 'usdc': + return CryptoCurrency.usdc; case 'usdterc20': return CryptoCurrency.usdterc20; case 'xlm': @@ -200,10 +261,38 @@ class CryptoCurrency extends EnumerableItem with Serializable { return CryptoCurrency.xnzd; case 'xusd': return CryptoCurrency.xusd; + case 'ape': + return CryptoCurrency.ape; + case 'avax': + return CryptoCurrency.avaxc; + case 'btt': + return CryptoCurrency.btt; + case 'bttbsc': + return CryptoCurrency.bttbsc; + case 'doge': + return CryptoCurrency.doge; + case 'firo': + return CryptoCurrency.firo; + case 'usdttrc20': + return CryptoCurrency.usdttrc20; + case 'hbar': + return CryptoCurrency.hbar; + case 'sc': + return CryptoCurrency.sc; + case 'sol': + return CryptoCurrency.sol; + case 'usdt': + return CryptoCurrency.usdt; + case 'usdcsol': + return CryptoCurrency.usdcsol; case 'zaddr': return CryptoCurrency.zaddr; case 'zec': return CryptoCurrency.zec; + case 'zen': + return CryptoCurrency.zen; + case 'xvg': + return CryptoCurrency.xvg; default: return null; } @@ -211,4 +300,4 @@ class CryptoCurrency extends EnumerableItem with Serializable { @override String toString() => title; -} +} \ No newline at end of file diff --git a/lib/exchange/changenow/changenow_exchange_provider.dart b/lib/exchange/changenow/changenow_exchange_provider.dart index e02ae66a9..c004f327a 100644 --- a/lib/exchange/changenow/changenow_exchange_provider.dart +++ b/lib/exchange/changenow/changenow_exchange_provider.dart @@ -96,12 +96,12 @@ class ChangeNowExchangeProvider extends ExchangeProvider { 'Content-Type': 'application/json'}; final flow = getFlow(isFixedRateMode); final body = { - 'fromCurrency': normalizeCryptoCurrency(_request.from), + 'fromCurrency': normalizeCryptoCurrency(_request.from), 'toCurrency': normalizeCryptoCurrency(_request.to), 'fromNetwork': networkFor(_request.from), 'toNetwork': networkFor(_request.to), - 'fromAmount': _request.fromAmount, - 'toAmount': _request.toAmount, + 'fromAmount': _request.fromAmount, + 'toAmount': _request.toAmount, 'address': _request.address, 'flow': flow, 'refundAddress': _request.refundAddress diff --git a/lib/src/screens/exchange/widgets/currency_picker_item_widget.dart b/lib/src/screens/exchange/widgets/currency_picker_item_widget.dart index b710494fd..a23b88c10 100644 --- a/lib/src/screens/exchange/widgets/currency_picker_item_widget.dart +++ b/lib/src/screens/exchange/widgets/currency_picker_item_widget.dart @@ -69,6 +69,5 @@ class PickerItemWidget extends StatelessWidget { ), ), ); - ; } } diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 8ae653e5a..557e3188d 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -40,8 +40,9 @@ class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel; abstract class ExchangeViewModelBase with Store { ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore, this.tradesStore, this._settingsStore, this.sharedPreferences) { - const excludeDepositCurrencies = []; - const excludeReceiveCurrencies = [CryptoCurrency.xlm, CryptoCurrency.xrp, CryptoCurrency.bnb]; + const excludeDepositCurrencies = [CryptoCurrency.btt, CryptoCurrency.nano]; + const excludeReceiveCurrencies = [CryptoCurrency.xlm, CryptoCurrency.xrp, + CryptoCurrency.bnb, CryptoCurrency.btt, CryptoCurrency.nano]; providerList = [ChangeNowExchangeProvider(), SideShiftExchangeProvider(), SimpleSwapExchangeProvider()]; _initialPairBasedOnWallet(); currentTradeAvailableProviders = SplayTreeMap(); From 4ae69d0344d47449979d5ecdc7d64810d39f58e9 Mon Sep 17 00:00:00 2001 From: mkyq <53115730+mkyq@users.noreply.github.com> Date: Wed, 7 Sep 2022 13:46:13 +0100 Subject: [PATCH 04/84] Update text for cake pay learn more notification (#502) --- res/values/strings_de.arb | 2 +- res/values/strings_en.arb | 2 +- res/values/strings_es.arb | 2 +- res/values/strings_fr.arb | 2 +- res/values/strings_hi.arb | 2 +- res/values/strings_hr.arb | 2 +- res/values/strings_it.arb | 2 +- res/values/strings_ja.arb | 2 +- res/values/strings_ko.arb | 2 +- res/values/strings_nl.arb | 2 +- res/values/strings_pl.arb | 2 +- res/values/strings_pt.arb | 2 +- res/values/strings_ru.arb | 2 +- res/values/strings_uk.arb | 2 +- res/values/strings_zh.arb | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index b4a3a923f..3457f8ab2 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -634,7 +634,7 @@ "contact_support": "Support kontaktieren", "gift_cards_unavailable": "Geschenkkarten können derzeit nur über Monero, Bitcoin und Litecoin erworben werden", "introducing_cake_pay": "Einführung von Cake Pay!", - "cake_pay_learn_more": "Karten sofort in der App kaufen und einlösen!\nWischen Sie nach rechts, um mehr zu erfahren!", + "cake_pay_learn_more": "Kaufen und lösen Sie Geschenkkarten sofort in der App ein!\nWischen Sie von links nach rechts, um mehr zu erfahren.", "automatic": "Automatisch", "fixed_pair_not_supported": "Dieses feste Paar wird von den ausgewählten Vermittlungsstellen nicht unterstützt", "variable_pair_not_supported": "Dieses Variablenpaar wird von den ausgewählten Börsen nicht unterstützt", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index c707b0d87..ef8e6e495 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -634,7 +634,7 @@ "contact_support": "Contact Support", "gift_cards_unavailable": "Gift cards are available for purchase only with Monero, Bitcoin, and Litecoin at this time", "introducing_cake_pay": "Introducing Cake Pay!", - "cake_pay_learn_more": "Instantly purchase and redeem cards in the app!\nSwipe right to learn more!", + "cake_pay_learn_more": "Instantly purchase and redeem gift cards in the app!\nSwipe left to right to learn more.", "automatic": "Automatic", "fixed_pair_not_supported": "This fixed pair is not supported with the selected exchanges", "variable_pair_not_supported": "This variable pair is not supported with the selected exchanges", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index f46fab21c..7c996134e 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -634,7 +634,7 @@ "contact_support": "Contactar con Soporte", "gift_cards_unavailable": "Las tarjetas de regalo están disponibles para comprar solo a través de Monero, Bitcoin y Litecoin en este momento", "introducing_cake_pay": "¡Presentamos Cake Pay!", - "cake_pay_learn_more": "¡Compre y canjee tarjetas al instante en la aplicación!\n¡Desliza hacia la derecha para obtener más información!", + "cake_pay_learn_more": "¡Compre y canjee tarjetas de regalo al instante en la aplicación!\nDeslice el dedo de izquierda a derecha para obtener más información.", "automatic": "Automático", "fixed_pair_not_supported": "Este par fijo no es compatible con los intercambios seleccionados", "variable_pair_not_supported": "Este par de variables no es compatible con los intercambios seleccionados", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index d7bd0c84a..405f9eb74 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -632,7 +632,7 @@ "contact_support": "Contacter l'assistance", "gift_cards_unavailable": "Les cartes-cadeaux ne sont disponibles à l'achat que via Monero, Bitcoin et Litecoin pour le moment", "introducing_cake_pay": "Présentation de Cake Pay!", - "cake_pay_learn_more": "Achetez et échangez instantanément des cartes dans l'application !\nBalayez vers la droite pour en savoir plus !", + "cake_pay_learn_more": "Achetez et utilisez instantanément des cartes-cadeaux dans l'application !\nBalayer de gauche à droite pour en savoir plus.", "automatic": "Automatique", "fixed_pair_not_supported": "Cette paire fixe n'est pas prise en charge avec les échanges sélectionnés", "variable_pair_not_supported": "Cette paire de variables n'est pas prise en charge avec les échanges sélectionnés", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 96e0dcb6a..83c0808c4 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -634,7 +634,7 @@ "contact_support": "सहायता से संपर्क करें", "gift_cards_unavailable": "उपहार कार्ड इस समय केवल मोनेरो, बिटकॉइन और लिटकोइन के माध्यम से खरीदने के लिए उपलब्ध हैं", "introducing_cake_pay": "परिचय Cake Pay!", - "cake_pay_learn_more": "ऐप में तुरंत कार्ड खरीदें और रिडीम करें!\nअधिक जानने के लिए दाएं स्वाइप करें!", + "cake_pay_learn_more": "ऐप में उपहार कार्ड तुरंत खरीदें और रिडीम करें!\nअधिक जानने के लिए बाएं से दाएं स्वाइप करें।", "automatic": "स्वचालित", "fixed_pair_not_supported": "यह निश्चित जोड़ी चयनित एक्सचेंजों के साथ समर्थित नहीं है", "variable_pair_not_supported": "यह परिवर्तनीय जोड़ी चयनित एक्सचेंजों के साथ समर्थित नहीं है", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 00a5f04c0..97d4d335c 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -634,7 +634,7 @@ "contact_support": "Kontaktirajte podršku", "gift_cards_unavailable": "Poklon kartice trenutno su dostupne za kupnju samo putem Monera, Bitcoina i Litecoina", "introducing_cake_pay": "Predstavljamo Cake Pay!", - "cake_pay_learn_more": "Odmah kupite i iskoristite kartice u aplikaciji!\nPrijeđite prstom udesno da biste saznali više!", + "cake_pay_learn_more": "Azonnal vásárolhat és válthat be ajándékutalványokat az alkalmazásban!\nTovábbi információért csúsztassa balról jobbra az ujját.", "automatic": "Automatski", "fixed_pair_not_supported": "Ovaj fiksni par nije podržan s odabranim burzama", "variable_pair_not_supported": "Ovaj par varijabli nije podržan s odabranim burzama", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index c88254f8c..cd779471f 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -634,7 +634,7 @@ "contact_support": "Contatta l'assistenza", "gift_cards_unavailable": "Le carte regalo sono disponibili per l'acquisto solo tramite Monero, Bitcoin e Litecoin in questo momento", "introducing_cake_pay": "Presentazione di Cake Pay!", - "cake_pay_learn_more": "Acquista e riscatta istantaneamente le carte nell'app!\nScorri verso destra per saperne di più!", + "cake_pay_learn_more": "Acquista e riscatta istantaneamente carte regalo nell'app!\nScorri da sinistra a destra per saperne di più.", "automatic": "Automatico", "fixed_pair_not_supported": "Questa coppia fissa non è supportata con gli scambi selezionati", "variable_pair_not_supported": "Questa coppia di variabili non è supportata con gli scambi selezionati", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index e909311f6..2b3920862 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -634,7 +634,7 @@ "contact_support": "サポートに連絡する", "gift_cards_unavailable": "現時点では、ギフトカードはMonero、Bitcoin、Litecoinからのみ購入できます。", "introducing_cake_pay": "序章Cake Pay!", - "cake_pay_learn_more": "アプリですぐにカードを購入して引き換えましょう!\n右にスワイプして詳細をご覧ください。", + "cake_pay_learn_more": "アプリですぐにギフトカードを購入して引き換えましょう!\n左から右にスワイプして詳細をご覧ください。", "automatic": "自動", "fixed_pair_not_supported": "この固定ペアは、選択したエクスチェンジではサポートされていません", "variable_pair_not_supported": "この変数ペアは、選択した取引所ではサポートされていません", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 48db01c9a..85ece0603 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -634,7 +634,7 @@ "contact_support": "지원팀에 문의", "gift_cards_unavailable": "기프트 카드는 현재 Monero, Bitcoin 및 Litecoin을 통해서만 구매할 수 있습니다.", "introducing_cake_pay": "소개 Cake Pay!", - "cake_pay_learn_more": "앱에서 즉시 카드를 구매하고 사용하세요!\n자세히 알아보려면 오른쪽으로 스와이프하세요!", + "cake_pay_learn_more": "앱에서 즉시 기프트 카드를 구매하고 사용하세요!\n자세히 알아보려면 왼쪽에서 오른쪽으로 스와이프하세요.", "automatic": "자동적 인", "fixed_pair_not_supported": "이 고정 쌍은 선택한 교환에서 지원되지 않습니다.", "variable_pair_not_supported": "이 변수 쌍은 선택한 교환에서 지원되지 않습니다.", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 9e39b271e..15da6fd7b 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -634,7 +634,7 @@ "contact_support": "Contact opnemen met ondersteuning", "gift_cards_unavailable": "Cadeaubonnen kunnen momenteel alleen worden gekocht via Monero, Bitcoin en Litecoin", "introducing_cake_pay": "Introductie van Cake Pay!", - "cake_pay_learn_more": "Koop en wissel direct kaarten in de app!\nSwipe naar rechts voor meer informatie!", + "cake_pay_learn_more": "Koop en wissel cadeaubonnen direct in de app in!\nSwipe van links naar rechts voor meer informatie.", "automatic": "automatisch", "fixed_pair_not_supported": "Dit vaste paar wordt niet ondersteund bij de geselecteerde exchanges", "variable_pair_not_supported": "Dit variabelenpaar wordt niet ondersteund met de geselecteerde uitwisselingen", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 30eb1eaa3..26d8a38d4 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -634,7 +634,7 @@ "contact_support": "Skontaktuj się z pomocą techniczną", "gift_cards_unavailable": "Karty podarunkowe można obecnie kupić tylko za pośrednictwem Monero, Bitcoin i Litecoin", "introducing_cake_pay": "Przedstawiamy Ciasto Pay!", - "cake_pay_learn_more": "Natychmiast kupuj i realizuj karty w aplikacji!\nPrzesuń w prawo, aby dowiedzieć się więcej!", + "cake_pay_learn_more": "Kupuj i wykorzystuj karty podarunkowe od razu w aplikacji!\nPrzesuń od lewej do prawej, aby dowiedzieć się więcej.", "automatic": "Automatyczny", "fixed_pair_not_supported": "Ta stała para nie jest obsługiwana na wybranych giełdach", "variable_pair_not_supported": "Ta para zmiennych nie jest obsługiwana na wybranych giełdach", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index eac4ac37c..bfb4a2d0a 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -634,7 +634,7 @@ "contact_support": "Contatar Suporte", "gift_cards_unavailable": "Os cartões-presente estão disponíveis para compra apenas através do Monero, Bitcoin e Litecoin no momento", "introducing_cake_pay": "Apresentando o Cake Pay!", - "cake_pay_learn_more": "Compre e resgate cartões instantaneamente no aplicativo!\nDeslize para a direita para saber mais!", + "cake_pay_learn_more": "Compre e resgate vales-presente instantaneamente no app!\nDeslize da esquerda para a direita para saber mais.", "automatic": "Automático", "fixed_pair_not_supported": "Este par fixo não é compatível com as exchanges selecionadas", "variable_pair_not_supported": "Este par de variáveis não é compatível com as trocas selecionadas", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index c82d8969a..94a5b3c0b 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -634,7 +634,7 @@ "contact_support": "Связаться со службой поддержки", "gift_cards_unavailable": "В настоящее время подарочные карты можно приобрести только через Monero, Bitcoin и Litecoin.", "introducing_cake_pay": "Представляем Cake Pay!", - "cake_pay_learn_more": "Мгновенно покупайте и погашайте карты в приложении!\nПроведите вправо, чтобы узнать больше!", + "cake_pay_learn_more": "Мгновенно покупайте и используйте подарочные карты в приложении!\nПроведите по экрану слева направо, чтобы узнать больше.", "automatic": "автоматический", "fixed_pair_not_supported": "Эта фиксированная пара не поддерживается выбранными биржами.", "variable_pair_not_supported": "Эта пара переменных не поддерживается выбранными биржами.", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index caac9111a..b139750be 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -633,7 +633,7 @@ "contact_support": "Звернутися до служби підтримки", "gift_cards_unavailable": "Наразі подарункові картки можна придбати лише через Monero, Bitcoin і Litecoin", "introducing_cake_pay": "Представляємо Cake Pay!", - "cake_pay_learn_more": "Миттєва купівля та погашення карток в додатку!\nПроведіть праворуч, щоб дізнатися більше!", + "cake_pay_learn_more": "Миттєво купуйте та активуйте подарункові картки в додатку!\nПроведіть пальцем зліва направо, щоб дізнатися більше.", "automatic": "Автоматичний", "fixed_pair_not_supported": "Ця фіксована пара не підтримується вибраними біржами", "variable_pair_not_supported": "Ця пара змінних не підтримується вибраними біржами", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index d8b156002..487c6be52 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -632,7 +632,7 @@ "contact_support": "联系支持", "gift_cards_unavailable": "目前只能通过门罗币、比特币和莱特币购买礼品卡", "introducing_cake_pay": "介绍 Cake Pay!", - "cake_pay_learn_more": "立即在应用程序中购买和兑换卡!\n向右滑动了解更多!", + "cake_pay_learn_more": "立即在应用中购买和兑换礼品卡!\n从左向右滑动以了解详情。", "automatic": "自动的", "fixed_pair_not_supported": "所选交易所不支持此固定货币对", "variable_pair_not_supported": "所选交易所不支持此变量对", From f6a2b3c74b819e891aefaa62659629a5fa579c9d Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 7 Sep 2022 15:53:33 +0300 Subject: [PATCH 05/84] Cw 126 check validation for new currencies (#501) * update address validation xmr, ape, avaxc, eth, usdc, hbar, sc, sol, usdcsoul, usdttrc20, btt, bttsc, doge,firo, xvg, zen, tzec, zzec * fix formating --- lib/core/address_validator.dart | 50 +++++++++++++++++-- .../widgets/currency_picker_item_widget.dart | 2 +- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index fec250a08..b9d6cf0d6 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -13,10 +13,14 @@ class AddressValidator extends TextValidator { static String getPattern(CryptoCurrency type) { switch (type) { case CryptoCurrency.xmr: - return '[0-9a-zA-Z]'; + return '^4[0-9a-zA-Z]{94}\$|^8[0-9a-zA-Z]{94}\$|^[0-9a-zA-Z]{106}\$'; case CryptoCurrency.ada: return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$' '|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$'; + case CryptoCurrency.ape: + return '0x[0-9a-zA-Z]'; + case CryptoCurrency.avaxc: + return '0x[0-9a-zA-Z]'; case CryptoCurrency.bch: return '[0-9a-zA-Z]'; case CryptoCurrency.bnb: @@ -31,13 +35,15 @@ class AddressValidator extends TextValidator { case CryptoCurrency.eos: return '[0-9a-zA-Z]'; case CryptoCurrency.eth: - return '[0-9a-zA-Z]'; + return '0x[0-9a-zA-Z]'; case CryptoCurrency.ltc: return '[0-9a-zA-Z]'; case CryptoCurrency.nano: return '[0-9a-zA-Z_]'; case CryptoCurrency.trx: return '[0-9a-zA-Z]'; + case CryptoCurrency.usdc: + return '0x[0-9a-zA-Z]'; case CryptoCurrency.usdt: return '[0-9a-zA-Z]'; case CryptoCurrency.usdterc20: @@ -62,6 +68,12 @@ class AddressValidator extends TextValidator { case CryptoCurrency.xnzd: case CryptoCurrency.xusd: return '[0-9a-zA-Z]'; + case CryptoCurrency.hbar: + return '[0-9a-zA-Z.]'; + case CryptoCurrency.zaddr: + return '^zs[0-9a-zA-Z]{75}\&|^zc[0-9a-zA-Z]{93}\$'; + case CryptoCurrency.zec: + return '^t1[0-9a-zA-Z]{33}\$|^t3[0-9a-zA-Z]{33}\$'; default: return '[0-9a-zA-Z]'; } @@ -70,9 +82,13 @@ class AddressValidator extends TextValidator { static List getLength(CryptoCurrency type) { switch (type) { case CryptoCurrency.xmr: - return [95, 106]; + return null; case CryptoCurrency.ada: return null; + case CryptoCurrency.ape: + return [42]; + case CryptoCurrency.avaxc: + return [42]; case CryptoCurrency.bch: return [42]; case CryptoCurrency.bnb: @@ -91,12 +107,22 @@ class AddressValidator extends TextValidator { return [34, 43]; case CryptoCurrency.nano: return [64, 65]; + case CryptoCurrency.sc: + return [76]; + case CryptoCurrency.sol: + return [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44]; case CryptoCurrency.trx: return [34]; + case CryptoCurrency.usdc: + return [42]; + case CryptoCurrency.usdcsol: + return [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44]; case CryptoCurrency.usdt: return [34]; case CryptoCurrency.usdterc20: return [42]; + case CryptoCurrency.usdttrc20: + return [34]; case CryptoCurrency.xlm: return [56]; case CryptoCurrency.xrp: @@ -116,6 +142,24 @@ class AddressValidator extends TextValidator { case CryptoCurrency.xnzd: case CryptoCurrency.xusd: return [98, 99, 106]; + case CryptoCurrency.btt: + return [34]; + case CryptoCurrency.bttbsc: + return [34]; + case CryptoCurrency.doge: + return [34]; + case CryptoCurrency.firo: + return [34]; + case CryptoCurrency.hbar: + return [4, 5, 6, 7, 8, 9, 10, 11]; + case CryptoCurrency.xvg: + return [34]; + case CryptoCurrency.zen: + return [35]; + case CryptoCurrency.zaddr: + return null; + case CryptoCurrency.zec: + return null; default: return []; } diff --git a/lib/src/screens/exchange/widgets/currency_picker_item_widget.dart b/lib/src/screens/exchange/widgets/currency_picker_item_widget.dart index a23b88c10..1c7fb81a3 100644 --- a/lib/src/screens/exchange/widgets/currency_picker_item_widget.dart +++ b/lib/src/screens/exchange/widgets/currency_picker_item_widget.dart @@ -22,7 +22,7 @@ class PickerItemWidget extends StatelessWidget { children: [ Container( child: Image.asset( - iconPath, + iconPath ?? '', height: 20.0, width: 20.0, ), From 7aa5013b5db3a5603afae9376a0097cf37b85184 Mon Sep 17 00:00:00 2001 From: Mathias Herberts Date: Wed, 7 Sep 2022 15:13:54 +0200 Subject: [PATCH 06/84] Fixed French translations (#384) * Fixed French translations * Update strings_fr.arb Add Haven to first_wallet_text for FR Co-authored-by: mkyq <53115730+mkyq@users.noreply.github.com> --- assets/faq/faq_fr.json | 28 ++++---- res/values/strings_fr.arb | 138 +++++++++++++++++++------------------- 2 files changed, 83 insertions(+), 83 deletions(-) diff --git a/assets/faq/faq_fr.json b/assets/faq/faq_fr.json index f1e3585bc..cc4e52873 100644 --- a/assets/faq/faq_fr.json +++ b/assets/faq/faq_fr.json @@ -5,31 +5,31 @@ }, { "question" : "Comment envoyer des Monero vers une plateforme d'échange qui nécessite un ID de paiement ?", - "answer" : "Appuyez sur le bouton envoyer de l'écran du Wallet. Ensuite, copiez l'adresse de dépôt de la plateforme d'échange et collez là dans le champ adresse. Puis, copiez l'ID de paiement fourni par la plateforme d'échange et copiez le dans le champ ID de paiement. Enfin, entrez le montant que vous souhaitez envoyer et vous êtes fin prêt !\n" + "answer" : "Appuyez sur le bouton envoyer de l'écran du Portefeuille (Wallet). Ensuite, copiez l'adresse de dépôt de la plateforme d'échange et collez là dans le champ adresse. Puis, copiez l'ID de paiement fourni par la plateforme d'échange et copiez le dans le champ ID de paiement. Enfin, entrez le montant que vous souhaitez envoyer et vous êtes fin prêt !\n" }, { "question" : "Que faire si j'oublie d'entrer l'ID de paiement quand j'envoie des Monero vers une plateforme d'échange ?", - "answer" : "Bien que notre service de support ne puisse pas vous aider directement pour ce type de souci, c'est un problème très courant que la plupart des plateformes d'échange ont l'habitude de gérer. Contactez simplement le support de la plateforme d'échange, expliquez que vous avez oublié d'inclure l'ID de paiement et envoyez leur l'ID de transaction comme preuve. Vous pouvez visualiser l'ID de transaction en tapant sur transaction sur l'écran de votre wallet.\n" + "answer" : "Bien que notre service de support ne puisse pas vous aider directement pour ce type de souci, c'est un problème très courant que la plupart des plateformes d'échange ont l'habitude de gérer. Contactez simplement le support de la plateforme d'échange, expliquez que vous avez oublié d'inclure l'ID de paiement et envoyez leur l'ID de transaction comme preuve. Vous pouvez visualiser l'ID de transaction en tapant sur transaction sur l'écran de votre portefeuille (wallet).\n" }, { - "question" : "Que signifient \"seed\" et \"clefs\" ?", - "answer" : "Vos clefs encodent l'information privée de votre wallet, ce sont elles qui permettent de dépenser vos fonds et de visualiser les transactions entrantes.\nVotre seed est simplement une version de votre clef privée sous une forme plus simple à recopier. Votre seed et vos clefs sont la même chose, juste sous des formes différentes !\nNE DONNEZ JAMAIS votre seed ou vos clefs à quiconque. Vos fonds seront dérobés si vous donner votre seed ou vos clefs. Merci d'écrire cependant votre seed et de le stocker en lieu sûr (afin de vous permettre de restaurer votre wallet si vous perdez votre téléphone.)\n" + "question" : "Que signifient \"phrase secrète (seed)\" et \"clefs\" ?", + "answer" : "Vos clefs encodent l'information privée de votre portefeuille (wallet), ce sont elles qui permettent de dépenser vos fonds et de visualiser les transactions entrantes.\nVotre phrase secrète (seed) est simplement une version de votre clef privée sous une forme plus simple à recopier. Votre phrase secrète et vos clefs sont la même chose, juste sous des formes différentes !\nNE DONNEZ JAMAIS votre phrase secrète ou vos clefs à quiconque. Vos fonds seront dérobés si vous donnez votre phrase secrète ou vos clefs. Merci d'écrire cependant votre phrase secrète et de la stocker en lieu sûr (afin de vous permettre de restaurer votre portefeuille si vous perdez votre téléphone.)\n" }, { - "question" : "Combien de wallets puis-je créer ?", - "answer" : "Il n'y a pas de limite ! Vous pouvez créer autant de wallets que vous le souhaitez.\n" + "question" : "Combien de portefeuilles (wallets) puis-je créer ?", + "answer" : "Il n'y a pas de limite ! Vous pouvez créer autant de portefeuilles (wallets) que vous le souhaitez.\n" }, { - "question" : "Commen puis-je restarurer mon wallet ?", - "answer" : "Appuyez sur le menu •••, sélectionnez Wallets, puis choisissez Restaurer un Wallet. Entrez alors votre seed (ou vos clefs), et de façon optionnelle une date antérieure à la première transaction de votre wallet (cela permettra d'accélérer le processus de synchronisation). Vous pourrez avoir besoin de maintenir l'application ouverte pendant 15 à 30 minutes afin de restaurer complètement votre wallet.\n" + "question" : "Commen puis-je restarurer mon portefeuille (wallet) ?", + "answer" : "Appuyez sur le menu •••, sélectionnez Portefeuilles (Wallets), puis choisissez Restaurer un Portefeuille. Entrez alors votre phrase secrète (seed) (ou vos clefs), et de façon optionnelle une date antérieure à la première transaction de votre portefeuille (cela permettra d'accélérer le processus de synchronisation). Vous pourrez avoir besoin de maintenir l'application ouverte pendant 15 à 30 minutes afin de restaurer complètement votre portefeuille.\n" }, { - "question" : "Que puis-je faire si je perds mon seed ?", - "answer" : "Si vous oubliez votre seed, il y a des chances que vous l'ayez inscrit quelque part. Vérifiez vos notes et regardez sur votre ordinateur. Si vous ne parvenez pas à le retrouver, il est possible que vous ayez effectué une sauvegarde de Cake Wallet (dans ce cas vous pourrez restaurer d'après cette sauvegarde). Si aucune des ces options ne convient, malheureusement il n'y a plus rien à faire, vos fonds sont définitivement perdus.\n" + "question" : "Que puis-je faire si je perds ma phrase secrète (seed) ?", + "answer" : "Si vous oubliez votre phrase secrète (seed), il y a des chances que vous l'ayez inscrite quelque part. Vérifiez vos notes et regardez sur votre ordinateur. Si vous ne parvenez pas à la retrouver, il est possible que vous ayez effectué une sauvegarde de Cake Wallet (dans ce cas vous pourrez restaurer d'après cette sauvegarde). Si aucune des ces options ne convient, malheureusement il n'y a plus rien à faire, vos fonds sont définitivement perdus.\n" }, { - "question" : "Collectez vous des informations à propos de mon wallet ?", - "answer" : "Cake Wallet NE COLLECTE PAS d'informations à propos de votre wallet. Nous sommes respectueux de votre intimité.\n" + "question" : "Collectez vous des informations à propos de mon portefeuille (wallet) ?", + "answer" : "Cake Wallet NE COLLECTE PAS d'informations à propos de votre portefeuille (wallet). Nous sommes respectueux de votre intimité.\n" }, { "question" : "Est il possible d'annuler une transaction ?", @@ -37,11 +37,11 @@ }, { "question" : "Que sont les sous-adresses, et comment s'en servir ?", - "answer" : "Une sous-adresse est une adresse unique que vous pouvez générer à tout moment. Les montants envoyés vers cette adresse arrivent toujours dans votre wallet principal, mais la personne qui vous envoie les fonds ne peut pas déterminer votre adresse principale. Les sous-adresses commencent par un 8.\nVous pouvez créer une nouvelle sous-adresse dans l'écran Réception en appuyant sur le + à côté du bouton Sous-Adresses. Entrez un nom pour la sous-adresse et appuyez sur Ajouter. Ensuite appuyez sur le nom de la sous-adresse quand vous souhaitez l'utiliser !\nSi vous êtres paranoïaque, vous devriez créer une nouvelle sous-adresse à chaque fois que vous voulez recevoir des Monero.\n" + "answer" : "Une sous-adresse est une adresse unique que vous pouvez générer à tout moment. Les montants envoyés vers cette adresse arrivent toujours dans votre portefeuille (wallet) principal, mais la personne qui vous envoie les fonds ne peut pas déterminer votre adresse principale. Les sous-adresses commencent par un 8.\nVous pouvez créer une nouvelle sous-adresse dans l'écran Réception en appuyant sur le + à côté du bouton Sous-Adresses. Entrez un nom pour la sous-adresse et appuyez sur Ajouter. Ensuite appuyez sur le nom de la sous-adresse quand vous souhaitez l'utiliser !\nSi vous êtres paranoïaque, vous devriez créer une nouvelle sous-adresse à chaque fois que vous voulez recevoir des Monero.\n" }, { "question" : "Qu'est-ce que l'ID de transaction ?", - "answer" : "Une empreinte (hash) de transaction, ou ID de transaction, est un moyen unique d'identifier une transaction. Chaque transaction a sa propre empreinte. Si vous deve fournir une empreinte de transaction à quelque'un, allez simplement sur l'écran principal du Wallet, appuyez sur la transaction puis appuyez longuement sur la section du haut et sélectionnez Copier.\n" + "answer" : "Une empreinte (hash) de transaction, ou ID de transaction, est un moyen unique d'identifier une transaction. Chaque transaction a sa propre empreinte. Si vous deve fournir une empreinte de transaction à quelque'un, allez simplement sur l'écran principal du Portefeuille (Wallet), appuyez sur la transaction puis appuyez longuement sur la section du haut et sélectionnez Copier.\n" }, { "question" : "Je n'ai pas reçu mes XMR ! Que puis-je faire ?", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 405f9eb74..7d5a3c74d 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -1,13 +1,13 @@ { "welcome" : "Bienvenue sur", "cake_wallet" : "Cake Wallet", - "first_wallet_text" : "Super portefeuille pour Monero, Bitcoin, Litecoin, et Haven", - "please_make_selection" : "Merci de faire un choix ci-dessous pour créer ou restaurer votre portefeuille.", - "create_new" : "Créer un Nouveau Portefeuille", - "restore_wallet" : "Restaurer un Portefeuille", + "first_wallet_text" : "Super portefeuille (wallet) pour Monero, Bitcoin, Litecoin et Haven", + "please_make_selection" : "Merci de faire un choix ci-dessous pour créer ou restaurer votre portefeuille (wallet).", + "create_new" : "Créer un Nouveau Portefeuille (Wallet)", + "restore_wallet" : "Restaurer un Portefeuille (Wallet)", "monero_com": "Monero.com par Cake Wallet", - "monero_com_wallet_text": "Super portefeuille pour Monero", + "monero_com_wallet_text": "Super portefeuille (wallet) pour Monero", "accounts" : "Comptes", @@ -54,9 +54,9 @@ "pending" : " (en attente)", "rescan" : "Analyser la blockchain", "reconnect" : "Reconnecter", - "wallets" : "Portefeuilles", - "show_seed" : "Visualiser la graine", - "show_keys" : "Visualiser graine/clefs", + "wallets" : "Portefeuilles (Wallets)", + "show_seed" : "Visualiser la phrase secrète (seed)", + "show_keys" : "Visualiser les /clefs", "address_book_menu" : "Carnet d'Adresses", "reconnection" : "Reconnexion", "reconnect_alert_text" : "Êtes vous certain de vouloir vous reconnecter ?", @@ -77,7 +77,7 @@ "max_value" : "Max: ${value} ${currency}", "change_currency" : "Changer de Devise", "overwrite_amount" : "Écraser montant", - "qr_payment_amount" : "Ce QR code contient un montant de paiement. Voulez-vous écraser la valeur actuelle?", + "qr_payment_amount" : "Ce QR code contient un montant de paiement. Voulez-vous écraser la valeur actuelle ?", "copy_id" : "Copier l'ID", "exchange_result_write_down_trade_id" : "Merci de copier ou d'écrire l'ID d'échange pour continuer.", @@ -92,7 +92,7 @@ "offer_expires_in" : "L'Offre expire dans : ", "trade_is_powered_by" : "Cet échange est proposé par ${provider}", "copy_address" : "Copier l'Adresse", - "exchange_result_confirm" : "En pressant confirmer, vous enverrez ${fetchingLabel} ${from} depuis votre portefeuille nommé ${walletName} vers l'adresse ci-dessous. Vous pouvez aussi envoyer depuis votre portefeuille externe vers l'adresse/QR code ci-dessous.\n\nMerci d'appuyer sur confirmer pour continuer ou retournez en arrière pour modifier les montants.", + "exchange_result_confirm" : "En pressant confirmer, vous enverrez ${fetchingLabel} ${from} depuis votre portefeuille (wallet) nommé ${walletName} vers l'adresse ci-dessous. Vous pouvez aussi envoyer depuis votre portefeuille externe vers l'adresse/QR code ci-dessous.\n\nMerci d'appuyer sur confirmer pour continuer ou retournez en arrière pour modifier les montants.", "exchange_result_description" : "Vous devez envoyer un minimum de ${fetchingLabel} ${from} à l'adresse indiquée page suivante. Si vous envoyez un montant inférieur à ${fetchingLabel} ${from} il pourrait ne pas être converti et ne pas être remboursé.", "exchange_result_write_down_ID" : "*Merci de copier ou écrire votre ID présenté ci-dessus.", "confirm" : "Confirmer", @@ -109,13 +109,13 @@ "enter_your_pin" : "Entrez votre code PIN", - "loading_your_wallet" : "Chargement de votre portefeuille", + "loading_your_wallet" : "Chargement de votre portefeuille (wallet)", - "new_wallet" : "Nouveau Portefeuille", - "wallet_name" : "Nom du Portefeuille", + "new_wallet" : "Nouveau Portefeuille (Wallet)", + "wallet_name" : "Nom du Portefeuille (Wallet)", "continue_text" : "Continuer", - "choose_wallet_currency" : "Merci de choisir la devise du portefeuille :", + "choose_wallet_currency" : "Merci de choisir la devise du portefeuille (wallet) :", "node_new" : "Nouveau Nœud", @@ -126,7 +126,7 @@ "nodes" : "Nœuds", "node_reset_settings_title" : "Réinitialisation des réglages", "nodes_list_reset_to_default_message" : "Êtes vous certain de vouloir revenir aux réglages par défaut ?", - "change_current_node" : "Êtes vous certain de vouloir changer le nœud actuel vers ${node}?", + "change_current_node" : "Êtes vous certain de vouloir changer le nœud actuel vers ${node} ?", "change" : "Changer", "remove_node" : "Supprimer le nœud", "remove_node_message" : "Êtes vous certain de vouloir supprimer le nœud sélectionné ?", @@ -156,39 +156,39 @@ "accounts_subaddresses" : "Comptes et sous-adresses", - "restore_restore_wallet" : "Restaurer le Portefeuille", - "restore_title_from_seed_keys" : "Restaurer depuis une graine ou des clefs", - "restore_description_from_seed_keys" : "Restaurez votre portefeuille depuis une graine ou des clefs que vous avez stockés en lieu sûr", + "restore_restore_wallet" : "Restaurer le Portefeuille (Wallet)", + "restore_title_from_seed_keys" : "Restaurer depuis une phrase secrète (seed) ou des clefs", + "restore_description_from_seed_keys" : "Restaurez votre portefeuille (wallet) depuis une phrase secrète (seed) ou des clefs que vous avez stockées en lieu sûr", "restore_next" : "Suivant", "restore_title_from_backup" : "Restaurer depuis une sauvegarde", "restore_description_from_backup" : "Vous pouvez restaurer l'intégralité de l'application Cake Wallet depuis un fichier de sauvegarde", - "restore_seed_keys_restore" : "Restaurer depuis Graine/Clefs", - "restore_title_from_seed" : "Restaurer depuis graine", - "restore_description_from_seed" : "Restaurez votre portefeuille depuis une combinaison de 25 ou 13 mots", + "restore_seed_keys_restore" : "Restaurer depuis Phrase secrète (seed)/Clefs", + "restore_title_from_seed" : "Restaurer depuis une phrase secrète (seed)", + "restore_description_from_seed" : "Restaurer votre portefeuille (wallet) depuis une phrase secrète (seed) de 25 ou 13 mots", "restore_title_from_keys" : "Restaurer depuis des clefs", - "restore_description_from_keys" : "Restaurer votre portefeuille d'après les séquences de touches générées d'après vos clefs privées", - "restore_wallet_name" : "Nom du portefeuille", + "restore_description_from_keys" : "Restaurer votre portefeuille (wallet) d'après les séquences de touches générées d'après vos clefs privées", + "restore_wallet_name" : "Nom du portefeuille (wallet)", "restore_address" : "Adresse", "restore_view_key_private" : "Clef d'audit (view key) (privée)", "restore_spend_key_private" : "Clef de dépense (spend key) (privée)", "restore_recover" : "Restaurer", - "restore_wallet_restore_description" : "Description de la restauration de portefeuille", - "restore_new_seed" : "Nouvelle graine", - "restore_active_seed" : "Graine actif", - "restore_bitcoin_description_from_seed" : "Restaurer votre portefeuille à l'aide d'une combinaison de 24 mots", - "restore_bitcoin_description_from_keys" : "Restaurer votre portefeuille d'après la chaîne WIF générée d'après vos clefs privées", + "restore_wallet_restore_description" : "Description de la restauration de portefeuille (wallet)", + "restore_new_seed" : "Nouvelle phrase secrète (seed)", + "restore_active_seed" : "Phrase secrète (seed) active", + "restore_bitcoin_description_from_seed" : "Restaurer votre portefeuille (wallet) à l'aide d'une phrase secrète (seed) de 24 mots", + "restore_bitcoin_description_from_keys" : "Restaurer votre portefeuille (wallet) d'après la chaîne WIF générée d'après vos clefs privées", "restore_bitcoin_title_from_keys" : "Restaurer depuis la chaîne WIF", - "restore_from_date_or_blockheight" : "Merci d'entrer une date antérieure de quelques jours à la date de création de votre portefeuille. Ou si vous connaissez la hauteur de bloc, merci de la spécifier plutôt qu'une date", + "restore_from_date_or_blockheight" : "Merci d'entrer une date antérieure de quelques jours à la date de création de votre portefeuille (wallet). Ou si vous connaissez la hauteur de bloc, merci de la spécifier plutôt qu'une date", - "seed_reminder" : "Merci d'écrire votre graine au cas où vous perdriez ou effaceriez votre téléphone", - "seed_title" : "Graine", - "seed_share" : "Partager la graine", + "seed_reminder" : "Merci d'écrire votre phrase secrète (seed) au cas où vous perdriez ou effaceriez votre téléphone", + "seed_title" : "Phrase secrète (seed)", + "seed_share" : "Partager la phrase secrète (seed)", "copy" : "Copier", - "seed_language_choose" : "Merci de choisir la langue de la graine :", - "seed_choose" : "Choisissez la langue de la graine", + "seed_language_choose" : "Merci de choisir la langue de la phrase secrète (seed) :", + "seed_choose" : "Choisissez la langue de la phrase secrète (seed)", "seed_language_next" : "Suivant", "seed_language_english" : "Anglais", "seed_language_chinese" : "Chinois", @@ -203,7 +203,7 @@ "send_title" : "Envoyer", - "send_your_wallet" : "Votre portefeuille", + "send_your_wallet" : "Votre portefeuille (wallet)", "send_address" : "adresse ${cryptoCurrency}", "send_payment_id" : "ID de paiement (optionnel)", "all" : "TOUT", @@ -225,8 +225,8 @@ "settings_title" : "Réglages", "settings_nodes" : "Nœuds", "settings_current_node" : "Nœud actuel", - "settings_wallets" : "Portefeuilles", - "settings_display_balance" : "Afficher le solde", + "settings_wallets" : "Portefeuilles (Wallets)", + "settings_display_balance_as" : "Affichage du solde", "settings_currency" : "Devise", "settings_fee_priority" : "Priorité des frais", "settings_save_recipient_address" : "Sauvegarder l'adresse du bénéficiaire", @@ -252,8 +252,8 @@ "setup_successful" : "Votre code PIN a été configuré avec succès !", - "wallet_keys" : "Graine/Clefs du portefeuille", - "wallet_seed" : "Graine du portefeuille", + "wallet_keys" : "Phrase secrète (seed)/Clefs du portefeuille (wallet)", + "wallet_seed" : "Phrase secrète (seed) du portefeuille (wallet)", "private_key" : "Clef privée", "public_key" : "Clef publique", "view_key_private" : "Clef d'audit (view key) (privée)", @@ -295,21 +295,21 @@ "transaction_details_recipient_address" : "Adresse du bénéficiaire", - "wallet_list_title" : "Portefeuille Monero", - "wallet_list_create_new_wallet" : "Créer un Nouveau Portefeuille", - "wallet_list_restore_wallet" : "Restaurer un Portefeuille", - "wallet_list_load_wallet" : "Charger Portefeuille", - "wallet_list_loading_wallet" : "Chargement du portefeuille ${wallet_name}", - "wallet_list_failed_to_load" : "Échec de chargement du portefeuille ${wallet_name}. ${error}", - "wallet_list_removing_wallet" : "Suppression du portefeuille ${wallet_name}", - "wallet_list_failed_to_remove" : "Échec de la suppression du portefeuille ${wallet_name}. ${error}", + "wallet_list_title" : "Portefeuille (Wallet) Monero", + "wallet_list_create_new_wallet" : "Créer un Nouveau Portefeuille (Wallet)", + "wallet_list_restore_wallet" : "Restaurer un Portefeuille (Wallet)", + "wallet_list_load_wallet" : "Charger un Portefeuille (Wallet)", + "wallet_list_loading_wallet" : "Chargement du portefeuille (wallet) ${wallet_name}", + "wallet_list_failed_to_load" : "Échec de chargement du portefeuille (wallet) ${wallet_name}. ${error}", + "wallet_list_removing_wallet" : "Suppression du portefeuille (wallet) ${wallet_name}", + "wallet_list_failed_to_remove" : "Échec de la suppression du portefeuille (wallet) ${wallet_name}. ${error}", "widgets_address" : "Adresse", "widgets_restore_from_blockheight" : "Restaurer depuis une hauteur de bloc", "widgets_restore_from_date" : "Restaurer depuis une date", "widgets_or" : "ou", - "widgets_seed" : "Graine", + "widgets_seed" : "Phrase secrète (seed)", "router_no_route" : "Aucune route définie pour ${name}", @@ -317,7 +317,7 @@ "error_text_account_name" : "Le nom de compte ne peut contenir que des lettres et des chiffres\net sa longueur doit être comprise entre 1 et 15 caractères", "error_text_contact_name" : "Un nom de contact ne peut pas contenir les symboles ` , ' \"\net doit faire entre 1 et 32 caractères", - "error_text_address" : "L'adresse du portefeuille doit correspondre au type de\ncryptomonnaie", + "error_text_address" : "L'adresse du portefeuille (wallet) doit correspondre au type de\ncryptomonnaie", "error_text_node_address" : "Merci d'entrer une adresse IPv4", "error_text_node_port" : "Le port d'un nœud doit être un nombre compris entre 0 et 65535", "error_text_payment_id" : "Un ID de paiement ne peut être constitué que de 16 à 64 caractères hexadécimaux", @@ -325,8 +325,8 @@ "error_text_fiat" : "La valeur du montant ne peut dépasser le solde disponible.\nLa partie décimale doit comporter au plus 2 chiffres", "error_text_subaddress_name" : "Le nom de sous-adresse ne peut contenir les symboles ` , ' \"\net sa longueur doit être comprise entre 1 et 20 caractères", "error_text_amount" : "Le montant ne peut comporter que des nombres", - "error_text_wallet_name" : "Le nom du portefeuille ne peut contenir que des lettres, des chiffres, des symboles _ -\net sa longueur doit être comprise entre 1 et 33 caractères", - "error_text_keys" : "Les clefs du portefeuille ne peuvent être constituées que de 64 caractères hexadécimaux", + "error_text_wallet_name" : "Le nom du portefeuille (wallet) ne peut contenir que des lettres et des chiffres\net sa longueur doit être comprise entre 1 et 15 caractères", + "error_text_keys" : "Les clefs du portefeuille (wallet) ne peuvent être constituées que de 64 caractères hexadécimaux", "error_text_crypto_currency" : "La partie décimale\ndoit comporter au plus 12 chiffres", "error_text_minimal_limit" : "Échange pour ${provider} non créé. Le montant est inférieur au minimum : ${min} ${currency}", "error_text_maximum_limit" : "Échange pour ${provider} non créé. Le montant est supérieur au maximum : ${max} ${currency}", @@ -338,8 +338,8 @@ "auth_store_banned_for" : "Banni pour ", "auth_store_banned_minutes" : " minutes", "auth_store_incorrect_password" : "Mauvais code PIN", - "wallet_store_monero_wallet" : "Portefeuille Monero", - "wallet_restoration_store_incorrect_seed_length" : "Longueur de graine incorrecte", + "wallet_store_monero_wallet" : "Portefeuille (Wallet) Monero", + "wallet_restoration_store_incorrect_seed_length" : "Longueur de phrase secrète (seed) incorrecte", "full_balance" : "Solde Complet", @@ -385,10 +385,10 @@ "trade_state_finished" : "Terminé", "change_language" : "Changer de langue", - "change_language_to" : "Changer la langue vers ${language}?", + "change_language_to" : "Changer la langue vers ${language} ?", "paste" : "Coller", - "restore_from_seed_placeholder" : "Merci d'entrer ou de coller votre graine ici", + "restore_from_seed_placeholder" : "Merci d'entrer ou de coller votre phrase secrète (seed) ici", "add_new_word" : "Ajouter un nouveau mot", "incorrect_seed" : "Le texte entré est invalide.", @@ -407,26 +407,26 @@ "template" : "Modèle", "confirm_delete_template" : "Cette action va supprimer ce modèle. Souhaitez-vous continuer ?", - "confirm_delete_wallet" : "Cette action va supprimer ce portefeuille. Souhaitez-vous contnuer ?", + "confirm_delete_wallet" : "Cette action va supprimer ce portefeuille (wallet). Souhaitez-vous contnuer ?", "picker_description" : "Pour choisir ChangeNOW ou MorphToken, merci de modifier d'abord la paire de votre échange", - "change_wallet_alert_title" : "Changer le portefeuille actuel", - "change_wallet_alert_content" : "Souhaitez-vous changer le portefeuille actuel vers ${wallet_name}?", + "change_wallet_alert_title" : "Changer le portefeuille (wallet) actuel", + "change_wallet_alert_content" : "Souhaitez-vous changer le portefeuille (wallet) actuel vers ${wallet_name} ?", - "creating_new_wallet" : "Création d'un nouveau portefeuille", + "creating_new_wallet" : "Création d'un nouveau portefeuille (wallet)", "creating_new_wallet_error" : "Erreur : ${description}", "seed_alert_title" : "Attention", - "seed_alert_content" : "La graine est la seule façon de restaurer votre portefeuille. L'avez-vous correctement écrit ?", + "seed_alert_content" : "La phrase secrète (seed) est la seule façon de restaurer votre portefeuille (wallet). L'avez-vous correctement écrite ?", "seed_alert_back" : "Retour", "seed_alert_yes" : "Oui, je suis sûr", - "exchange_sync_alert_content" : "Merci d'attendre que votre portefeuille soit synchronisé", + "exchange_sync_alert_content" : "Merci d'attendre que votre portefeuille (wallet) soit synchronisé", "pre_seed_title" : "IMPORTANT", - "pre_seed_description" : "Sur la page suivante vous allez voir une série de ${words} mots. Ils constituent votre graine unique et privé et sont le SEUL moyen de restaurer votre portefeuille en cas de perte ou de dysfonctionnement. Il est de VOTRE responsabilité d'écrire cette série de mots et de les stocker dans un lieu sûr en dehors de l'application Cake Wallet.", - "pre_seed_button_text" : "J'ai compris. Montrez moi ma graine", + "pre_seed_description" : "Sur la page suivante vous allez voir une série de ${words} mots. Ils constituent votre phrase secrète (seed) unique et privé et sont le SEUL moyen de restaurer votre portefeuille (wallet) en cas de perte ou de dysfonctionnement. Il est de VOTRE responsabilité d'écrire cette série de mots et de les stocker dans un lieu sûr en dehors de l'application Cake Wallet.", + "pre_seed_button_text" : "J'ai compris. Montrez moi ma phrase secrète (seed)", "xmr_to_error" : "Erreur XMR.TO", "xmr_to_error_description" : "Montant invalide. La partie décimale doit contenir au plus 8 chiffres", @@ -475,17 +475,17 @@ "xlm_extra_info" : "Merci de ne pas oublier de spécifier l'ID de mémo lors de l'envoi de la transaction XLM de l'échange", "xrp_extra_info" : "Merci de ne pas oublier de spécifier le tag de destination lors de l'envoi de la transaction XRP de l'échange", - "exchange_incorrect_current_wallet_for_xmr" : "Si vous souhaitez échanger des XMR du solde Monero de votre Cake Wallet, merci de sélectionner votre portefeuille Monero au préalable.", + "exchange_incorrect_current_wallet_for_xmr" : "Si vous souhaitez échanger des XMR du solde Monero de votre Cake Wallet, merci de sélectionner votre portefeuille (wallet) Monero au préalable.", "confirmed" : "Confirmé", "unconfirmed" : "Non confirmé", "displayable" : "Visible", "submit_request" : "soumettre une requête", - "buy_alert_content" : "Pour le moment nous ne supportons que l'achat de Bitcoin et Litecoin. Pour acheter du Bitcoin ou du Litecoin, merci de créer ou de sélectionner votre portefeuille Bitcoin ou Litecoin.", - "sell_alert_content": "Pour le moment nous ne supportons que la vente de Bitcoin. Pour vendre du Bitcoin, merci de créer ou de sélectionner votre portefeuille Bitcoint.", + "buy_alert_content" : "Pour le moment nous ne supportons que l'achat de Bitcoin et Litecoin. Pour acheter du Bitcoin ou du Litecoin, merci de créer ou de sélectionner votre portefeuille (wallet) Bitcoin ou Litecoin.", + "sell_alert_content": "Pour le moment nous ne supportons que la vente de Bitcoin. Pour vendre du Bitcoin, merci de créer ou de sélectionner votre portefeuille (wallet) Bitcoin.", - "outdated_electrum_wallet_description" : "Les nouveaux portefeuilles Bitcoin créés dans Cake ont dorénavant une graine de 24 mots. Il est impératif que vous créiez un nouveau portefeuille Bitcoin, que vous y transfériez tous vos fonds puis que vous cessiez d'utiliser le portefeuille avec une graine de 12 mots. Merci de faire cela immédiatement pour assurer la sécurité de vos avoirs.", + "outdated_electrum_wallet_description" : "Les nouveaux portefeuilles (wallets) Bitcoin créés dans Cake ont dorénavant une phrase secrète (seed) de 24 mots. Il est impératif que vous créiez un nouveau portefeuille Bitcoin, que vous y transfériez tous vos fonds puis que vous cessiez d'utiliser le portefeuille avec une phrase secrète de 12 mots. Merci de faire cela immédiatement pour assurer la sécurité de vos avoirs.", "understand" : "J'ai compris", "apk_update" : "Mise à jour d'APK", @@ -494,7 +494,7 @@ "buy_with" : "Acheter avec", "moonpay_alert_text" : "Le montant doit être au moins égal à ${minAmount} ${fiatCurrency}", - "outdated_electrum_wallet_receive_warning": "Si ce portefeuille a une graine de 12 mots et a été créé dans Cake, NE PAS y déposer de Bitcoin. Tous les BTC transférés vers ce portefeuille seront perdus. Créez un nouveau portefeuille avec graine de 24 mots (appuyez sur le menu en haut à droite, sélectionnez Portefeuilles puis Créer un Nouveau Portefeuille et enfin Bitcoin) et transférez y IMMÉDIATEMENT vos BTC. Les nouveaux portefeuilles BTC Cake (avec graine de 24 mots) sont sécurisés", + "outdated_electrum_wallet_receive_warning": "Si ce portefeuille (wallet) a une phrase secrète (seed) de 12 mots et a été créé dans Cake, NE PAS y déposer de Bitcoin. Tous les BTC transférés vers ce portefeuille seront perdus. Créez un nouveau portefeuille avec phrase secrète de 24 mots (appuyez sur le menu en haut à droite, sélectionnez Portefeuilles puis Créer un Nouveau Portefeuille et enfin Bitcoin) et transférez y IMMÉDIATEMENT vos BTC. Les nouveaux portefeuilles BTC Cake (avec phrase secrète de 24 mots) sont sécurisés", "do_not_show_me": "Ne plus me montrer ceci à l'avenir", "unspent_coins_title" : "Pièces (coins) non dépensées", @@ -520,7 +520,7 @@ "yat_error" : "Erreur Yat", "yat_error_content" : "Aucune adresse associée à ce Yat. Essayez un autre Yat", "choose_address" : "\n\nMerci de choisir l'adresse :", - "yat_popup_title" : "L'adresse de votre portefeuille peut être emojifiée.", + "yat_popup_title" : "L'adresse de votre portefeuille (wallet) peut être emojifiée.", "yat_popup_content" : "Vous pouvez à présent envoyer et recevoir des cryptos dans Cake Wallet à l'aide de votre Yat - un nom d'utilisateur court à base d'emoji. Gérér les Yats à tout moment depuis l'écran de paramétrage", "second_intro_title" : "Une adresse emoji pour les gouverner tous", "second_intro_content" : "Votre Yat est une seule et unique adresse emoji qui remplace toutes vos longues adresses hexadécimales pour toutes vos cryptomonnaies.", From d137e570dfbd66397735184ab621c3ebf55d93cf Mon Sep 17 00:00:00 2001 From: mkyq <53115730+mkyq@users.noreply.github.com> Date: Wed, 7 Sep 2022 14:30:50 +0100 Subject: [PATCH 07/84] Update cake wallet version to 4.4.6 and monero.com to 1.1.0 (#503) --- scripts/android/app_env.sh | 8 ++++---- scripts/ios/app_env.sh | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 1ba99ef0e..3a3d9ccf4 100755 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -14,14 +14,14 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.0.9" -MONERO_COM_BUILD_NUMBER=16 +MONERO_COM_VERSION="1.1.0" +MONERO_COM_BUILD_NUMBER=17 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.4.5" -CAKEWALLET_BUILD_NUMBER=113 +CAKEWALLET_VERSION="4.4.6" +CAKEWALLET_BUILD_NUMBER=116 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 4510b7213..40de6482a 100755 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.0.9" -MONERO_COM_BUILD_NUMBER=19 +MONERO_COM_VERSION="1.1.0" +MONERO_COM_BUILD_NUMBER=20 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.4.5" -CAKEWALLET_BUILD_NUMBER=113 +CAKEWALLET_VERSION="4.4.6" +CAKEWALLET_BUILD_NUMBER=116 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" From bc8900e879b41612a8f8a8a6771300e07223a514 Mon Sep 17 00:00:00 2001 From: Justin Ehrenhofer Date: Wed, 14 Sep 2022 14:46:14 -0500 Subject: [PATCH 08/84] fix zaddr validation (#506) Sideshift only supports `zs` Sapling addresses now. They do not support `zc` Sprout addresses or `u` unified addresses --- lib/core/address_validator.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index b9d6cf0d6..9ef8d3340 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -71,7 +71,7 @@ class AddressValidator extends TextValidator { case CryptoCurrency.hbar: return '[0-9a-zA-Z.]'; case CryptoCurrency.zaddr: - return '^zs[0-9a-zA-Z]{75}\&|^zc[0-9a-zA-Z]{93}\$'; + return '^zs[0-9a-zA-Z]{75}'; case CryptoCurrency.zec: return '^t1[0-9a-zA-Z]{33}\$|^t3[0-9a-zA-Z]{33}\$'; default: From e9807944c060b90604dd4559e09d273fed5b5531 Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 14 Sep 2022 22:55:39 +0300 Subject: [PATCH 09/84] add tags and update titles for currencies (#505) * add tags and update titles for currencies AVAXC -> AVAX (C-CHAIN) BTTBSC -> BTT (BSC) USDTTRC20 -> USDT (TRX) USDCSOL -> USDC (SOL) * Update crypto_currency.dart Add ETH tag to usdc Co-authored-by: mkyq <53115730+mkyq@users.noreply.github.com> --- cw_core/lib/crypto_currency.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 8baca39a1..271066f58 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -80,18 +80,18 @@ class CryptoCurrency extends EnumerableItem with Serializable { static const xnzd = CryptoCurrency(title: 'XNZD', tag: 'XHV', raw: 28); static const xusd = CryptoCurrency(title: 'XUSD', tag: 'XHV', raw: 29); - static const ape = CryptoCurrency(title: 'APE', iconPath: 'assets/images/ape_icon.png', raw: 30); - static const avaxc = CryptoCurrency(title: 'AVAXC', iconPath: 'assets/images/avaxc_icon.png', raw: 31); + static const ape = CryptoCurrency(title: 'APE', iconPath: 'assets/images/ape_icon.png', tag: 'ETH', raw: 30); + static const avaxc = CryptoCurrency(title: 'AVAX', iconPath: 'assets/images/avaxc_icon.png', tag: 'C-CHAIN', raw: 31); static const btt = CryptoCurrency(title: 'BTT', iconPath: 'assets/images/btt_icon.png', raw: 32); - static const bttbsc = CryptoCurrency(title: 'BTTBSC', iconPath: 'assets/images/bttbsc_icon.png', raw: 33); + static const bttbsc = CryptoCurrency(title: 'BTT', iconPath: 'assets/images/bttbsc_icon.png', tag: 'BSC', raw: 33); static const doge = CryptoCurrency(title: 'DOGE', iconPath: 'assets/images/doge_icon.png', raw: 34); static const firo = CryptoCurrency(title: 'FIRO', iconPath: 'assets/images/firo_icon.png', raw: 35); - static const usdttrc20 = CryptoCurrency(title: 'USDTTRC20', iconPath: 'assets/images/usdttrc20_icon.png', raw: 36); + static const usdttrc20 = CryptoCurrency(title: 'USDT', iconPath: 'assets/images/usdttrc20_icon.png', tag: 'TRX', raw: 36); static const hbar = CryptoCurrency(title: 'HBAR', iconPath: 'assets/images/hbar_icon.png', raw: 37); static const sc = CryptoCurrency(title: 'SC', iconPath: 'assets/images/sc_icon.png', raw: 38); static const sol = CryptoCurrency(title: 'SOL', iconPath: 'assets/images/sol_icon.png', raw: 39); - static const usdc = CryptoCurrency(title: 'USDC', iconPath: 'assets/images/usdc_icon.png', raw: 40); - static const usdcsol = CryptoCurrency(title: 'USDCSOL', iconPath: 'assets/images/usdcsol_icon.png', raw: 41); + static const usdc = CryptoCurrency(title: 'USDC', iconPath: 'assets/images/usdc_icon.png', tag: 'ETH', raw: 40); + static const usdcsol = CryptoCurrency(title: 'USDC', iconPath: 'assets/images/usdcsol_icon.png', tag: 'SOL', raw: 41); static const zaddr = CryptoCurrency(title: 'ZZEC', tag: 'ZEC', name: 'Shielded Zcash', iconPath: 'assets/images/zaddr_icon.png', raw: 42); static const zec = CryptoCurrency(title: 'TZEC', tag: 'ZEC', name: 'Transparent Zcash', iconPath: 'assets/images/zec_icon.png', raw: 43); static const zen = CryptoCurrency(title: 'ZEN', iconPath: 'assets/images/zen_icon.png', raw: 44); @@ -300,4 +300,4 @@ class CryptoCurrency extends EnumerableItem with Serializable { @override String toString() => title; -} \ No newline at end of file +} From 3f931ed8f21aa83a4d43d929f42700738d6e1acc Mon Sep 17 00:00:00 2001 From: M Date: Wed, 14 Sep 2022 16:15:11 -0400 Subject: [PATCH 10/84] Update ios build number --- scripts/ios/app_env.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 40de6482a..eda8c87a7 100755 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -19,7 +19,7 @@ MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.4.6" -CAKEWALLET_BUILD_NUMBER=116 +CAKEWALLET_BUILD_NUMBER=117 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" From cb8cf8e77c8e238194380d0b0d97de6f4a8f3e71 Mon Sep 17 00:00:00 2001 From: M Date: Wed, 14 Sep 2022 18:22:31 -0400 Subject: [PATCH 11/84] Temporary disable more options button on ionia gift card details screen. --- .../cards/ionia_gift_card_detail_page.dart | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart index 9132ad52a..a6326fc80 100644 --- a/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_gift_card_detail_page.dart @@ -133,19 +133,19 @@ class IoniaGiftCardDetailPage extends BasePage { if (!viewModel.giftCard.isEmpty) { return Column( children: [ - PrimaryButton( - onPressed: () async { - final amount = await Navigator.of(context) - .pushNamed(Routes.ioniaMoreOptionsPage, arguments: [viewModel.giftCard]) as String; - if (amount != null) { - viewModel.updateRemaining(double.parse(amount)); - } - }, - text: S.of(context).more_options, - color: Theme.of(context).accentTextTheme.caption.color, - textColor: Theme.of(context).primaryTextTheme.title.color, - ), - SizedBox(height: 12), + //PrimaryButton( + // onPressed: () async { + // final amount = await Navigator.of(context) + // .pushNamed(Routes.ioniaMoreOptionsPage, arguments: [viewModel.giftCard]) as String; + // if (amount != null) { + // viewModel.updateRemaining(double.parse(amount)); + // } + // }, + // text: S.of(context).more_options, + // color: Theme.of(context).accentTextTheme.caption.color, + // textColor: Theme.of(context).primaryTextTheme.title.color, + //), + //SizedBox(height: 12), LoadingPrimaryButton( isLoading: viewModel.redeemState is IsExecutingState, onPressed: () => viewModel.redeem().then( From b978132fcf8703f78986038e0e6f731a62ab64ba Mon Sep 17 00:00:00 2001 From: M Date: Wed, 14 Sep 2022 19:04:46 -0400 Subject: [PATCH 12/84] Update ios build number for monero.com --- scripts/ios/app_env.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index eda8c87a7..5d99e66c2 100755 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -14,7 +14,7 @@ APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" MONERO_COM_VERSION="1.1.0" -MONERO_COM_BUILD_NUMBER=20 +MONERO_COM_BUILD_NUMBER=21 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" From 04e059a3de2413f24e70c5898e62de55add0ef2c Mon Sep 17 00:00:00 2001 From: M Date: Thu, 15 Sep 2022 08:23:42 -0400 Subject: [PATCH 13/84] Update build number for android --- scripts/android/app_env.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 3a3d9ccf4..bcb8ef231 100755 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -15,13 +15,13 @@ APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" MONERO_COM_VERSION="1.1.0" -MONERO_COM_BUILD_NUMBER=17 +MONERO_COM_BUILD_NUMBER=19 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.4.6" -CAKEWALLET_BUILD_NUMBER=116 +CAKEWALLET_BUILD_NUMBER=118 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" From 1a9becbebcf46a7f95e13afddd0454a89dffc9b7 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Fri, 16 Sep 2022 17:28:43 +0300 Subject: [PATCH 14/84] fix color for login back button (#510) --- lib/src/screens/ionia/auth/ionia_login_page.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/src/screens/ionia/auth/ionia_login_page.dart b/lib/src/screens/ionia/auth/ionia_login_page.dart index bcbc0fee3..44df933b5 100644 --- a/lib/src/screens/ionia/auth/ionia_login_page.dart +++ b/lib/src/screens/ionia/auth/ionia_login_page.dart @@ -26,9 +26,6 @@ class IoniaLoginPage extends BasePage { final IoniaAuthViewModel _authViewModel; - @override - Color get titleColor => Colors.black; - final TextEditingController _emailController; @override From 4f0b7bf51ac8b527272b004c46726d58a9fb0869 Mon Sep 17 00:00:00 2001 From: Mathias Herberts Date: Fri, 16 Sep 2022 19:22:51 +0200 Subject: [PATCH 15/84] Minor fixes for French locale post v4.4.6 (#508) * Fixed typo and added missing settings_display_balance entry. * Fixed typographical errors and typo --- res/values/strings_fr.arb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 7d5a3c74d..b347086ae 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -56,7 +56,7 @@ "reconnect" : "Reconnecter", "wallets" : "Portefeuilles (Wallets)", "show_seed" : "Visualiser la phrase secrète (seed)", - "show_keys" : "Visualiser les /clefs", + "show_keys" : "Visualiser la phrase secrète (seed) et les clefs", "address_book_menu" : "Carnet d'Adresses", "reconnection" : "Reconnexion", "reconnect_alert_text" : "Êtes vous certain de vouloir vous reconnecter ?", @@ -226,7 +226,7 @@ "settings_nodes" : "Nœuds", "settings_current_node" : "Nœud actuel", "settings_wallets" : "Portefeuilles (Wallets)", - "settings_display_balance_as" : "Affichage du solde", + "settings_display_balance" : "Affichage du solde", "settings_currency" : "Devise", "settings_fee_priority" : "Priorité des frais", "settings_save_recipient_address" : "Sauvegarder l'adresse du bénéficiaire", @@ -541,7 +541,7 @@ "already_have_account": "Vous avez déjà un compte ?", "create_account": "Créer un compte", "privacy_policy": "Politique de confidentialité", - "welcome_to_cakepay": "Bienvenue sur Cake Pay!", + "welcome_to_cakepay": "Bienvenue sur Cake Pay !", "sign_up": "S'inscrire", "forgot_password": "Mot de passe oublié", "reset_password": "Réinitialiser le mot de passe", @@ -555,7 +555,7 @@ "dont_get_code": "Vous ne recevez pas le code ?", "resend_code": "Veuillez le renvoyer", "debit_card": "Carte de débit", - "cakepay_prepaid_card": "Carte de débit prépayée CakePay", + "cakepay_prepaid_card": "Carte de débit prépayée Cake Pay", "no_id_needed": "Aucune pièce d'identité nécessaire !", "frequently_asked_questions": "Foire aux questions", "debit_card_terms": "Le stockage et l'utilisation de votre numéro de carte de paiement (et des informations d'identification correspondant à votre numéro de carte de paiement) dans ce portefeuille numérique sont soumis aux conditions générales de l'accord du titulaire de carte applicable avec l'émetteur de la carte de paiement, en vigueur à partir de de temps en temps.", @@ -631,7 +631,7 @@ "open_gift_card": "Ouvrir la carte-cadeau", "contact_support": "Contacter l'assistance", "gift_cards_unavailable": "Les cartes-cadeaux ne sont disponibles à l'achat que via Monero, Bitcoin et Litecoin pour le moment", - "introducing_cake_pay": "Présentation de Cake Pay!", + "introducing_cake_pay": "Présentation de Cake Pay !", "cake_pay_learn_more": "Achetez et utilisez instantanément des cartes-cadeaux dans l'application !\nBalayer de gauche à droite pour en savoir plus.", "automatic": "Automatique", "fixed_pair_not_supported": "Cette paire fixe n'est pas prise en charge avec les échanges sélectionnés", From 54bc40c50307a8e13504ea6114e76353ca810251 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Fri, 16 Sep 2022 23:15:34 +0300 Subject: [PATCH 16/84] hide tips section (#509) --- lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart index 5b0109d0e..9642be130 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_card_detail_page.dart @@ -155,6 +155,7 @@ class IoniaBuyGiftCardDetailPage extends BasePage { ], ), ), + if(ioniaPurchaseViewModel.ioniaMerchant.acceptsTips) Padding( padding: const EdgeInsets.fromLTRB(24.0, 24.0, 0, 24.0), child: Column( From c921ad890a00bd700f96716eb01f39a09d20b266 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Mon, 19 Sep 2022 18:09:23 +0300 Subject: [PATCH 17/84] Truncate to double (#511) * Truncate to double * fix format remaining amount --- lib/src/screens/ionia/cards/ionia_custom_redeem_page.dart | 2 +- lib/view_model/ionia/ionia_custom_redeem_view_model.dart | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/src/screens/ionia/cards/ionia_custom_redeem_page.dart b/lib/src/screens/ionia/cards/ionia_custom_redeem_page.dart index 4bb76848f..88d8f4e97 100644 --- a/lib/src/screens/ionia/cards/ionia_custom_redeem_page.dart +++ b/lib/src/screens/ionia/cards/ionia_custom_redeem_page.dart @@ -117,7 +117,7 @@ class IoniaCustomRedeemPage extends BasePage { Observer(builder: (_)=> !ioniaCustomRedeemViewModel.disableRedeem ? Center( - child: Text('\$${giftCard.remainingAmount} - \$${ioniaCustomRedeemViewModel.amount} = \$${ioniaCustomRedeemViewModel.remaining} ${S.of(context).remaining}', + child: Text('\$${giftCard.remainingAmount} - \$${ioniaCustomRedeemViewModel.amount} = \$${ioniaCustomRedeemViewModel.formattedRemaining} ${S.of(context).remaining}', style: TextStyle( color: Theme.of(context).primaryTextTheme.headline.color, ),), diff --git a/lib/view_model/ionia/ionia_custom_redeem_view_model.dart b/lib/view_model/ionia/ionia_custom_redeem_view_model.dart index 88cc08e83..6bd8e15fa 100644 --- a/lib/view_model/ionia/ionia_custom_redeem_view_model.dart +++ b/lib/view_model/ionia/ionia_custom_redeem_view_model.dart @@ -16,6 +16,9 @@ abstract class IoniaCustomRedeemViewModelBase with Store { @computed double get remaining => amount <= giftCard.remainingAmount ? giftCard.remainingAmount - amount : 0; + @computed + String get formattedRemaining => remaining.toStringAsFixed(2); + @computed bool get disableRedeem => amount > giftCard.remainingAmount; From 6b8e749abfd1e4d642d9aad82847816306e12950 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Mon, 26 Sep 2022 23:12:32 +0200 Subject: [PATCH 18/84] Fix SimpleSwap create exchange (#522) * Check for status code 201 instead of 200 to follow SimpleSwap API documentation * allow 200 and 201 success status code --- lib/exchange/simpleswap/simpleswap_exchange_provider.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/exchange/simpleswap/simpleswap_exchange_provider.dart b/lib/exchange/simpleswap/simpleswap_exchange_provider.dart index cf6fb3d38..1183de696 100644 --- a/lib/exchange/simpleswap/simpleswap_exchange_provider.dart +++ b/lib/exchange/simpleswap/simpleswap_exchange_provider.dart @@ -91,7 +91,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { final response = await post(uri, headers: headers, body: json.encode(body)); - if (response.statusCode != 200) { + if (response.statusCode != 200 && response.statusCode != 201) { if (response.statusCode == 400) { final responseJSON = json.decode(response.body) as Map; final error = responseJSON['message'] as String; From 40e4f7de1fc254fbfbad4fbfa2f13e50512524b2 Mon Sep 17 00:00:00 2001 From: M Date: Mon, 26 Sep 2022 17:15:51 -0400 Subject: [PATCH 19/84] Changed app version to 4.4.7 (1.1.1 for monero.com) --- scripts/android/app_env.sh | 8 ++++---- scripts/ios/app_env.sh | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index bcb8ef231..311913329 100755 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -14,14 +14,14 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.1.0" -MONERO_COM_BUILD_NUMBER=19 +MONERO_COM_VERSION="1.1.1" +MONERO_COM_BUILD_NUMBER=20 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.4.6" -CAKEWALLET_BUILD_NUMBER=118 +CAKEWALLET_VERSION="4.4.7" +CAKEWALLET_BUILD_NUMBER=119 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 5d99e66c2..236482b05 100755 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.1.0" -MONERO_COM_BUILD_NUMBER=21 +MONERO_COM_VERSION="1.1.1" +MONERO_COM_BUILD_NUMBER=22 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.4.6" -CAKEWALLET_BUILD_NUMBER=117 +CAKEWALLET_VERSION="4.4.7" +CAKEWALLET_BUILD_NUMBER=118 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" From 1beb18b04575d2e0735ec52f2592f0bb330800db Mon Sep 17 00:00:00 2001 From: M Date: Wed, 12 Oct 2022 13:09:57 -0400 Subject: [PATCH 20/84] Flutter upgrade --- analysis_options.yaml | 114 +++++---- cw_bitcoin/lib/address_from_output.dart | 6 +- cw_bitcoin/lib/bitcoin_address_record.dart | 6 +- cw_bitcoin/lib/bitcoin_amount_format.dart | 4 +- cw_bitcoin/lib/bitcoin_mnemonic.dart | 13 +- .../lib/bitcoin_transaction_credentials.dart | 6 +- .../lib/bitcoin_transaction_priority.dart | 12 +- cw_bitcoin/lib/bitcoin_wallet.dart | 57 +++-- cw_bitcoin/lib/bitcoin_wallet_addresses.dart | 14 +- .../bitcoin_wallet_creation_credentials.dart | 6 +- cw_bitcoin/lib/bitcoin_wallet_keys.dart | 4 +- cw_bitcoin/lib/bitcoin_wallet_service.dart | 18 +- cw_bitcoin/lib/electrum.dart | 111 +++++---- cw_bitcoin/lib/electrum_balance.dart | 8 +- .../lib/electrum_transaction_history.dart | 18 +- cw_bitcoin/lib/electrum_transaction_info.dart | 73 +++--- cw_bitcoin/lib/electrum_wallet.dart | 104 ++++---- cw_bitcoin/lib/electrum_wallet_addresses.dart | 29 ++- cw_bitcoin/lib/electrum_wallet_snapshot.dart | 60 +++-- cw_bitcoin/lib/file.dart | 15 +- cw_bitcoin/lib/litecoin_wallet.dart | 56 +++-- cw_bitcoin/lib/litecoin_wallet_addresses.dart | 14 +- cw_bitcoin/lib/litecoin_wallet_service.dart | 18 +- .../lib/pending_bitcoin_transaction.dart | 7 +- cw_bitcoin/lib/script_hash.dart | 3 +- cw_bitcoin/lib/utils.dart | 42 ++-- cw_bitcoin/pubspec.lock | 216 ++++++++--------- cw_bitcoin/pubspec.yaml | 28 +-- cw_core/lib/account.dart | 4 +- cw_core/lib/account_list.dart | 4 +- cw_core/lib/crypto_amount_format.dart | 2 +- cw_core/lib/crypto_currency.dart | 22 +- cw_core/lib/currency_for_wallet_type.dart | 2 +- cw_core/lib/enumerable_item.dart | 4 +- cw_core/lib/get_height_by_date.dart | 4 +- cw_core/lib/key.dart | 4 +- cw_core/lib/monero_amount_format.dart | 6 +- cw_core/lib/monero_balance.dart | 7 +- cw_core/lib/monero_transaction_priority.dart | 6 +- cw_core/lib/monero_wallet_keys.dart | 8 +- cw_core/lib/node.dart | 37 +-- cw_core/lib/output_info.dart | 26 +- cw_core/lib/pathForWallet.dart | 6 +- cw_core/lib/sec_random_native.dart | 2 +- cw_core/lib/subaddress.dart | 4 +- cw_core/lib/transaction_direction.dart | 18 +- cw_core/lib/transaction_history.dart | 5 +- cw_core/lib/transaction_info.dart | 20 +- cw_core/lib/transaction_priority.dart | 2 +- cw_core/lib/unspent_coins_info.dart | 10 +- cw_core/lib/wallet_addresses.dart | 9 +- .../lib/wallet_addresses_with_account.dart | 4 +- cw_core/lib/wallet_base.dart | 10 +- cw_core/lib/wallet_credentials.dart | 12 +- cw_core/lib/wallet_info.dart | 30 +-- cw_core/lib/wallet_type.dart | 4 +- cw_core/pubspec.lock | 181 +++++++------- cw_core/pubspec.yaml | 22 +- cw_haven/lib/api/account_list.dart | 16 +- cw_haven/lib/api/convert_utf8_to_string.dart | 6 +- cw_haven/lib/api/cw_haven.dart | 2 +- .../connection_to_node_exception.dart | 2 +- .../creation_transaction_exception.dart | 2 +- .../exceptions/setup_wallet_exception.dart | 2 +- .../exceptions/wallet_creation_exception.dart | 2 +- .../exceptions/wallet_opening_exception.dart | 2 +- .../wallet_restore_from_keys_exception.dart | 2 +- .../wallet_restore_from_seed_exception.dart | 2 +- cw_haven/lib/api/monero_output.dart | 2 +- cw_haven/lib/api/signatures.dart | 4 +- cw_haven/lib/api/structs/account_row.dart | 7 +- .../lib/api/structs/haven_balance_row.dart | 7 +- cw_haven/lib/api/structs/haven_rate.dart | 7 +- .../lib/api/structs/pending_transaction.dart | 14 +- cw_haven/lib/api/structs/subaddress_row.dart | 12 +- .../lib/api/structs/transaction_info_row.dart | 30 +-- cw_haven/lib/api/structs/ut8_box.dart | 4 +- cw_haven/lib/api/subaddress_list.dart | 18 +- cw_haven/lib/api/transaction_history.dart | 98 ++++---- cw_haven/lib/api/types.dart | 4 +- cw_haven/lib/api/wallet.dart | 71 +++--- cw_haven/lib/api/wallet_manager.dart | 124 +++++----- cw_haven/lib/haven_account_list.dart | 4 +- cw_haven/lib/haven_balance.dart | 2 +- cw_haven/lib/haven_subaddress_list.dart | 17 +- ...aven_transaction_creation_credentials.dart | 5 +- cw_haven/lib/haven_transaction_info.dart | 15 +- cw_haven/lib/haven_wallet.dart | 56 +++-- cw_haven/lib/haven_wallet_addresses.dart | 25 +- cw_haven/lib/haven_wallet_service.dart | 43 ++-- cw_haven/lib/pending_haven_transaction.dart | 14 +- cw_haven/lib/update_haven_rate.dart | 2 +- cw_haven/pubspec.lock | 181 +++++++------- cw_haven/pubspec.yaml | 20 +- cw_monero/lib/api/account_list.dart | 16 +- cw_monero/lib/api/convert_utf8_to_string.dart | 6 +- .../connection_to_node_exception.dart | 2 +- .../creation_transaction_exception.dart | 2 +- .../exceptions/setup_wallet_exception.dart | 2 +- .../exceptions/wallet_creation_exception.dart | 2 +- .../exceptions/wallet_opening_exception.dart | 2 +- .../wallet_restore_from_keys_exception.dart | 2 +- .../wallet_restore_from_seed_exception.dart | 2 +- cw_monero/lib/api/monero_output.dart | 4 +- cw_monero/lib/api/signatures.dart | 4 +- cw_monero/lib/api/structs/account_row.dart | 7 +- .../lib/api/structs/pending_transaction.dart | 28 +-- cw_monero/lib/api/structs/subaddress_row.dart | 12 +- .../lib/api/structs/transaction_info_row.dart | 26 +- cw_monero/lib/api/structs/ut8_box.dart | 4 +- cw_monero/lib/api/subaddress_list.dart | 20 +- cw_monero/lib/api/transaction_history.dart | 82 +++---- cw_monero/lib/api/types.dart | 4 +- cw_monero/lib/api/wallet.dart | 76 +++--- cw_monero/lib/api/wallet_manager.dart | 130 +++++----- cw_monero/lib/monero_account_list.dart | 5 +- cw_monero/lib/monero_subaddress_list.dart | 19 +- ...nero_transaction_creation_credentials.dart | 2 +- cw_monero/lib/monero_transaction_info.dart | 13 +- cw_monero/lib/monero_wallet.dart | 79 +++--- cw_monero/lib/monero_wallet_addresses.dart | 27 ++- cw_monero/lib/monero_wallet_service.dart | 37 ++- cw_monero/lib/pending_monero_transaction.dart | 14 +- cw_monero/pubspec.lock | 227 +++++++++++------- cw_monero/pubspec.yaml | 22 +- ios/Podfile.lock | 84 +++---- ios/Runner.xcodeproj/project.pbxproj | 8 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- lib/anypay/any_pay_payment.dart | 24 +- .../any_pay_payment_committed_info.dart | 10 +- lib/anypay/any_pay_payment_instruction.dart | 10 +- lib/anypay/any_pay_trasnaction.dart | 6 +- lib/anypay/anypay_api.dart | 24 +- lib/bitcoin/cw_bitcoin.dart | 23 +- lib/buy/buy_amount.dart | 6 +- lib/buy/buy_exception.dart | 2 +- lib/buy/buy_provider.dart | 2 +- lib/buy/buy_provider_description.dart | 6 +- lib/buy/get_buy_provider_icon.dart | 2 +- lib/buy/moonpay/moonpay_buy_provider.dart | 21 +- lib/buy/order.dart | 35 +-- lib/buy/wyre/wyre_buy_provider.dart | 23 +- lib/core/address_label_validator.dart | 2 +- lib/core/address_validator.dart | 4 +- lib/core/amount.dart | 54 ++--- lib/core/amount_converter.dart | 8 +- lib/core/amount_validator.dart | 2 +- lib/core/auth_service.dart | 8 +- lib/core/auth_state.dart | 4 +- lib/core/backup_service.dart | 75 +++--- lib/core/fiat_conversion_service.dart | 2 +- lib/core/key_service.dart | 7 +- lib/core/monero_account_label_validator.dart | 2 +- lib/core/seed_validator.dart | 15 +- lib/core/sync_status_title.dart | 2 + lib/core/validator.dart | 24 +- lib/core/wallet_creation_service.dart | 24 +- lib/core/wallet_creation_state.dart | 2 +- lib/core/wallet_loading_service.dart | 15 +- lib/di.dart | 114 +++++---- lib/entities/action_list_display_mode.dart | 2 +- lib/entities/balance_display_mode.dart | 6 +- lib/entities/biometric_auth.dart | 5 +- lib/entities/calculate_fiat_amount.dart | 2 +- lib/entities/calculate_fiat_amount_raw.dart | 2 +- lib/entities/contact.dart | 11 +- lib/entities/contact_base.dart | 2 + lib/entities/contact_record.dart | 5 +- lib/entities/default_settings_migration.dart | 128 +++++----- lib/entities/encrypt.dart | 24 +- lib/entities/fiat_currency.dart | 4 +- lib/entities/fio_address_provider.dart | 2 +- lib/entities/fs_migration.dart | 79 ++++-- lib/entities/get_encryption_key.dart | 2 +- lib/entities/ios_legacy_helper.dart | 20 +- lib/entities/language_service.dart | 2 +- lib/entities/load_current_wallet.dart | 5 + lib/entities/mnemonic_item.dart | 2 +- lib/entities/node_list.dart | 68 +++--- lib/entities/openalias_record.dart | 11 +- lib/entities/parse_address_from_domain.dart | 2 +- lib/entities/parsed_address.dart | 33 ++- lib/entities/qr_scanner.dart | 20 +- lib/entities/record.dart | 2 +- lib/entities/secret_store_key.dart | 2 +- lib/entities/template.dart | 9 +- lib/entities/transaction_description.dart | 6 +- lib/entities/transaction_history.dart | 3 + lib/entities/unstoppable_domain_address.dart | 4 +- lib/entities/update_haven_rate.dart | 29 ++- lib/entities/wallet_contact.dart | 1 + lib/entities/wallet_description.dart | 2 +- lib/entities/yat_record.dart | 17 +- .../changenow_exchange_provider.dart | 29 +-- lib/exchange/changenow/changenow_request.dart | 14 +- lib/exchange/exchange_pair.dart | 5 +- lib/exchange/exchange_provider.dart | 22 +- .../exchange_provider_description.dart | 10 +- lib/exchange/exchange_template.dart | 12 +- lib/exchange/exchange_trade_state.dart | 4 +- lib/exchange/limits.dart | 4 +- lib/exchange/limits_state.dart | 4 +- .../morphtoken_exchange_provider.dart | 41 ++-- .../morphtoken/morphtoken_request.dart | 10 +- .../sideshift_exchange_provider.dart | 46 ++-- lib/exchange/sideshift/sideshift_request.dart | 14 +- .../simpleswap_exchange_provider.dart | 25 +- .../simpleswap/simpleswap_request.dart | 11 +- lib/exchange/trade.dart | 57 +++-- lib/exchange/trade_not_found_exeption.dart | 6 +- lib/exchange/trade_state.dart | 6 +- .../xmrto/xmrto_exchange_provider.dart | 41 ++-- lib/exchange/xmrto/xmrto_trade_request.dart | 14 +- lib/haven/cw_haven.dart | 95 +++++--- lib/ionia/ionia_anypay.dart | 16 +- lib/ionia/ionia_api.dart | 179 +++++++------- lib/ionia/ionia_category.dart | 6 +- lib/ionia/ionia_create_state.dart | 10 +- lib/ionia/ionia_gift_card.dart | 38 +-- lib/ionia/ionia_gift_card_instruction.dart | 2 +- lib/ionia/ionia_merchant.dart | 164 +++---------- lib/ionia/ionia_order.dart | 10 +- lib/ionia/ionia_service.dart | 65 +++-- lib/ionia/ionia_tip.dart | 7 +- lib/ionia/ionia_token_data.dart | 2 +- lib/ionia/ionia_virtual_card.dart | 24 +- lib/main.dart | 38 +-- lib/monero/cw_monero.dart | 69 +++--- lib/reactions/check_connection.dart | 2 +- lib/reactions/fiat_rate_update.dart | 14 +- .../on_authentication_state_change.dart | 9 +- lib/reactions/on_current_fiat_change.dart | 12 +- lib/reactions/on_current_node_change.dart | 6 +- lib/reactions/on_current_wallet_change.dart | 23 +- .../on_wallet_sync_status_change.dart | 4 +- lib/router.dart | 16 +- lib/src/screens/auth/auth_page.dart | 70 +++--- lib/src/screens/backup/backup_page.dart | 16 +- .../backup/edit_backup_password_page.dart | 4 +- lib/src/screens/base_page.dart | 44 ++-- lib/src/screens/buy/buy_webview_page.dart | 35 +-- lib/src/screens/buy/pre_order_page.dart | 44 ++-- .../screens/buy/widgets/buy_list_item.dart | 22 +- .../screens/contact/contact_list_page.dart | 89 +++---- lib/src/screens/contact/contact_page.dart | 14 +- lib/src/screens/dashboard/dashboard_page.dart | 77 +++--- .../screens/dashboard/wallet_menu_item.dart | 6 +- .../dashboard/widgets/action_button.dart | 19 +- .../dashboard/widgets/address_page.dart | 42 ++-- .../dashboard/widgets/balance_page.dart | 66 ++--- .../dashboard/widgets/date_section_raw.dart | 4 +- .../dashboard/widgets/filter_tile.dart | 2 +- .../dashboard/widgets/filter_widget.dart | 46 ++-- .../screens/dashboard/widgets/header_row.dart | 8 +- .../dashboard/widgets/market_place_page.dart | 4 +- .../dashboard/widgets/menu_widget.dart | 68 +++--- .../screens/dashboard/widgets/order_row.dart | 28 +-- .../dashboard/widgets/sync_indicator.dart | 8 +- .../widgets/sync_indicator_icon.dart | 4 +- .../screens/dashboard/widgets/trade_row.dart | 45 ++-- .../dashboard/widgets/transaction_raw.dart | 34 +-- .../dashboard/widgets/transactions_page.dart | 14 +- .../screens/disclaimer/disclaimer_page.dart | 52 ++-- lib/src/screens/exchange/exchange_page.dart | 145 ++++++----- .../exchange/exchange_template_page.dart | 104 ++++---- .../exchange/widgets/currency_picker.dart | 27 +-- .../widgets/currency_picker_item_widget.dart | 25 +- .../widgets/currency_picker_widget.dart | 10 +- .../exchange/widgets/exchange_card.dart | 142 +++++------ .../screens/exchange/widgets/picker_item.dart | 5 +- .../widgets/present_provider_picker.dart | 11 +- .../exchange_trade/exchange_confirm_page.dart | 30 +-- .../exchange_trade/exchange_trade_item.dart | 6 +- .../exchange_trade/exchange_trade_page.dart | 73 +++--- .../exchange_trade/information_page.dart | 10 +- .../exchange_trade/widgets/timer_widget.dart | 10 +- lib/src/screens/faq/faq_item.dart | 9 +- .../ionia/auth/ionia_create_account_page.dart | 10 +- .../screens/ionia/auth/ionia_login_page.dart | 6 +- .../ionia/auth/ionia_verify_otp_page.dart | 6 +- .../ionia/auth/ionia_welcome_page.dart | 10 +- .../ionia/cards/ionia_account_cards_page.dart | 36 +-- .../ionia/cards/ionia_account_page.dart | 23 +- .../cards/ionia_activate_debit_card_page.dart | 4 +- .../cards/ionia_buy_card_detail_page.dart | 81 ++++--- .../ionia/cards/ionia_buy_gift_card.dart | 22 +- .../ionia/cards/ionia_custom_redeem_page.dart | 18 +- .../ionia/cards/ionia_custom_tip_page.dart | 18 +- .../ionia/cards/ionia_debit_card_page.dart | 58 ++--- .../cards/ionia_gift_card_detail_page.dart | 33 ++- .../ionia/cards/ionia_manage_cards_page.dart | 61 ++--- .../ionia/cards/ionia_more_options_page.dart | 15 +- .../cards/ionia_payment_status_page.dart | 28 +-- lib/src/screens/ionia/widgets/card_item.dart | 26 +- .../screens/ionia/widgets/confirm_modal.dart | 35 +-- .../ionia/widgets/ionia_alert_model.dart | 14 +- .../ionia/widgets/ionia_filter_modal.dart | 10 +- lib/src/screens/ionia/widgets/ionia_tile.dart | 14 +- .../ionia/widgets/rounded_checkbox.dart | 4 +- .../ionia/widgets/text_icon_button.dart | 13 +- .../monero_account_edit_or_create_page.dart | 6 +- .../monero_account_list_page.dart | 13 +- .../monero_accounts/widgets/account_tile.dart | 41 ++-- .../screens/new_wallet/new_wallet_page.dart | 46 ++-- .../new_wallet/new_wallet_type_page.dart | 69 ++---- .../new_wallet/widgets/select_button.dart | 18 +- .../nodes/node_create_or_edit_page.dart | 6 +- lib/src/screens/nodes/nodes_list_page.dart | 73 +++--- .../screens/nodes/widgets/node_list_row.dart | 12 +- .../order_details/order_details_page.dart | 1 + lib/src/screens/pin_code/pin_code_widget.dart | 90 ++++--- .../screens/receive/fullscreen_qr_page.dart | 17 +- lib/src/screens/receive/receive_page.dart | 65 ++--- .../screens/receive/widgets/address_cell.dart | 67 +++--- .../screens/receive/widgets/header_tile.dart | 12 +- lib/src/screens/receive/widgets/qr_image.dart | 6 +- .../screens/receive/widgets/qr_painter.dart | 8 +- .../screens/receive/widgets/qr_widget.dart | 29 +-- lib/src/screens/rescan/rescan_page.dart | 4 +- .../restore/restore_from_backup_page.dart | 4 +- .../restore_wallet_from_keys_page.dart | 6 +- .../restore_wallet_from_seed_details.dart | 12 +- .../restore_wallet_from_seed_page.dart | 25 +- .../restore/restore_wallet_options_page.dart | 6 +- .../wallet_restore_from_keys_form.dart | 13 +- .../wallet_restore_from_seed_form.dart | 24 +- .../screens/restore/wallet_restore_page.dart | 78 +++--- .../restore/widgets/restore_button.dart | 14 +- lib/src/screens/root/root.dart | 17 +- lib/src/screens/seed/pre_seed_page.dart | 12 +- lib/src/screens/seed/wallet_seed_page.dart | 43 ++-- .../seed_language/seed_language_page.dart | 8 +- .../widgets/seed_language_picker.dart | 24 +- lib/src/screens/send/send_page.dart | 76 +++--- lib/src/screens/send/send_template_page.dart | 64 ++--- .../widgets/choose_yat_address_alert.dart | 10 +- .../send/widgets/confirm_sending_alert.dart | 81 +++---- .../widgets/extract_address_from_parsed.dart | 6 +- .../widgets/prefix_currency_icon_widget.dart | 4 +- lib/src/screens/send/widgets/send_card.dart | 128 +++++----- .../screens/settings/items/settings_item.dart | 14 +- lib/src/screens/settings/settings.dart | 4 +- .../widgets/settings_cell_with_arrow.dart | 4 +- .../widgets/settings_choices_cell.dart | 10 +- .../widgets/settings_link_provider_cell.dart | 19 +- .../widgets/settings_picker_cell.dart | 24 +- .../widgets/settings_switcher_cell.dart | 6 +- .../widgets/settings_version_cell.dart | 4 +- .../setup_pin_code/setup_pin_code.dart | 7 +- .../address_edit_or_create_page.dart | 6 +- lib/src/screens/support/support_page.dart | 6 +- .../trade_details/track_trade_list_item.dart | 5 +- .../trade_details_list_card.dart | 24 +- .../trade_details/trade_details_page.dart | 2 + .../trade_details_status_item.dart | 2 +- .../blockexplorer_list_item.dart | 3 +- .../standart_list_item.dart | 2 +- .../textfield_list_item.dart | 5 +- .../transaction_details_list_item.dart | 2 +- .../transaction_details_page.dart | 6 +- .../widgets/textfield_list_row.dart | 23 +- .../unspent_coins_details_page.dart | 6 +- .../unspent_coins_list_page.dart | 4 +- .../widgets/unspent_coins_list_item.dart | 20 +- .../widgets/unspent_coins_switch_row.dart | 11 +- .../screens/wallet_keys/wallet_keys_page.dart | 2 +- .../screens/wallet_list/wallet_list_page.dart | 67 +++--- lib/src/screens/wallet_list/wallet_menu.dart | 2 +- .../screens/wallet_list/wallet_menu_item.dart | 8 +- .../widgets/wallet_menu_alert.dart | 12 +- lib/src/screens/welcome/welcome_page.dart | 82 +++---- .../yat/widgets/first_introduction.dart | 4 +- .../yat/widgets/second_introduction.dart | 4 +- .../yat/widgets/third_introduction.dart | 7 +- lib/src/screens/yat/widgets/yat_bar.dart | 2 +- .../screens/yat/widgets/yat_close_button.dart | 2 +- .../yat/widgets/yat_page_indicator.dart | 2 +- lib/src/screens/yat/yat_popup.dart | 2 +- lib/src/screens/yat_emoji_id.dart | 8 +- lib/src/widgets/address_text_field.dart | 105 ++++---- lib/src/widgets/alert_background.dart | 2 +- lib/src/widgets/alert_close_button.dart | 2 +- lib/src/widgets/alert_with_one_action.dart | 21 +- lib/src/widgets/alert_with_two_actions.dart | 28 +-- lib/src/widgets/base_alert_dialog.dart | 28 ++- lib/src/widgets/base_text_form_field.dart | 56 ++--- lib/src/widgets/blockchain_height_widget.dart | 18 +- lib/src/widgets/cake_scrollbar.dart | 14 +- lib/src/widgets/check_box_picker.dart | 29 +-- lib/src/widgets/checkbox_widget.dart | 12 +- .../widgets/collapsible_standart_list.dart | 49 ++-- lib/src/widgets/discount_badge.dart | 8 +- lib/src/widgets/introducing_card.dart | 19 +- lib/src/widgets/market_place_item.dart | 20 +- lib/src/widgets/nav_bar.dart | 28 +-- lib/src/widgets/picker.dart | 51 ++-- lib/src/widgets/primary_button.dart | 111 +++++---- .../scollable_with_bottom_section.dart | 8 +- lib/src/widgets/seed_language_selector.dart | 4 +- lib/src/widgets/seed_widget.dart | 26 +- lib/src/widgets/standard_checkbox.dart | 19 +- lib/src/widgets/standard_list.dart | 57 ++--- lib/src/widgets/standart_list_card.dart | 11 +- lib/src/widgets/standart_list_row.dart | 14 +- lib/src/widgets/standart_list_status_row.dart | 12 +- lib/src/widgets/standart_switch.dart | 6 +- lib/src/widgets/template_tile.dart | 16 +- lib/src/widgets/trail_button.dart | 14 +- .../validable_annotated_editable_text.dart | 38 +-- lib/store/app_store.dart | 10 +- lib/store/dashboard/orders_store.dart | 16 +- lib/store/dashboard/trade_filter_store.dart | 2 +- lib/store/dashboard/trades_store.dart | 23 +- .../dashboard/transaction_filter_store.dart | 10 +- lib/store/node_list_store.dart | 14 +- lib/store/secret_store.dart | 5 +- lib/store/settings_store.dart | 206 +++++++++------- .../templates/exchange_template_store.dart | 14 +- lib/store/templates/send_template_store.dart | 25 +- lib/store/yat/yat_exception.dart | 2 +- lib/store/yat/yat_store.dart | 31 ++- lib/themes/bright_theme.dart | 92 ++++--- lib/themes/dark_theme.dart | 92 ++++--- lib/themes/light_theme.dart | 92 ++++--- lib/themes/theme_base.dart | 2 +- lib/themes/theme_list.dart | 4 +- lib/typography.dart | 38 +-- lib/utils/date_picker.dart | 16 +- lib/utils/debounce.dart | 2 +- lib/utils/item_cell.dart | 4 +- lib/utils/list_section.dart | 2 +- lib/utils/mobx.dart | 20 +- lib/utils/show_bar.dart | 95 ++++---- lib/utils/show_pop_up.dart | 10 +- lib/view_model/auth_state.dart | 2 +- lib/view_model/auth_view_model.dart | 11 +- lib/view_model/backup_view_model.dart | 11 +- lib/view_model/buy/buy_amount_view_model.dart | 11 +- lib/view_model/buy/buy_item.dart | 4 +- lib/view_model/buy/buy_view_model.dart | 17 +- .../contact_list/contact_list_view_model.dart | 2 +- .../contact_list/contact_view_model.dart | 31 ++- .../dashboard/action_list_display_mode.dart | 2 +- .../dashboard/balance_view_model.dart | 75 ++++-- .../dashboard/dashboard_view_model.dart | 118 +++++---- lib/view_model/dashboard/filter_item.dart | 5 +- .../dashboard/formatted_item_list.dart | 2 +- lib/view_model/dashboard/order_list_item.dart | 4 +- lib/view_model/dashboard/trade_list_item.dart | 6 +- .../dashboard/transaction_list_item.dart | 14 +- lib/view_model/dashboard/wallet_balance.dart | 2 +- .../edit_backup_password_view_model.dart | 9 +- .../exchange/exchange_trade_view_model.dart | 32 ++- .../exchange/exchange_view_model.dart | 93 ++++--- .../ionia/ionia_account_view_model.dart | 7 +- .../ionia/ionia_auth_view_model.dart | 6 +- .../ionia/ionia_buy_card_view_model.dart | 7 +- .../ionia/ionia_custom_redeem_view_model.dart | 5 +- .../ionia/ionia_custom_tip_view_model.dart | 11 +- .../ionia_gift_card_details_view_model.dart | 11 +- .../ionia_gift_cards_list_view_model.dart | 19 +- .../ionia_payment_status_view_model.dart | 27 ++- .../ionia_purchase_merch_view_model.dart | 43 ++-- .../account_list_item.dart | 2 +- ...ero_account_edit_or_create_view_model.dart | 14 +- .../monero_account_list_view_model.dart | 14 +- .../node_create_or_edit_view_model.dart | 10 +- .../node_list/node_list_view_model.dart | 16 +- lib/view_model/order_details_view_model.dart | 24 +- lib/view_model/rescan_view_model.dart | 12 +- .../restore_from_backup_view_model.dart | 15 +- lib/view_model/send/output.dart | 34 +-- .../send/send_template_view_model.dart | 20 +- lib/view_model/send/send_view_model.dart | 77 ++++-- .../settings/choices_list_item.dart | 12 +- lib/view_model/settings/link_list_item.dart | 8 +- lib/view_model/settings/picker_list_item.dart | 24 +- .../settings/regular_list_item.dart | 4 +- .../settings/settings_view_model.dart | 34 ++- .../settings/switcher_list_item.dart | 6 +- .../settings/version_list_item.dart | 2 +- lib/view_model/support_view_model.dart | 34 +-- lib/view_model/trade_details_view_model.dart | 21 +- .../transaction_details_view_model.dart | 58 +++-- .../unspent_coins_details_view_model.dart | 16 +- .../unspent_coins/unspent_coins_item.dart | 12 +- .../unspent_coins_list_view_model.dart | 14 +- .../unspent_coins_switch_item.dart | 8 +- ...let_address_edit_or_create_view_model.dart | 24 +- .../wallet_address_list_item.dart | 12 +- .../wallet_address_list_view_model.dart | 63 +++-- lib/view_model/wallet_creation_vm.dart | 10 +- lib/view_model/wallet_keys_view_model.dart | 28 ++- .../wallet_list/wallet_list_item.dart | 6 +- .../wallet_list/wallet_list_view_model.dart | 10 +- lib/view_model/wallet_new_vm.dart | 12 +- .../wallet_restoration_from_keys_vm.dart | 15 +- .../wallet_restoration_from_seed_vm.dart | 11 +- lib/view_model/wallet_restore_view_model.dart | 25 +- pubspec_base.yaml | 107 +++++---- pubspec_description.yaml | 3 +- tool/configure.dart | 188 +++++++-------- tool/generate_localization.dart | 2 +- tool/localization/localization_constants.dart | 16 +- tool/utils/utils.dart | 2 +- 505 files changed, 6657 insertions(+), 5875 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 56738f471..524f70011 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,51 +1,73 @@ +include: package:lints/recommended.yaml + analyzer: - strong-mode: - implicit-casts: false - implicit-dynamic: false - exclude: [build/**, lib/generated/*.dart, lib/**.g.dart, cw_monero/ios/External/**, cw_shared_external/**, shared_external/**] + exclude: [ + build/**, + lib/**.g.dart, + cw_core/lib/**.g.dart, + cw_haven/lib/**.g.dart, + cw_monero/lib/**.g.dart, + lib/generated/*.dart, + cw_monero/ios/External/**, + cw_shared_external/**, + shared_external/**] + language: + strict-casts: true + strict-raw-types: true linter: rules: - - always_declare_return_types - - annotate_overrides - - avoid_empty_else - - avoid_init_to_null - - avoid_return_types_on_setters - - await_only_futures - - camel_case_types - cancel_subscriptions - - close_sinks - - comment_references - - constant_identifier_names - - control_flow_in_finally - - empty_catches - - empty_constructor_bodies - - empty_statements - - hash_and_equals - - invariant_booleans - - iterable_contains_unrelated_type - - library_names - - library_prefixes - - list_remove_unrelated_type - - literal_only_boolean_expressions - - non_constant_identifier_names - - one_member_abstracts - - only_throw_errors - - overridden_fields - - package_api_docs - - package_names - - package_prefixed_library_names - - parameter_assignments - - prefer_final_fields - - prefer_final_locals - - prefer_is_not_empty - - slash_for_doc_comments - - sort_constructors_first - - sort_unnamed_constructors_first - - test_types_in_equals - - throw_in_finally - - type_init_formals - - unawaited_futures - - unnecessary_getters_setters - - unrelated_type_equality_checks - - valid_regexps \ No newline at end of file + + +# analyzer: +# strong-mode: +# implicit-casts: false +# implicit-dynamic: false +# exclude: [build/**, lib/generated/*.dart, lib/**.g.dart, cw_monero/ios/External/**, cw_shared_external/**, shared_external/**] + +# linter: +# rules: +# - always_declare_return_types +# - annotate_overrides +# - avoid_empty_else +# - avoid_init_to_null +# - avoid_return_types_on_setters +# - await_only_futures +# - camel_case_types +# - cancel_subscriptions +# - close_sinks +# - comment_references +# - constant_identifier_names +# - control_flow_in_finally +# - empty_catches +# - empty_constructor_bodies +# - empty_statements +# - hash_and_equals +# - invariant_booleans +# - iterable_contains_unrelated_type +# - library_names +# - library_prefixes +# - list_remove_unrelated_type +# - literal_only_boolean_expressions +# - non_constant_identifier_names +# - one_member_abstracts +# - only_throw_errors +# - overridden_fields +# - package_api_docs +# - package_names +# - package_prefixed_library_names +# - parameter_assignments +# - prefer_final_fields +# - prefer_final_locals +# - prefer_is_not_empty +# - slash_for_doc_comments +# - sort_constructors_first +# - sort_unnamed_constructors_first +# - test_types_in_equals +# - throw_in_finally +# - type_init_formals +# - unawaited_futures +# - unnecessary_getters_setters +# - unrelated_type_equality_checks +# - valid_regexps \ No newline at end of file diff --git a/cw_bitcoin/lib/address_from_output.dart b/cw_bitcoin/lib/address_from_output.dart index 6aa90e883..d06ffe402 100644 --- a/cw_bitcoin/lib/address_from_output.dart +++ b/cw_bitcoin/lib/address_from_output.dart @@ -8,7 +8,7 @@ String addressFromOutput(Uint8List script, bitcoin.NetworkType networkType) { data: PaymentData(output: script), network: networkType) .data - .address; + .address!; } catch (_) {} try { @@ -16,8 +16,8 @@ String addressFromOutput(Uint8List script, bitcoin.NetworkType networkType) { data: PaymentData(output: script), network: networkType) .data - .address; + .address!; } catch(_) {} - return null; + return ''; } \ No newline at end of file diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart index 5210604ef..392771ab0 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -2,7 +2,7 @@ import 'dart:convert'; class BitcoinAddressRecord { BitcoinAddressRecord(this.address, - {this.index, this.isHidden = false, bool isUsed = false}) + {required this.index, this.isHidden = false, bool isUsed = false}) : _isUsed = isUsed; factory BitcoinAddressRecord.fromJSON(String jsonSource) { @@ -11,8 +11,8 @@ class BitcoinAddressRecord { return BitcoinAddressRecord( decoded['address'] as String, index: decoded['index'] as int, - isHidden: decoded['isHidden'] as bool ?? false, - isUsed: decoded['isUsed'] as bool ?? false); + isHidden: decoded['isHidden'] as bool? ?? false, + isUsed: decoded['isUsed'] as bool? ?? false); } @override diff --git a/cw_bitcoin/lib/bitcoin_amount_format.dart b/cw_bitcoin/lib/bitcoin_amount_format.dart index 0c846596f..c72d21960 100644 --- a/cw_bitcoin/lib/bitcoin_amount_format.dart +++ b/cw_bitcoin/lib/bitcoin_amount_format.dart @@ -7,10 +7,10 @@ final bitcoinAmountFormat = NumberFormat() ..maximumFractionDigits = bitcoinAmountLength ..minimumFractionDigits = 1; -String bitcoinAmountToString({int amount}) => bitcoinAmountFormat.format( +String bitcoinAmountToString({required int amount}) => bitcoinAmountFormat.format( cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider)); -double bitcoinAmountToDouble({int amount}) => +double bitcoinAmountToDouble({required int amount}) => cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider); int stringDoubleToBitcoinAmount(String amount) { diff --git a/cw_bitcoin/lib/bitcoin_mnemonic.dart b/cw_bitcoin/lib/bitcoin_mnemonic.dart index 65f4ae0df..f4ebd7e5d 100644 --- a/cw_bitcoin/lib/bitcoin_mnemonic.dart +++ b/cw_bitcoin/lib/bitcoin_mnemonic.dart @@ -106,15 +106,18 @@ Future generateMnemonic( return result; } -Uint8List mnemonicToSeedBytes(String mnemonic, {String prefix = segwit}) { +Future mnemonicToSeedBytes(String mnemonic, {String prefix = segwit}) async { final pbkdf2 = cryptography.Pbkdf2( - macAlgorithm: cryptography.Hmac(cryptography.sha512), + macAlgorithm: cryptography.Hmac.sha512(), iterations: 2048, bits: 512); final text = normalizeText(mnemonic); - - return pbkdf2.deriveBitsSync(text.codeUnits, - nonce: cryptography.Nonce('electrum'.codeUnits)); + // pbkdf2.deriveKey(secretKey: secretKey, nonce: nonce) + final key = await pbkdf2.deriveKey( + secretKey: cryptography.SecretKey(text.codeUnits), + nonce: 'electrum'.codeUnits); + final bytes = await key.extractBytes(); + return Uint8List.fromList(bytes); } bool matchesAnyPrefix(String mnemonic) => diff --git a/cw_bitcoin/lib/bitcoin_transaction_credentials.dart b/cw_bitcoin/lib/bitcoin_transaction_credentials.dart index 7df93400a..bd8f1763c 100644 --- a/cw_bitcoin/lib/bitcoin_transaction_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_transaction_credentials.dart @@ -2,9 +2,9 @@ import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_core/output_info.dart'; class BitcoinTransactionCredentials { - BitcoinTransactionCredentials(this.outputs, {this.priority, this.feeRate}); + BitcoinTransactionCredentials(this.outputs, {required this.priority, this.feeRate}); final List outputs; - final BitcoinTransactionPriority priority; - final int feeRate; + final BitcoinTransactionPriority? priority; + final int? feeRate; } diff --git a/cw_bitcoin/lib/bitcoin_transaction_priority.dart b/cw_bitcoin/lib/bitcoin_transaction_priority.dart index f006e25e4..d82ea429e 100644 --- a/cw_bitcoin/lib/bitcoin_transaction_priority.dart +++ b/cw_bitcoin/lib/bitcoin_transaction_priority.dart @@ -2,7 +2,7 @@ import 'package:cw_core/transaction_priority.dart'; //import 'package:cake_wallet/generated/i18n.dart'; class BitcoinTransactionPriority extends TransactionPriority { - const BitcoinTransactionPriority({String title, int raw}) + const BitcoinTransactionPriority({required String title, required int raw}) : super(title: title, raw: raw); static const List all = [fast, medium, slow]; @@ -13,7 +13,7 @@ class BitcoinTransactionPriority extends TransactionPriority { static const BitcoinTransactionPriority fast = BitcoinTransactionPriority(title: 'Fast', raw: 2); - static BitcoinTransactionPriority deserialize({int raw}) { + static BitcoinTransactionPriority deserialize({required int raw}) { switch (raw) { case 0: return slow; @@ -22,7 +22,7 @@ class BitcoinTransactionPriority extends TransactionPriority { case 2: return fast; default: - return null; + throw Exception('Unexpected token: $raw for BitcoinTransactionPriority deserialize'); } } @@ -53,7 +53,7 @@ class BitcoinTransactionPriority extends TransactionPriority { } class LitecoinTransactionPriority extends BitcoinTransactionPriority { - const LitecoinTransactionPriority({String title, int raw}) + const LitecoinTransactionPriority({required String title, required int raw}) : super(title: title, raw: raw); static const List all = [fast, medium, slow]; @@ -64,7 +64,7 @@ class LitecoinTransactionPriority extends BitcoinTransactionPriority { static const LitecoinTransactionPriority fast = LitecoinTransactionPriority(title: 'Fast', raw: 2); - static LitecoinTransactionPriority deserialize({int raw}) { + static LitecoinTransactionPriority deserialize({required int raw}) { switch (raw) { case 0: return slow; @@ -73,7 +73,7 @@ class LitecoinTransactionPriority extends BitcoinTransactionPriority { case 2: return fast; default: - return null; + throw Exception('Unexpected token: $raw for LitecoinTransactionPriority deserialize'); } } diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 243e342b7..c4675df1c 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -1,4 +1,5 @@ import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; @@ -17,12 +18,13 @@ class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet; abstract class BitcoinWalletBase extends ElectrumWallet with Store { BitcoinWalletBase( - {@required String mnemonic, - @required String password, - @required WalletInfo walletInfo, - @required Box unspentCoinsInfo, - List initialAddresses, - ElectrumBalance initialBalance, + {required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required Uint8List seedBytes, + List? initialAddresses, + ElectrumBalance? initialBalance, int initialRegularAddressIndex = 0, int initialChangeAddressIndex = 0}) : super( @@ -32,7 +34,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { unspentCoinsInfo: unspentCoinsInfo, networkType: bitcoin.bitcoin, initialAddresses: initialAddresses, - initialBalance: initialBalance) { + initialBalance: initialBalance, + seedBytes: seedBytes, + currency: CryptoCurrency.btc) { walletAddresses = BitcoinWalletAddresses( walletInfo, electrumClient: electrumClient, @@ -40,20 +44,40 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex, mainHd: hd, - sideHd: bitcoin.HDWallet.fromSeed( - mnemonicToSeedBytes(mnemonic), network: networkType) + sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) .derivePath("m/0'/1"), networkType: networkType); } - static Future open({ - @required String name, - @required WalletInfo walletInfo, - @required Box unspentCoinsInfo, - @required String password, + static Future create({ + required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + List? initialAddresses, + ElectrumBalance? initialBalance, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0 }) async { - final snp = ElectrumWallletSnapshot(name, walletInfo.type, password); - await snp.load(); + return BitcoinWallet( + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: await mnemonicToSeedBytes(mnemonic), + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex); + } + + static Future open({ + required String name, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required String password, + }) async { + final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password); return BitcoinWallet( mnemonic: snp.mnemonic, password: password, @@ -61,6 +85,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { unspentCoinsInfo: unspentCoinsInfo, initialAddresses: snp.addresses, initialBalance: snp.balance, + seedBytes: await mnemonicToSeedBytes(snp.mnemonic), initialRegularAddressIndex: snp.regularAddressIndex, initialChangeAddressIndex: snp.changeAddressIndex); } diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index 33e79c102..de3fdfbca 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -16,13 +16,13 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store { BitcoinWalletAddressesBase( WalletInfo walletInfo, - {@required List initialAddresses, + {required bitcoin.HDWallet mainHd, + required bitcoin.HDWallet sideHd, + required bitcoin.NetworkType networkType, + required ElectrumClient electrumClient, + List? initialAddresses, int initialRegularAddressIndex = 0, - int initialChangeAddressIndex = 0, - ElectrumClient electrumClient, - @required bitcoin.HDWallet mainHd, - @required bitcoin.HDWallet sideHd, - @required bitcoin.NetworkType networkType}) + int initialChangeAddressIndex = 0}) : super( walletInfo, initialAddresses: initialAddresses, @@ -34,6 +34,6 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses networkType: networkType); @override - String getAddress({@required int index, @required bitcoin.HDWallet hd}) => + String getAddress({required int index, required bitcoin.HDWallet hd}) => generateP2WPKHAddress(hd: hd, index: index, networkType: networkType); } \ No newline at end of file diff --git a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart index d3ade5c5e..82173b2d2 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart @@ -2,13 +2,13 @@ import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; class BitcoinNewWalletCredentials extends WalletCredentials { - BitcoinNewWalletCredentials({String name, WalletInfo walletInfo}) + BitcoinNewWalletCredentials({required String name, WalletInfo? walletInfo}) : super(name: name, walletInfo: walletInfo); } class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials { BitcoinRestoreWalletFromSeedCredentials( - {String name, String password, this.mnemonic, WalletInfo walletInfo}) + {required String name, required String password, required this.mnemonic, WalletInfo? walletInfo}) : super(name: name, password: password, walletInfo: walletInfo); final String mnemonic; @@ -16,7 +16,7 @@ class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials { class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials { BitcoinRestoreWalletFromWIFCredentials( - {String name, String password, this.wif, WalletInfo walletInfo}) + {required String name, required String password, required this.wif, WalletInfo? walletInfo}) : super(name: name, password: password, walletInfo: walletInfo); final String wif; diff --git a/cw_bitcoin/lib/bitcoin_wallet_keys.dart b/cw_bitcoin/lib/bitcoin_wallet_keys.dart index 74212c74c..0a4afc10d 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_keys.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_keys.dart @@ -1,7 +1,5 @@ -import 'package:flutter/foundation.dart'; - class BitcoinWalletKeys { - const BitcoinWalletKeys({@required this.wif, @required this.privateKey, @required this.publicKey}); + const BitcoinWalletKeys({required this.wif, required this.privateKey, required this.publicKey}); final String wif; final String privateKey; diff --git a/cw_bitcoin/lib/bitcoin_wallet_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart index 300f4daa9..398d68fc2 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart @@ -10,6 +10,7 @@ import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; +import 'package:collection/collection.dart'; class BitcoinWalletService extends WalletService< BitcoinNewWalletCredentials, @@ -25,10 +26,10 @@ class BitcoinWalletService extends WalletService< @override Future create(BitcoinNewWalletCredentials credentials) async { - final wallet = BitcoinWallet( + final wallet = await BitcoinWalletBase.create( mnemonic: await generateMnemonic(), - password: credentials.password, - walletInfo: credentials.walletInfo, + password: credentials.password!, + walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource); await wallet.save(); await wallet.init(); @@ -41,9 +42,8 @@ class BitcoinWalletService extends WalletService< @override Future openWallet(String name, String password) async { - final walletInfo = walletInfoSource.values.firstWhere( - (info) => info.id == WalletBase.idFor(name, getType()), - orElse: () => null); + final walletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(name, getType()))!; final wallet = await BitcoinWalletBase.open( password: password, name: name, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource); @@ -68,10 +68,10 @@ class BitcoinWalletService extends WalletService< throw BitcoinMnemonicIsIncorrectException(); } - final wallet = BitcoinWallet( - password: credentials.password, + final wallet = await BitcoinWalletBase.create( + password: credentials.password!, mnemonic: credentials.mnemonic, - walletInfo: credentials.walletInfo, + walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource); await wallet.save(); await wallet.init(); diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 8a8894fb8..678c7bc18 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -7,6 +7,7 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/script_hash.dart'; import 'package:flutter/foundation.dart'; import 'package:rxdart/rxdart.dart'; +import 'package:collection/collection.dart'; String jsonrpcparams(List params) { final _params = params?.map((val) => '"${val.toString()}"')?.join(','); @@ -14,14 +15,20 @@ String jsonrpcparams(List params) { } String jsonrpc( - {String method, List params, int id, double version = 2.0}) => + {required String method, + required List params, + required int id, + double version = 2.0}) => '{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n'; class SocketTask { - SocketTask({this.completer, this.isSubscription, this.subject}); + SocketTask({ + required this.isSubscription, + this.completer, + this.subject}); - final Completer completer; - final BehaviorSubject subject; + final Completer? completer; + final BehaviorSubject? subject; final bool isSubscription; } @@ -36,18 +43,18 @@ class ElectrumClient { static const aliveTimerDuration = Duration(seconds: 2); bool get isConnected => _isConnected; - Socket socket; - void Function(bool) onConnectionStatusChange; + Socket? socket; + void Function(bool)? onConnectionStatusChange; int _id; final Map _tasks; bool _isConnected; - Timer _aliveTimer; + Timer? _aliveTimer; String unterminatedString; Future connectToUri(Uri uri) async => await connect(host: uri.host, port: uri.port); - Future connect({@required String host, @required int port}) async { + Future connect({required String host, required int port}) async { try { await socket?.close(); } catch (_) {} @@ -56,10 +63,10 @@ class ElectrumClient { timeout: connectionTimeout, onBadCertificate: (_) => true); _setIsConnected(true); - socket.listen((Uint8List event) { + socket!.listen((Uint8List event) { try { final response = - json.decode(utf8.decode(event.toList())) as Map; + json.decode(utf8.decode(event.toList())) as Map; _handleResponse(response); } on FormatException catch (e) { final msg = e.message.toLowerCase(); @@ -75,12 +82,12 @@ class ElectrumClient { if (isJSONStringCorrect(unterminatedString)) { final response = - json.decode(unterminatedString) as Map; + json.decode(unterminatedString) as Map; _handleResponse(response); unterminatedString = ''; } } on TypeError catch (e) { - if (!e.toString().contains('Map')) { + if (!e.toString().contains('Map') || !e.toString().contains('Map')) { return; } @@ -89,9 +96,10 @@ class ElectrumClient { if (isJSONStringCorrect(unterminatedString)) { final response = - json.decode(unterminatedString) as Map; + json.decode(unterminatedString) as Map; _handleResponse(response); - unterminatedString = null; + // unterminatedString = null; + unterminatedString = ''; } } catch (e) { print(e.toString()); @@ -207,7 +215,7 @@ class ElectrumClient { }); Future> getTransactionRaw( - {@required String hash}) async => + {required String hash}) async => call(method: 'blockchain.transaction.get', params: [hash, true]) .then((dynamic result) { if (result is Map) { @@ -218,7 +226,7 @@ class ElectrumClient { }); Future getTransactionHex( - {@required String hash}) async => + {required String hash}) async => call(method: 'blockchain.transaction.get', params: [hash, false]) .then((dynamic result) { if (result is String) { @@ -229,7 +237,7 @@ class ElectrumClient { }); Future broadcastTransaction( - {@required String transactionRaw}) async => + {required String transactionRaw}) async => call(method: 'blockchain.transaction.broadcast', params: [transactionRaw]) .then((dynamic result) { if (result is String) { @@ -240,16 +248,16 @@ class ElectrumClient { }); Future> getMerkle( - {@required String hash, @required int height}) async => + {required String hash, required int height}) async => await call( method: 'blockchain.transaction.get_merkle', params: [hash, height]) as Map; - Future> getHeader({@required int height}) async => + Future> getHeader({required int height}) async => await call(method: 'blockchain.block.get_header', params: [height]) as Map; - Future estimatefee({@required int p}) => + Future estimatefee({required int p}) => call(method: 'blockchain.estimatefee', params: [p]) .then((dynamic result) { if (result is double) { @@ -266,13 +274,26 @@ class ElectrumClient { Future>> feeHistogram() => call(method: 'mempool.get_fee_histogram').then((dynamic result) { if (result is List) { - return result.map((dynamic e) { - if (e is List) { - return e.map((dynamic ee) => ee is int ? ee : null).toList(); - } + // return result.map((dynamic e) { + // if (e is List) { + // return e.map((dynamic ee) => ee is int ? ee : null).toList(); + // } - return null; - }).toList(); + // return null; + // }).toList(); + final histogram = >[]; + for (final e in result) { + if (e is List) { + final eee = []; + for (final ee in e) { + if (ee is int) { + eee.add(ee); + } + } + histogram.add(eee); + } + } + return histogram; } return []; @@ -299,7 +320,7 @@ class ElectrumClient { } } - BehaviorSubject scripthashUpdate(String scripthash) { + BehaviorSubject? scripthashUpdate(String scripthash) { _id += 1; return subscribe( id: 'blockchain.scripthash.subscribe:$scripthash', @@ -307,14 +328,14 @@ class ElectrumClient { params: [scripthash]); } - BehaviorSubject subscribe( - {@required String id, - @required String method, + BehaviorSubject? subscribe( + {required String id, + required String method, List params = const []}) { try { final subscription = BehaviorSubject(); _regisrySubscription(id, subscription); - socket.write(jsonrpc(method: method, id: _id, params: params)); + socket!.write(jsonrpc(method: method, id: _id, params: params)); return subscription; } catch(e) { @@ -323,18 +344,18 @@ class ElectrumClient { } } - Future call({String method, List params = const []}) async { + Future call({required String method, List params = const []}) async { final completer = Completer(); _id += 1; final id = _id; _registryTask(id, completer); - socket.write(jsonrpc(method: method, id: id, params: params)); + socket!.write(jsonrpc(method: method, id: id, params: params)); return completer.future; } Future callWithTimeout( - {String method, + {required String method, List params = const [], int timeout = 2000}) async { try { @@ -342,7 +363,7 @@ class ElectrumClient { _id += 1; final id = _id; _registryTask(id, completer); - socket.write(jsonrpc(method: method, id: id, params: params)); + socket!.write(jsonrpc(method: method, id: id, params: params)); Timer(Duration(milliseconds: timeout), () { if (!completer.isCompleted) { completer.completeError(RequestFailedTimeoutException(method, id)); @@ -356,35 +377,35 @@ class ElectrumClient { } Future close() async { - _aliveTimer.cancel(); - await socket.close(); + _aliveTimer?.cancel(); + await socket?.close(); onConnectionStatusChange = null; } - void _registryTask(int id, Completer completer) => _tasks[id.toString()] = + void _registryTask(int id, Completer completer) => _tasks[id.toString()] = SocketTask(completer: completer, isSubscription: false); - void _regisrySubscription(String id, BehaviorSubject subject) => + void _regisrySubscription(String id, BehaviorSubject subject) => _tasks[id] = SocketTask(subject: subject, isSubscription: true); - void _finish(String id, Object data) { + void _finish(String id, Object? data) { if (_tasks[id] == null) { return; } if (!(_tasks[id]?.completer?.isCompleted ?? false)) { - _tasks[id]?.completer?.complete(data); + _tasks[id]?.completer!.complete(data); } if (!(_tasks[id]?.isSubscription ?? false)) { - _tasks[id] = null; + _tasks.remove(id); } else { - _tasks[id].subject.add(data); + _tasks[id]?.subject?.add(data); } } void _methodHandler( - {@required String method, @required Map request}) { + {required String method, required Map request}) { switch (method) { case 'blockchain.scripthash.subscribe': final params = request['params'] as List; @@ -406,7 +427,7 @@ class ElectrumClient { _isConnected = isConnected; } - void _handleResponse(Map response) { + void _handleResponse(Map response) { final method = response['method']; final id = response['id'] as String; final result = response['result']; diff --git a/cw_bitcoin/lib/electrum_balance.dart b/cw_bitcoin/lib/electrum_balance.dart index ec71977ad..a26b79ddb 100644 --- a/cw_bitcoin/lib/electrum_balance.dart +++ b/cw_bitcoin/lib/electrum_balance.dart @@ -4,10 +4,10 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_core/balance.dart'; class ElectrumBalance extends Balance { - const ElectrumBalance({@required this.confirmed, @required this.unconfirmed}) + const ElectrumBalance({required this.confirmed, required this.unconfirmed}) : super(confirmed, unconfirmed); - factory ElectrumBalance.fromJSON(String jsonSource) { + static ElectrumBalance? fromJSON(String? jsonSource) { if (jsonSource == null) { return null; } @@ -15,8 +15,8 @@ class ElectrumBalance extends Balance { final decoded = json.decode(jsonSource) as Map; return ElectrumBalance( - confirmed: decoded['confirmed'] as int ?? 0, - unconfirmed: decoded['unconfirmed'] as int ?? 0); + confirmed: decoded['confirmed'] as int? ?? 0, + unconfirmed: decoded['unconfirmed'] as int? ?? 0); } final int confirmed; diff --git a/cw_bitcoin/lib/electrum_transaction_history.dart b/cw_bitcoin/lib/electrum_transaction_history.dart index 94f328900..f8662eb95 100644 --- a/cw_bitcoin/lib/electrum_transaction_history.dart +++ b/cw_bitcoin/lib/electrum_transaction_history.dart @@ -17,7 +17,7 @@ class ElectrumTransactionHistory = ElectrumTransactionHistoryBase abstract class ElectrumTransactionHistoryBase extends TransactionHistoryBase with Store { ElectrumTransactionHistoryBase( - {@required this.walletInfo, @required String password}) + {required this.walletInfo, required String password}) : _password = password, _height = 0 { transactions = ObservableMap(); @@ -56,18 +56,18 @@ abstract class ElectrumTransactionHistoryBase await save(); } - Future> _read() async { + Future> _read() async { final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); final path = '$dirPath/$_transactionsHistoryFileName'; final content = await read(path: path, password: _password); - return json.decode(content) as Map; + return json.decode(content) as Map; } Future _load() async { try { final content = await _read(); - final txs = content['transactions'] as Map ?? {}; + final txs = content['transactions'] as Map ?? {}; txs.entries.forEach((entry) { final val = entry.value; @@ -93,11 +93,11 @@ abstract class ElectrumTransactionHistoryBase transactions[transaction.id] = transaction; } else { final originalTx = transactions[transaction.id]; - originalTx.confirmations = transaction.confirmations; - originalTx.amount = transaction.amount; - originalTx.height = transaction.height; - originalTx.date ??= transaction.date; - originalTx.isPending = transaction.isPending; + originalTx?.confirmations = transaction.confirmations; + originalTx?.amount = transaction.amount; + originalTx?.height = transaction.height; + originalTx?.date ??= transaction.date; + originalTx?.isPending = transaction.isPending; } } } diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index 491ebddfb..6e85a2f88 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -10,23 +10,26 @@ import 'package:cw_core/format_amount.dart'; import 'package:cw_core/wallet_type.dart'; class ElectrumTransactionBundle { - ElectrumTransactionBundle(this.originalTransaction, {this.ins, this.time, this.confirmations}); + ElectrumTransactionBundle(this.originalTransaction, + {required this.ins, + required this.confirmations, + this.time}); final bitcoin.Transaction originalTransaction; final List ins; - final int time; + final int? time; final int confirmations; } class ElectrumTransactionInfo extends TransactionInfo { ElectrumTransactionInfo(this.type, - {@required String id, - @required int height, - @required int amount, - @required int fee, - @required TransactionDirection direction, - @required bool isPending, - @required DateTime date, - @required int confirmations}) { + {required String id, + required int height, + required int amount, + int? fee, + required TransactionDirection direction, + required bool isPending, + required DateTime date, + required int confirmations}) { this.id = id; this.height = height; this.amount = amount; @@ -39,15 +42,15 @@ class ElectrumTransactionInfo extends TransactionInfo { factory ElectrumTransactionInfo.fromElectrumVerbose( Map obj, WalletType type, - {@required List addresses, @required int height}) { + {required List addresses, required int height}) { final addressesSet = addresses.map((addr) => addr.address).toSet(); final id = obj['txid'] as String; - final vins = obj['vin'] as List ?? []; - final vout = (obj['vout'] as List ?? []); + final vins = obj['vin'] as List? ?? []; + final vout = (obj['vout'] as List? ?? []); final date = obj['time'] is int ? DateTime.fromMillisecondsSinceEpoch((obj['time'] as int) * 1000) : DateTime.now(); - final confirmations = obj['confirmations'] as int ?? 0; + final confirmations = obj['confirmations'] as int? ?? 0; var direction = TransactionDirection.incoming; var inputsAmount = 0; var amount = 0; @@ -57,21 +60,21 @@ class ElectrumTransactionInfo extends TransactionInfo { final vout = vin['vout'] as int; final out = vin['tx']['vout'][vout] as Map; final outAddresses = - (out['scriptPubKey']['addresses'] as List)?.toSet(); + (out['scriptPubKey']['addresses'] as List?)?.toSet(); inputsAmount += - stringDoubleToBitcoinAmount((out['value'] as double ?? 0).toString()); + stringDoubleToBitcoinAmount((out['value'] as double? ?? 0).toString()); - if (outAddresses?.intersection(addressesSet)?.isNotEmpty ?? false) { + if (outAddresses?.intersection(addressesSet).isNotEmpty ?? false) { direction = TransactionDirection.outgoing; } } for (dynamic out in vout) { final outAddresses = - out['scriptPubKey']['addresses'] as List ?? []; + out['scriptPubKey']['addresses'] as List? ?? []; final ntrs = outAddresses.toSet().intersection(addressesSet); final value = stringDoubleToBitcoinAmount( - (out['value'] as double ?? 0.0).toString()); + (out['value'] as double? ?? 0.0).toString()); totalOutAmount += value; if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) || @@ -97,10 +100,10 @@ class ElectrumTransactionInfo extends TransactionInfo { ElectrumTransactionBundle bundle, WalletType type, bitcoin.NetworkType networkType, - {@required Set addresses, - int height}) { + {required Set addresses, + required int height}) { final date = bundle.time != null - ? DateTime.fromMillisecondsSinceEpoch(bundle.time * 1000) + ? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000) : DateTime.now(); var direction = TransactionDirection.incoming; var amount = 0; @@ -111,21 +114,21 @@ class ElectrumTransactionInfo extends TransactionInfo { final input = bundle.originalTransaction.ins[i]; final inputTransaction = bundle.ins[i]; final vout = input.index; - final outTransaction = inputTransaction.outs[vout]; - final address = addressFromOutput(outTransaction.script, networkType); - inputAmount += outTransaction.value; + final outTransaction = inputTransaction.outs[vout!]; + final address = addressFromOutput(outTransaction.script!, networkType); + inputAmount += outTransaction.value!; if (addresses.contains(address)) { direction = TransactionDirection.outgoing; } } for (final out in bundle.originalTransaction.outs) { - totalOutAmount += out.value; - final address = addressFromOutput(out.script, networkType); + totalOutAmount += out.value!; + final address = addressFromOutput(out.script!, networkType); final addressExists = addresses.contains(address); if ((direction == TransactionDirection.incoming && addressExists) || (direction == TransactionDirection.outgoing && !addressExists)) { - amount += out.value; + amount += out.value!; } } @@ -142,7 +145,7 @@ class ElectrumTransactionInfo extends TransactionInfo { } factory ElectrumTransactionInfo.fromHexAndHeader(WalletType type, String hex, - {List addresses, int height, int timestamp, int confirmations}) { + {List? addresses, required int height, int? timestamp, required int confirmations}) { final tx = bitcoin.Transaction.fromHex(hex); var exist = false; var amount = 0; @@ -155,7 +158,7 @@ class ElectrumTransactionInfo extends TransactionInfo { exist = addresses.contains(p2pkh.data.address); if (exist) { - amount += out.value; + amount += out.value!; } } catch (_) {} }); @@ -191,15 +194,15 @@ class ElectrumTransactionInfo extends TransactionInfo { final WalletType type; - String _fiatAmount; + String? _fiatAmount; @override String amountFormatted() => '${formatAmount(bitcoinAmountToString(amount: amount))} ${walletTypeToCryptoCurrency(type).title}'; @override - String feeFormatted() => fee != null - ? '${formatAmount(bitcoinAmountToString(amount: fee))} ${walletTypeToCryptoCurrency(type).title}' + String? feeFormatted() => fee != null + ? '${formatAmount(bitcoinAmountToString(amount: fee!))} ${walletTypeToCryptoCurrency(type).title}' : ''; @override @@ -225,7 +228,9 @@ class ElectrumTransactionInfo extends TransactionInfo { m['id'] = id; m['height'] = height; m['amount'] = amount; - m['direction'] = direction.index; + // FIX-ME: Hardcoded value + // m['direction'] = direction.index; + m['direction'] = 0; m['date'] = date.millisecondsSinceEpoch; m['isPending'] = isPending; m['confirmations'] = confirmations; diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index a4db681f3..516b44f56 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -34,6 +34,7 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_bitcoin/electrum.dart'; import 'package:hex/hex.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:collection/collection.dart'; part 'electrum_wallet.g.dart'; @@ -42,31 +43,34 @@ class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet; abstract class ElectrumWalletBase extends WalletBase with Store { ElectrumWalletBase( - {@required String password, - @required WalletInfo walletInfo, - @required Box unspentCoinsInfo, - @required List initialAddresses, - @required this.networkType, - @required this.mnemonic, - ElectrumClient electrumClient, - ElectrumBalance initialBalance}) - : hd = bitcoin.HDWallet.fromSeed(mnemonicToSeedBytes(mnemonic), - network: networkType) + {required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required this.networkType, + required this.mnemonic, + required Uint8List seedBytes, + List? initialAddresses, + ElectrumClient? electrumClient, + ElectrumBalance? initialBalance, + CryptoCurrency? currency}) + : hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) .derivePath("m/0'/0"), syncStatus = NotConnectedSyncStatus(), _password = password, _feeRates = [], _isTransactionUpdating = false, + unspentCoins = [], + _scripthashesUpdateSubject = {}, + balance = ObservableMap.of( + currency != null + ? {currency: initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0)} + : {}), + this.unspentCoinsInfo = unspentCoinsInfo, super(walletInfo) { - balance = ObservableMap.of({ - currency: initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0)}); this.electrumClient = electrumClient ?? ElectrumClient(); this.walletInfo = walletInfo; - this.unspentCoinsInfo = unspentCoinsInfo; transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password); - unspentCoins = []; - _scripthashesUpdateSubject = {}; } static int estimatedTransactionSize(int inputsCount, int outputsCounts) => @@ -75,15 +79,15 @@ abstract class ElectrumWalletBase extends WalletBase unspentCoinsInfo; @override - ElectrumWalletAddresses walletAddresses; + late ElectrumWalletAddresses walletAddresses; @override @observable - ObservableMap balance; + late ObservableMap balance; @override @observable @@ -98,7 +102,7 @@ abstract class ElectrumWalletBase extends WalletBase scriptHash(addr.address, networkType: networkType)) .toList(); - String get xpub => hd.base58; + String get xpub => hd.base58!; @override String get seed => mnemonic; @@ -107,12 +111,12 @@ abstract class ElectrumWalletBase extends WalletBase BitcoinWalletKeys( - wif: hd.wif, privateKey: hd.privKey, publicKey: hd.pubKey); + wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!); String _password; List unspentCoins; List _feeRates; - Map> _scripthashesUpdateSubject; + Map?> _scripthashesUpdateSubject; bool _isTransactionUpdating; Future init() async { @@ -137,7 +141,8 @@ abstract class ElectrumWalletBase extends WalletBase _feeRates = await electrumClient.feeRates()); syncStatus = SyncedSyncStatus(); - } catch (e) { + } catch (e, stacktrace) { + print(stacktrace); print(e.toString()); syncStatus = FailedSyncStatus(); } @@ -145,7 +150,7 @@ abstract class ElectrumWalletBase extends WalletBase connectToNode({@required Node node}) async { + Future connectToNode({required Node node}) async { try { syncStatus = ConnectingSyncStatus(); await electrumClient.connectToUri(node.uri); @@ -187,7 +192,7 @@ abstract class ElectrumWalletBase extends WalletBase item.sendAll - || item.formattedCryptoAmount <= 0)) { + || item.formattedCryptoAmount! <= 0)) { throw BitcoinTransactionWrongBalanceException(currency); } credentialsAmount = outputs.fold(0, (acc, value) => - acc + value.formattedCryptoAmount); + acc + value.formattedCryptoAmount!); if (allAmount - credentialsAmount < minAmount) { throw BitcoinTransactionWrongBalanceException(currency); @@ -210,7 +215,7 @@ abstract class ElectrumWalletBase extends WalletBase allAmount) { @@ -233,7 +238,7 @@ abstract class ElectrumWalletBase extends WalletBase balance[currency].confirmed || totalAmount > allInputsAmount) { + if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) { throw BitcoinTransactionWrongBalanceException(currency); } @@ -298,11 +303,11 @@ abstract class ElectrumWalletBase extends WalletBase + bitcoin.ECPair keyPairFor({required int index}) => generateKeyPair(hd: hd, index: index, network: networkType); @override - Future rescan({int height}) async => throw UnimplementedError(); + Future rescan({required int height}) async => throw UnimplementedError(); @override Future close() async { try { - await electrumClient?.close(); + await electrumClient.close(); } catch (_) {} } @@ -498,10 +503,9 @@ abstract class ElectrumWalletBase extends WalletBase element.hash.contains(coin?.hash)); + final existUnspentCoins = unspentCoins.where((coin) => element.hash.contains(coin.hash)); - if (existUnspentCoins?.isEmpty ?? true) { + if (existUnspentCoins.isEmpty) { keys.add(element.key); } }); @@ -516,7 +520,7 @@ abstract class ElectrumWalletBase extends WalletBase getTransactionExpanded( - {@required String hash, @required int height}) async { + {required String hash, required int height}) async { final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); final transactionHex = verboseTransaction['hex'] as String; final original = bitcoin.Transaction.fromHex(transactionHex); @@ -525,7 +529,7 @@ abstract class ElectrumWalletBase extends WalletBase fetchTransactionInfo( - {@required String hash, @required int height}) async { + {required String hash, required int height}) async { final tx = await getTransactionExpanded(hash: hash, height: height); final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet(); return ElectrumTransactionInfo.fromElectrumBundle( @@ -567,7 +571,7 @@ abstract class ElectrumWalletBase extends WalletBase initialAddresses, + {required this.mainHd, + required this.sideHd, + required this.electrumClient, + required this.networkType, + List? initialAddresses, int initialRegularAddressIndex = 0, - int initialChangeAddressIndex = 0, - this.mainHd, - this.sideHd, - this.electrumClient, - this.networkType}) + int initialChangeAddressIndex = 0}) : addresses = ObservableList.of( (initialAddresses ?? []).toSet()), receiveAddresses = ObservableList.of( @@ -31,10 +30,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { (initialAddresses ?? []) .where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed) .toSet()), - super(walletInfo) { - currentReceiveAddressIndex = initialRegularAddressIndex; - currentChangeAddressIndex = initialChangeAddressIndex; - } + currentReceiveAddressIndex = initialRegularAddressIndex, + currentChangeAddressIndex = initialChangeAddressIndex, + super(walletInfo); static const defaultReceiveAddressesCount = 22; static const defaultChangeAddressesCount = 17; @@ -124,17 +122,18 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } Future generateNewAddress( - {bool isHidden = false, bitcoin.HDWallet hd}) async { + {bitcoin.HDWallet? hd, bool isHidden = false}) async { currentReceiveAddressIndex += 1; + // FIX-ME: Check logic for whichi HD should be used here ??? final address = BitcoinAddressRecord( - getAddress(index: currentReceiveAddressIndex, hd: hd), + getAddress(index: currentReceiveAddressIndex, hd: hd ?? sideHd), index: currentReceiveAddressIndex, isHidden: isHidden); addresses.add(address); return address; } - String getAddress({@required int index, @required bitcoin.HDWallet hd}) => ''; + String getAddress({required int index, required bitcoin.HDWallet hd}) => ''; @override Future updateAddressesInBox() async { @@ -239,7 +238,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } Future> _createNewAddresses(int count, - {int startIndex = 0, bitcoin.HDWallet hd, bool isHidden = false}) async { + {required bitcoin.HDWallet hd, int startIndex = 0, bool isHidden = false}) async { final list = []; for (var i = startIndex; i < count + startIndex; i++) { diff --git a/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart index 3bc1f0607..3755e7d18 100644 --- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart +++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart @@ -6,7 +6,15 @@ import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/wallet_type.dart'; class ElectrumWallletSnapshot { - ElectrumWallletSnapshot(this.name, this.type, this.password); + ElectrumWallletSnapshot({ + required this.name, + required this.type, + required this.password, + required this.mnemonic, + required this.addresses, + required this.balance, + required this.regularAddressIndex, + required this.changeAddressIndex}); final String name; final String password; @@ -18,28 +26,34 @@ class ElectrumWallletSnapshot { int regularAddressIndex; int changeAddressIndex; - Future load() async { - try { - final path = await pathForWallet(name: name, type: type); - final jsonSource = await read(path: path, password: password); - final data = json.decode(jsonSource) as Map; - final addressesTmp = data['addresses'] as List ?? []; - mnemonic = data['mnemonic'] as String; - addresses = addressesTmp - .whereType() - .map((addr) => BitcoinAddressRecord.fromJSON(addr)) - .toList(); - balance = ElectrumBalance.fromJSON(data['balance'] as String) ?? - ElectrumBalance(confirmed: 0, unconfirmed: 0); - regularAddressIndex = 0; - changeAddressIndex = 0; + static Future load(String name, WalletType type, String password) async { + final path = await pathForWallet(name: name, type: type); + final jsonSource = await read(path: path, password: password); + final data = json.decode(jsonSource) as Map; + final addressesTmp = data['addresses'] as List? ?? []; + final mnemonic = data['mnemonic'] as String; + final addresses = addressesTmp + .whereType() + .map((addr) => BitcoinAddressRecord.fromJSON(addr)) + .toList(); + final balance = ElectrumBalance.fromJSON(data['balance'] as String) ?? + ElectrumBalance(confirmed: 0, unconfirmed: 0); + var regularAddressIndex = 0; + var changeAddressIndex = 0; - try { - regularAddressIndex = int.parse(data['account_index'] as String); - changeAddressIndex = int.parse(data['change_address_index'] as String); - } catch (_) {} - } catch (e) { - print(e); - } + try { + regularAddressIndex = int.parse(data['account_index'] as String? ?? '0'); + changeAddressIndex = int.parse(data['change_address_index'] as String? ?? '0'); + } catch (_) {} + + return ElectrumWallletSnapshot( + name: name, + type: type, + password: password, + mnemonic: mnemonic, + addresses: addresses, + balance: balance, + regularAddressIndex: regularAddressIndex, + changeAddressIndex: changeAddressIndex); } } diff --git a/cw_bitcoin/lib/file.dart b/cw_bitcoin/lib/file.dart index e046e74fe..8fd236ec3 100644 --- a/cw_bitcoin/lib/file.dart +++ b/cw_bitcoin/lib/file.dart @@ -1,12 +1,11 @@ import 'dart:io'; import 'package:cw_core/key.dart'; import 'package:encrypt/encrypt.dart' as encrypt; -import 'package:flutter/foundation.dart'; Future write( - {@required String path, - @required String password, - @required String data}) async { + {required String path, + required String password, + required String data}) async { final keys = extractKeys(password); final key = encrypt.Key.fromBase64(keys.first); final iv = encrypt.IV.fromBase64(keys.last); @@ -16,9 +15,9 @@ Future write( } Future writeData( - {@required String path, - @required String password, - @required String data}) async { + {required String path, + required String password, + required String data}) async { final keys = extractKeys(password); final key = encrypt.Key.fromBase64(keys.first); final iv = encrypt.IV.fromBase64(keys.last); @@ -27,7 +26,7 @@ Future writeData( f.writeAsStringSync(encrypted); } -Future read({@required String path, @required String password}) async { +Future read({required String path, required String password}) async { final file = File(path); if (!file.existsSync()) { diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 6f6ba3880..6bf1c5735 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -1,5 +1,6 @@ import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_bitcoin/litecoin_wallet_addresses.dart'; import 'package:cw_core/transaction_priority.dart'; @@ -20,12 +21,13 @@ class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet; abstract class LitecoinWalletBase extends ElectrumWallet with Store { LitecoinWalletBase( - {@required String mnemonic, - @required String password, - @required WalletInfo walletInfo, - @required Box unspentCoinsInfo, - List initialAddresses, - ElectrumBalance initialBalance, + {required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required Uint8List seedBytes, + List? initialAddresses, + ElectrumBalance? initialBalance, int initialRegularAddressIndex = 0, int initialChangeAddressIndex = 0}) : super( @@ -35,7 +37,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { unspentCoinsInfo: unspentCoinsInfo, networkType: litecoinNetwork, initialAddresses: initialAddresses, - initialBalance: initialBalance) { + initialBalance: initialBalance, + seedBytes: seedBytes, + currency: CryptoCurrency.ltc) { walletAddresses = LitecoinWalletAddresses( walletInfo, electrumClient: electrumClient, @@ -44,19 +48,40 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { initialChangeAddressIndex: initialChangeAddressIndex, mainHd: hd, sideHd: bitcoin.HDWallet - .fromSeed(mnemonicToSeedBytes(mnemonic), network: networkType) + .fromSeed(seedBytes, network: networkType) .derivePath("m/0'/1"), networkType: networkType,); } - static Future open({ - @required String name, - @required WalletInfo walletInfo, - @required Box unspentCoinsInfo, - @required String password, + static Future create({ + required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + List? initialAddresses, + ElectrumBalance? initialBalance, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0 }) async { - final snp = ElectrumWallletSnapshot(name, walletInfo.type, password); - await snp.load(); + return LitecoinWallet( + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: await mnemonicToSeedBytes(mnemonic), + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex); + } + + static Future open({ + required String name, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required String password, + }) async { + final snp = await ElectrumWallletSnapshot.load (name, walletInfo.type, password); return LitecoinWallet( mnemonic: snp.mnemonic, password: password, @@ -64,6 +89,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { unspentCoinsInfo: unspentCoinsInfo, initialAddresses: snp.addresses, initialBalance: snp.balance, + seedBytes: await mnemonicToSeedBytes(snp.mnemonic), initialRegularAddressIndex: snp.regularAddressIndex, initialChangeAddressIndex: snp.changeAddressIndex); } diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart index 1a39b8452..a317fa9f2 100644 --- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart @@ -16,13 +16,13 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store { LitecoinWalletAddressesBase( WalletInfo walletInfo, - {@required List initialAddresses, + {required bitcoin.HDWallet mainHd, + required bitcoin.HDWallet sideHd, + required bitcoin.NetworkType networkType, + required ElectrumClient electrumClient, + List? initialAddresses, int initialRegularAddressIndex = 0, - int initialChangeAddressIndex = 0, - ElectrumClient electrumClient, - @required bitcoin.HDWallet mainHd, - @required bitcoin.HDWallet sideHd, - @required bitcoin.NetworkType networkType}) + int initialChangeAddressIndex = 0}) : super( walletInfo, initialAddresses: initialAddresses, @@ -34,6 +34,6 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses networkType: networkType); @override - String getAddress({@required int index, @required bitcoin.HDWallet hd}) => + String getAddress({required int index, required bitcoin.HDWallet hd}) => generateP2WPKHAddress(hd: hd, index: index, networkType: networkType); } \ No newline at end of file diff --git a/cw_bitcoin/lib/litecoin_wallet_service.dart b/cw_bitcoin/lib/litecoin_wallet_service.dart index f0b3f1693..2093647fd 100644 --- a/cw_bitcoin/lib/litecoin_wallet_service.dart +++ b/cw_bitcoin/lib/litecoin_wallet_service.dart @@ -10,6 +10,7 @@ import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_base.dart'; +import 'package:collection/collection.dart'; class LitecoinWalletService extends WalletService< BitcoinNewWalletCredentials, @@ -25,10 +26,10 @@ class LitecoinWalletService extends WalletService< @override Future create(BitcoinNewWalletCredentials credentials) async { - final wallet = LitecoinWallet( + final wallet = await LitecoinWalletBase.create( mnemonic: await generateMnemonic(), - password: credentials.password, - walletInfo: credentials.walletInfo, + password: credentials.password!, + walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource); await wallet.save(); await wallet.init(); @@ -42,9 +43,8 @@ class LitecoinWalletService extends WalletService< @override Future openWallet(String name, String password) async { - final walletInfo = walletInfoSource.values.firstWhere( - (info) => info.id == WalletBase.idFor(name, getType()), - orElse: () => null); + final walletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(name, getType()))!; final wallet = await LitecoinWalletBase.open( password: password, name: name, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource); @@ -69,10 +69,10 @@ class LitecoinWalletService extends WalletService< throw BitcoinMnemonicIsIncorrectException(); } - final wallet = LitecoinWallet( - password: credentials.password, + final wallet = await LitecoinWalletBase.create( + password: credentials.password!, mnemonic: credentials.mnemonic, - walletInfo: credentials.walletInfo, + walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource); await wallet.save(); await wallet.init(); diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart index b9f754c72..e2dc10bfb 100644 --- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart +++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart @@ -1,5 +1,4 @@ import 'package:cw_bitcoin/bitcoin_commit_transaction_exception.dart'; -import 'package:flutter/foundation.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_bitcoin/electrum.dart'; @@ -10,9 +9,9 @@ import 'package:cw_core/wallet_type.dart'; class PendingBitcoinTransaction with PendingTransaction { PendingBitcoinTransaction(this._tx, this.type, - {@required this.electrumClient, - @required this.amount, - @required this.fee}) + {required this.electrumClient, + required this.amount, + required this.fee}) : _listeners = []; final WalletType type; diff --git a/cw_bitcoin/lib/script_hash.dart b/cw_bitcoin/lib/script_hash.dart index b1025f66b..76a1bfcf0 100644 --- a/cw_bitcoin/lib/script_hash.dart +++ b/cw_bitcoin/lib/script_hash.dart @@ -1,8 +1,7 @@ -import 'package:flutter/foundation.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:crypto/crypto.dart'; -String scriptHash(String address, {@required bitcoin.NetworkType networkType}) { +String scriptHash(String address, {required bitcoin.NetworkType networkType}) { final outputScript = bitcoin.Address.addressToOutputScript(address, networkType); final parts = sha256.convert(outputScript).toString().split(''); diff --git a/cw_bitcoin/lib/utils.dart b/cw_bitcoin/lib/utils.dart index 3a638555a..0d5a413b3 100644 --- a/cw_bitcoin/lib/utils.dart +++ b/cw_bitcoin/lib/utils.dart @@ -5,51 +5,51 @@ import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData; import 'package:hex/hex.dart'; bitcoin.PaymentData generatePaymentData( - {@required bitcoin.HDWallet hd, @required int index}) => + {required bitcoin.HDWallet hd, required int index}) => PaymentData( - pubkey: Uint8List.fromList(HEX.decode(hd.derive(index).pubKey))); + pubkey: Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!))); bitcoin.ECPair generateKeyPair( - {@required bitcoin.HDWallet hd, - @required int index, - bitcoin.NetworkType network}) => - bitcoin.ECPair.fromWIF(hd.derive(index).wif, network: network); + {required bitcoin.HDWallet hd, + required int index, + required bitcoin.NetworkType network}) => + bitcoin.ECPair.fromWIF(hd.derive(index).wif!, network: network); String generateP2WPKHAddress( - {@required bitcoin.HDWallet hd, - @required int index, - bitcoin.NetworkType networkType}) => + {required bitcoin.HDWallet hd, + required int index, + required bitcoin.NetworkType networkType}) => bitcoin .P2WPKH( data: PaymentData( pubkey: - Uint8List.fromList(HEX.decode(hd.derive(index).pubKey))), + Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!))), network: networkType) .data - .address; + .address!; String generateP2WPKHAddressByPath( - {@required bitcoin.HDWallet hd, - @required String path, - bitcoin.NetworkType networkType}) => + {required bitcoin.HDWallet hd, + required String path, + required bitcoin.NetworkType networkType}) => bitcoin .P2WPKH( data: PaymentData( pubkey: - Uint8List.fromList(HEX.decode(hd.derivePath(path).pubKey))), + Uint8List.fromList(HEX.decode(hd.derivePath(path).pubKey!))), network: networkType) .data - .address; + .address!; String generateP2PKHAddress( - {@required bitcoin.HDWallet hd, - @required int index, - bitcoin.NetworkType networkType}) => + {required bitcoin.HDWallet hd, + required int index, + required bitcoin.NetworkType networkType}) => bitcoin .P2PKH( data: PaymentData( pubkey: - Uint8List.fromList(HEX.decode(hd.derive(index).pubKey))), + Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!))), network: networkType) .data - .address; + .address!; diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 504c92fab..9207fc209 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -7,64 +7,64 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "14.0.0" + version: "47.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.41.2" + version: "4.7.0" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.6.0" + version: "2.3.1" asn1lib: dependency: transitive description: name: asn1lib url: "https://pub.dartlang.org" source: hosted - version: "0.6.5" + version: "1.1.1" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.9.0" bech32: dependency: transitive description: path: "." - ref: cake - resolved-ref: "02fef082f20af13de00b4e64efb93a2c1e5e1cf2" + ref: "cake-0.2.1" + resolved-ref: cafd1c270641e95017d57d69f55cca9831d4db56 url: "https://github.com/cake-tech/bech32.git" source: git - version: "0.2.0" + version: "0.2.1" bip32: dependency: transitive description: name: bip32 url: "https://pub.dartlang.org" source: hosted - version: "1.0.7" + version: "2.0.0" bip39: dependency: transitive description: name: bip39 url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.0.6" bitcoin_flutter: dependency: "direct main" description: path: "." - ref: cake - resolved-ref: cbabfd87b6ce3cae6051a3e86ddb56e7a934e188 + ref: cake-update-v2 + resolved-ref: "8f86453761c0c26e368392d0ff2c6f12f3b7397b" url: "https://github.com/cake-tech/bitcoin_flutter.git" source: git version: "2.0.2" @@ -81,133 +81,119 @@ packages: name: bs58check url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.0.2" build: dependency: transitive description: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" + version: "2.3.1" build_config: dependency: transitive description: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "0.4.6" + version: "1.1.0" build_daemon: dependency: transitive description: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "2.1.10" + version: "3.1.0" build_resolvers: dependency: "direct dev" description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "1.5.3" + version: "2.0.10" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.11.5" + version: "2.2.1" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "6.1.10" + version: "7.2.4" built_collection: dependency: transitive description: name: built_collection url: "https://pub.dartlang.org" source: hosted - version: "4.3.2" + version: "5.1.1" built_value: dependency: transitive description: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "7.1.0" + version: "8.4.1" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" + version: "1.2.1" checked_yaml: dependency: transitive description: name: checked_yaml url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" - cli_util: - dependency: transitive - description: - name: cli_util - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.5" + version: "2.0.1" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" code_builder: dependency: transitive description: name: code_builder url: "https://pub.dartlang.org" source: hosted - version: "3.7.0" + version: "4.3.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" convert: dependency: transitive description: name: convert url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "3.0.2" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "3.0.2" cryptography: dependency: "direct main" description: name: cryptography url: "https://pub.dartlang.org" source: hosted - version: "1.4.1" + version: "2.0.5" cw_core: dependency: "direct main" description: @@ -221,35 +207,28 @@ packages: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.3.12" - dartx: - dependency: transitive - description: - name: dartx - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.0" + version: "2.2.4" encrypt: dependency: "direct main" description: name: encrypt url: "https://pub.dartlang.org" source: hosted - version: "4.0.3" + version: "5.0.1" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" ffi: dependency: transitive description: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "2.0.1" file: dependency: transitive description: @@ -263,7 +242,7 @@ packages: name: fixnum url: "https://pub.dartlang.org" source: hosted - version: "0.10.11" + version: "1.0.1" flutter: dependency: "direct main" description: flutter @@ -275,12 +254,19 @@ packages: name: flutter_mobx url: "https://pub.dartlang.org" source: hosted - version: "1.1.0+2" + version: "2.0.6+4" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" glob: dependency: transitive description: @@ -294,49 +280,49 @@ packages: name: graphs url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "2.1.0" hex: dependency: transitive description: name: hex url: "https://pub.dartlang.org" source: hosted - version: "0.1.2" + version: "0.2.0" hive: dependency: transitive description: name: hive url: "https://pub.dartlang.org" source: hosted - version: "1.4.4+1" + version: "2.2.3" hive_generator: dependency: "direct dev" description: name: hive_generator url: "https://pub.dartlang.org" source: hosted - version: "0.8.2" + version: "1.1.3" http: dependency: "direct main" description: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.2" + version: "0.13.5" http_multi_server: dependency: transitive description: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "3.2.1" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" + version: "4.0.1" intl: dependency: "direct main" description: @@ -350,7 +336,7 @@ packages: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.5" + version: "1.0.3" js: dependency: transitive description: @@ -364,7 +350,7 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "4.0.1" + version: "4.7.0" logging: dependency: transitive description: @@ -378,14 +364,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10" + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.8.0" mime: dependency: transitive description: @@ -399,63 +392,77 @@ packages: name: mobx url: "https://pub.dartlang.org" source: hosted - version: "1.2.1+4" + version: "2.1.0" mobx_codegen: dependency: "direct dev" description: name: mobx_codegen url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "2.0.7+3" package_config: dependency: transitive description: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "2.1.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.2" path_provider: dependency: "direct main" description: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.6.28" + version: "2.0.11" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.20" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" path_provider_linux: dependency: transitive description: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+2" + version: "2.1.7" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+8" + version: "2.0.6" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.5" path_provider_windows: dependency: transitive description: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.5" + version: "2.1.3" pedantic: dependency: transitive description: @@ -476,14 +483,14 @@ packages: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.1.3" pointycastle: dependency: transitive description: name: pointycastle url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "3.6.2" pool: dependency: transitive description: @@ -511,35 +518,28 @@ packages: name: pubspec_parse url: "https://pub.dartlang.org" source: hosted - version: "0.1.8" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.5" + version: "1.2.1" rxdart: dependency: "direct main" description: name: rxdart url: "https://pub.dartlang.org" source: hosted - version: "0.26.0" + version: "0.27.5" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "0.7.9" + version: "1.4.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.4+1" + version: "1.0.2" sky_engine: dependency: transitive description: flutter @@ -551,14 +551,21 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "0.9.10+3" + version: "1.2.5" + source_helper: + dependency: transitive + description: + name: source_helper + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.3" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -586,35 +593,28 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19" - time: - dependency: transitive - description: - name: time - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.1" + version: "0.4.12" timing: dependency: transitive description: name: timing url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+3" + version: "1.0.0" typed_data: dependency: transitive description: @@ -635,7 +635,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.2" watcher: dependency: transitive description: @@ -649,21 +649,21 @@ packages: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.2.0" win32: dependency: transitive description: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "3.0.0" xdg_directories: dependency: transitive description: name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "0.1.2" + version: "0.2.0+2" yaml: dependency: transitive description: @@ -672,5 +672,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.12.0 <3.0.0" - flutter: ">=1.20.0" + dart: ">=2.17.5 <3.0.0" + flutter: ">=3.0.0" diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index d7c324775..455ceb4a7 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -6,35 +6,35 @@ author: Cake Wallet homepage: https://cakewallet.com environment: - sdk: ">=2.7.0 <3.0.0" - flutter: ">=1.17.0" + sdk: ">=2.17.5 <3.0.0" + flutter: ">=1.20.0" dependencies: flutter: sdk: flutter - path_provider: ^1.4.0 - http: ^0.12.0+2 - mobx: ^1.2.1+2 - flutter_mobx: ^1.1.0+2 + path_provider: ^2.0.11 + http: ^0.13.4 + mobx: ^2.0.7+4 + flutter_mobx: ^2.0.6+1 intl: ^0.17.0 cw_core: path: ../cw_core bitcoin_flutter: git: url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake - rxdart: ^0.26.0 + ref: cake-update-v2 + rxdart: ^0.27.5 unorm_dart: ^0.2.0 - cryptography: ^1.4.0 - encrypt: ^4.0.0 + cryptography: ^2.0.5 + encrypt: ^5.0.1 dev_dependencies: flutter_test: sdk: flutter - build_runner: ^1.10.3 - build_resolvers: ^1.3.10 - mobx_codegen: ^1.1.0+1 - hive_generator: ^0.8.1 + build_runner: ^2.1.11 + build_resolvers: ^2.0.9 + mobx_codegen: ^2.0.7 + hive_generator: ^1.1.3 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/cw_core/lib/account.dart b/cw_core/lib/account.dart index 5bb002945..1633ee189 100644 --- a/cw_core/lib/account.dart +++ b/cw_core/lib/account.dart @@ -1,7 +1,7 @@ class Account { - Account({this.id, this.label}); + Account({required this.id, required this.label}); - Account.fromMap(Map map) + Account.fromMap(Map map) : this.id = map['id'] == null ? 0 : int.parse(map['id'] as String), this.label = (map['label'] ?? '') as String; diff --git a/cw_core/lib/account_list.dart b/cw_core/lib/account_list.dart index 4ac5faa3e..f49f69aa9 100644 --- a/cw_core/lib/account_list.dart +++ b/cw_core/lib/account_list.dart @@ -8,9 +8,9 @@ abstract class AccountList { List getAll(); - Future addAccount({String label}); + Future addAccount({required String label}); - Future setLabelAccount({int accountIndex, String label}); + Future setLabelAccount({required int accountIndex, required String label}); void refresh(); } diff --git a/cw_core/lib/crypto_amount_format.dart b/cw_core/lib/crypto_amount_format.dart index 649ac45f5..a5c06e485 100644 --- a/cw_core/lib/crypto_amount_format.dart +++ b/cw_core/lib/crypto_amount_format.dart @@ -1 +1 @@ -double cryptoAmountToDouble({num amount, num divider}) => amount / divider; \ No newline at end of file +double cryptoAmountToDouble({required num amount, required num divider}) => amount / divider; \ No newline at end of file diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 271066f58..1da753667 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -5,12 +5,17 @@ part 'crypto_currency.g.dart'; @HiveType(typeId: 0) class CryptoCurrency extends EnumerableItem with Serializable { - const CryptoCurrency({final String title, this.tag, this.name, this.iconPath, final int raw}) + const CryptoCurrency({ + String title = '', + int raw = -1, + this.name, + this.iconPath, + this.tag,}) : super(title: title, raw: raw); - final String tag; - final String name; - final String iconPath; + final String? tag; + final String? name; + final String? iconPath; static const all = [ CryptoCurrency.xmr, @@ -97,10 +102,7 @@ class CryptoCurrency extends EnumerableItem with Serializable { static const zen = CryptoCurrency(title: 'ZEN', iconPath: 'assets/images/zen_icon.png', raw: 44); static const xvg = CryptoCurrency(title: 'XVG', name: 'Verge', iconPath: 'assets/images/xvg_icon.png', raw: 45); - - - - static CryptoCurrency deserialize({int raw}) { + static CryptoCurrency deserialize({required int raw}) { switch (raw) { case 0: return CryptoCurrency.xmr; @@ -195,7 +197,7 @@ class CryptoCurrency extends EnumerableItem with Serializable { case 45: return CryptoCurrency.xvg; default: - return null; + throw Exception('Unexpected token: $raw for CryptoCurrency deserialize'); } } @@ -294,7 +296,7 @@ class CryptoCurrency extends EnumerableItem with Serializable { case 'xvg': return CryptoCurrency.xvg; default: - return null; + throw Exception('Unexpected token: $raw for CryptoCurrency fromString'); } } diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart index b6d1f18c0..3904fc049 100644 --- a/cw_core/lib/currency_for_wallet_type.dart +++ b/cw_core/lib/currency_for_wallet_type.dart @@ -12,6 +12,6 @@ CryptoCurrency currencyForWalletType(WalletType type) { case WalletType.haven: return CryptoCurrency.xhv; default: - return null; + throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType'); } } diff --git a/cw_core/lib/enumerable_item.dart b/cw_core/lib/enumerable_item.dart index e9deb3056..e7a863909 100644 --- a/cw_core/lib/enumerable_item.dart +++ b/cw_core/lib/enumerable_item.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; abstract class EnumerableItem { - const EnumerableItem({@required this.title, @required this.raw}); + const EnumerableItem({required this.title, required this.raw}); final T raw; final String title; @@ -11,6 +11,6 @@ abstract class EnumerableItem { } mixin Serializable on EnumerableItem { - static Serializable deserialize({T raw}) => null; + static Serializable deserialize({required T raw}) => throw Exception('Unimplemented'); T serialize() => raw; } diff --git a/cw_core/lib/get_height_by_date.dart b/cw_core/lib/get_height_by_date.dart index 99e834c58..e350a19cb 100644 --- a/cw_core/lib/get_height_by_date.dart +++ b/cw_core/lib/get_height_by_date.dart @@ -85,7 +85,7 @@ final dates = { "2020-11": 2220000 }; -int getMoneroHeigthByDate({DateTime date}) { +int getMoneroHeigthByDate({required DateTime date}) { final raw = '${date.year}' + '-' + '${date.month}'; final lastHeight = dates.values.last; int startHeight; @@ -105,7 +105,7 @@ int getMoneroHeigthByDate({DateTime date}) { final daysHeight = (differenceInDays * heightPerDay).round(); height = endHeight + daysHeight; } else { - startHeight = dates[raw]; + startHeight = dates[raw]!; final index = dates.values.toList().indexOf(startHeight); endHeight = dates.values.toList()[index + 1]; final heightPerDay = ((endHeight - startHeight) / 31).round(); diff --git a/cw_core/lib/key.dart b/cw_core/lib/key.dart index bdabe7852..6973b7a68 100644 --- a/cw_core/lib/key.dart +++ b/cw_core/lib/key.dart @@ -16,14 +16,14 @@ List extractKeys(String key) { return [_key, iv]; } -Future encode({encrypt.Key key, encrypt.IV iv, String data}) async { +Future encode({required encrypt.Key key, required encrypt.IV iv, required String data}) async { final encrypter = encrypt.Encrypter(encrypt.Salsa20(key)); final encrypted = encrypter.encrypt(data, iv: iv); return encrypted.base64; } -Future decode({String password, String data}) async { +Future decode({required String password, required String data}) async { final keys = extractKeys(password); final key = encrypt.Key.fromBase64(keys.first); final iv = encrypt.IV.fromBase64(keys.last); diff --git a/cw_core/lib/monero_amount_format.dart b/cw_core/lib/monero_amount_format.dart index 92bf0da25..912527b4e 100644 --- a/cw_core/lib/monero_amount_format.dart +++ b/cw_core/lib/monero_amount_format.dart @@ -7,12 +7,12 @@ final moneroAmountFormat = NumberFormat() ..maximumFractionDigits = moneroAmountLength ..minimumFractionDigits = 1; -String moneroAmountToString({int amount}) => moneroAmountFormat +String moneroAmountToString({required int amount}) => moneroAmountFormat .format(cryptoAmountToDouble(amount: amount, divider: moneroAmountDivider)) .replaceAll(',', ''); -double moneroAmountToDouble({int amount}) => +double moneroAmountToDouble({required int amount}) => cryptoAmountToDouble(amount: amount, divider: moneroAmountDivider); -int moneroParseAmount({String amount}) => +int moneroParseAmount({required String amount}) => (double.parse(amount) * moneroAmountDivider).toInt(); diff --git a/cw_core/lib/monero_balance.dart b/cw_core/lib/monero_balance.dart index 8f0ae610e..7d569ef2f 100644 --- a/cw_core/lib/monero_balance.dart +++ b/cw_core/lib/monero_balance.dart @@ -1,17 +1,16 @@ import 'package:cw_core/balance.dart'; -import 'package:flutter/foundation.dart'; import 'package:cw_core/monero_amount_format.dart'; class MoneroBalance extends Balance { - MoneroBalance({@required this.fullBalance, @required this.unlockedBalance}) + MoneroBalance({required this.fullBalance, required this.unlockedBalance}) : formattedFullBalance = moneroAmountToString(amount: fullBalance), formattedUnlockedBalance = moneroAmountToString(amount: unlockedBalance), super(unlockedBalance, fullBalance); MoneroBalance.fromString( - {@required this.formattedFullBalance, - @required this.formattedUnlockedBalance}) + {required this.formattedFullBalance, + required this.formattedUnlockedBalance}) : fullBalance = moneroParseAmount(amount: formattedFullBalance), unlockedBalance = moneroParseAmount(amount: formattedUnlockedBalance), super(moneroParseAmount(amount: formattedUnlockedBalance), diff --git a/cw_core/lib/monero_transaction_priority.dart b/cw_core/lib/monero_transaction_priority.dart index fe937cb1f..1aca5dd8c 100644 --- a/cw_core/lib/monero_transaction_priority.dart +++ b/cw_core/lib/monero_transaction_priority.dart @@ -4,7 +4,7 @@ import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/enumerable_item.dart'; class MoneroTransactionPriority extends TransactionPriority { - const MoneroTransactionPriority({String title, int raw}) + const MoneroTransactionPriority({required String title, required int raw}) : super(title: title, raw: raw); static const all = [ @@ -37,7 +37,7 @@ class MoneroTransactionPriority extends TransactionPriority { } } - static MoneroTransactionPriority deserialize({int raw}) { + static MoneroTransactionPriority deserialize({required int raw}) { switch (raw) { case 0: return slow; @@ -50,7 +50,7 @@ class MoneroTransactionPriority extends TransactionPriority { case 4: return fastest; default: - return null; + throw Exception('Unexpected token: $raw for MoneroTransactionPriority deserialize'); } } diff --git a/cw_core/lib/monero_wallet_keys.dart b/cw_core/lib/monero_wallet_keys.dart index f0a96bfd3..1435002a8 100644 --- a/cw_core/lib/monero_wallet_keys.dart +++ b/cw_core/lib/monero_wallet_keys.dart @@ -1,9 +1,9 @@ class MoneroWalletKeys { const MoneroWalletKeys( - {this.privateSpendKey, - this.privateViewKey, - this.publicSpendKey, - this.publicViewKey}); + {required this.privateSpendKey, + required this.privateViewKey, + required this.publicSpendKey, + required this.publicViewKey}); final String publicViewKey; final String privateViewKey; diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 7de2e84b6..9cf289090 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -1,7 +1,5 @@ import 'dart:io'; - import 'package:cw_core/keyable.dart'; -import 'package:flutter/foundation.dart'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:hive/hive.dart'; @@ -11,22 +9,26 @@ import 'package:http/io_client.dart' as ioc; part 'node.g.dart'; Uri createUriFromElectrumAddress(String address) => - Uri.tryParse('tcp://$address'); + Uri.tryParse('tcp://$address')!; @HiveType(typeId: Node.typeId) class Node extends HiveObject with Keyable { Node( - {@required String uri, - @required WalletType type, - this.login, + {this.login, this.password, - this.useSSL}) { - uriRaw = uri; - this.type = type; + this.useSSL, + String? uri, + WalletType? type,}) { + if (uri != null) { + uriRaw = uri; + } + if (type != null) { + this.type = type; + } } - Node.fromMap(Map map) - : uriRaw = map['uri'] as String ?? '', + Node.fromMap(Map map) + : uriRaw = map['uri'] as String? ?? '', login = map['login'] as String, password = map['password'] as String, typeRaw = map['typeRaw'] as int, @@ -36,19 +38,19 @@ class Node extends HiveObject with Keyable { static const boxName = 'Nodes'; @HiveField(0) - String uriRaw; + late String uriRaw; @HiveField(1) - String login; + String? login; @HiveField(2) - String password; + String? password; @HiveField(3) - int typeRaw; + late int typeRaw; @HiveField(4) - bool useSSL; + bool? useSSL; bool get isSSL => useSSL ?? false; @@ -63,7 +65,7 @@ class Node extends HiveObject with Keyable { case WalletType.haven: return Uri.http(uriRaw, ''); default: - return null; + throw Exception('Unexpected type ${type.toString()} for Node uri'); } } @@ -99,7 +101,6 @@ class Node extends HiveObject with Keyable { } Future requestMoneroNode() async { - final path = '/json_rpc'; final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path); final realm = 'monero-rpc'; diff --git a/cw_core/lib/output_info.dart b/cw_core/lib/output_info.dart index 2c5a2ba5b..e2b1201a8 100644 --- a/cw_core/lib/output_info.dart +++ b/cw_core/lib/output_info.dart @@ -1,20 +1,20 @@ class OutputInfo { const OutputInfo( - {this.fiatAmount, - this.cryptoAmount, - this.address, - this.note, - this.sendAll, - this.extractedAddress, - this.isParsedAddress, - this.formattedCryptoAmount}); + {required this.address, + required this.sendAll, + required this.isParsedAddress, + this.cryptoAmount, + this.formattedCryptoAmount, + this.fiatAmount, + this.note, + this.extractedAddress,}); - final String fiatAmount; - final String cryptoAmount; + final String? fiatAmount; + final String? cryptoAmount; final String address; - final String note; - final String extractedAddress; + final String? note; + final String? extractedAddress; final bool sendAll; final bool isParsedAddress; - final int formattedCryptoAmount; + final int? formattedCryptoAmount; } \ No newline at end of file diff --git a/cw_core/lib/pathForWallet.dart b/cw_core/lib/pathForWallet.dart index bd05892ab..af4838ffa 100644 --- a/cw_core/lib/pathForWallet.dart +++ b/cw_core/lib/pathForWallet.dart @@ -3,7 +3,7 @@ import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; import 'package:path_provider/path_provider.dart'; -Future pathForWalletDir({@required String name, @required WalletType type}) async { +Future pathForWalletDir({required String name, required WalletType type}) async { final root = await getApplicationDocumentsDirectory(); final prefix = walletTypeToString(type).toLowerCase(); final walletsDir = Directory('${root.path}/wallets'); @@ -16,11 +16,11 @@ Future pathForWalletDir({@required String name, @required WalletType ty return walletDire.path; } -Future pathForWallet({@required String name, @required WalletType type}) async => +Future pathForWallet({required String name, required WalletType type}) async => await pathForWalletDir(name: name, type: type) .then((path) => path + '/$name'); -Future outdatedAndroidPathForWalletDir({String name}) async { +Future outdatedAndroidPathForWalletDir({required String name}) async { final directory = await getApplicationDocumentsDirectory(); final pathDir = directory.path + '/$name'; diff --git a/cw_core/lib/sec_random_native.dart b/cw_core/lib/sec_random_native.dart index b9800fd71..ce251efc0 100644 --- a/cw_core/lib/sec_random_native.dart +++ b/cw_core/lib/sec_random_native.dart @@ -6,7 +6,7 @@ const utils = const MethodChannel('com.cake_wallet/native_utils'); Future secRandom(int count) async { try { - return await utils.invokeMethod('sec_random', {'count': count}); + return await utils.invokeMethod('sec_random', {'count': count}) ?? Uint8List.fromList([]); } on PlatformException catch (_) { return Uint8List.fromList([]); } diff --git a/cw_core/lib/subaddress.dart b/cw_core/lib/subaddress.dart index 0f884dfa4..8571544a9 100644 --- a/cw_core/lib/subaddress.dart +++ b/cw_core/lib/subaddress.dart @@ -1,7 +1,7 @@ class Subaddress { - Subaddress({this.id, this.address, this.label}); + Subaddress({required this.id, required this.address, required this.label}); - Subaddress.fromMap(Map map) + Subaddress.fromMap(Map map) : this.id = map['id'] == null ? 0 : int.parse(map['id'] as String), this.address = (map['address'] ?? '') as String, this.label = (map['label'] ?? '') as String; diff --git a/cw_core/lib/transaction_direction.dart b/cw_core/lib/transaction_direction.dart index a82420e2c..8d1ddfe02 100644 --- a/cw_core/lib/transaction_direction.dart +++ b/cw_core/lib/transaction_direction.dart @@ -2,16 +2,22 @@ enum TransactionDirection { incoming, outgoing } TransactionDirection parseTransactionDirectionFromInt(int raw) { switch (raw) { - case 0: return TransactionDirection.incoming; - case 1: return TransactionDirection.outgoing; - default: return null; + case 0: + return TransactionDirection.incoming; + case 1: + return TransactionDirection.outgoing; + default: + throw Exception('Unexpected token: raw for TransactionDirection parseTransactionDirectionFromInt'); } } TransactionDirection parseTransactionDirectionFromNumber(String raw) { switch (raw) { - case "0": return TransactionDirection.incoming; - case "1": return TransactionDirection.outgoing; - default: return null; + case "0": + return TransactionDirection.incoming; + case "1": + return TransactionDirection.outgoing; + default: + throw Exception('Unexpected token: raw for TransactionDirection parseTransactionDirectionFromNumber'); } } \ No newline at end of file diff --git a/cw_core/lib/transaction_history.dart b/cw_core/lib/transaction_history.dart index 0011cd811..508f3aeca 100644 --- a/cw_core/lib/transaction_history.dart +++ b/cw_core/lib/transaction_history.dart @@ -1,10 +1,9 @@ -import 'package:flutter/foundation.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/transaction_info.dart'; abstract class TransactionHistoryBase { - TransactionHistoryBase(); - // : _isUpdating = false; + TransactionHistoryBase() + : transactions = ObservableMap(); @observable ObservableMap transactions; diff --git a/cw_core/lib/transaction_info.dart b/cw_core/lib/transaction_info.dart index ae6bddbe2..b8e4a5e0c 100644 --- a/cw_core/lib/transaction_info.dart +++ b/cw_core/lib/transaction_info.dart @@ -2,21 +2,21 @@ import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/keyable.dart'; abstract class TransactionInfo extends Object with Keyable { - String id; - int amount; - int fee; - TransactionDirection direction; - bool isPending; - DateTime date; - int height; - int confirmations; + late String id; + late int amount; + int? fee; + late TransactionDirection direction; + late bool isPending; + late DateTime date; + late int height; + late int confirmations; String amountFormatted(); String fiatAmount(); - String feeFormatted(); + String? feeFormatted(); void changeFiatAmount(String amount); @override dynamic get keyIndex => id; - Map additionalInfo; + late Map additionalInfo; } \ No newline at end of file diff --git a/cw_core/lib/transaction_priority.dart b/cw_core/lib/transaction_priority.dart index fe34bf629..c173f1ddd 100644 --- a/cw_core/lib/transaction_priority.dart +++ b/cw_core/lib/transaction_priority.dart @@ -2,5 +2,5 @@ import 'package:cw_core/enumerable_item.dart'; abstract class TransactionPriority extends EnumerableItem with Serializable { - const TransactionPriority({String title, int raw}) : super(title: title, raw: raw); + const TransactionPriority({required String title, required int raw}) : super(title: title, raw: raw); } diff --git a/cw_core/lib/unspent_coins_info.dart b/cw_core/lib/unspent_coins_info.dart index 6ce647dce..3efbe26c5 100644 --- a/cw_core/lib/unspent_coins_info.dart +++ b/cw_core/lib/unspent_coins_info.dart @@ -5,11 +5,11 @@ part 'unspent_coins_info.g.dart'; @HiveType(typeId: UnspentCoinsInfo.typeId) class UnspentCoinsInfo extends HiveObject { UnspentCoinsInfo({ - this.walletId, - this.hash, - this.isFrozen, - this.isSending, - this.note}); + required this.walletId, + required this.hash, + required this.isFrozen, + required this.isSending, + required this.note}); static const typeId = 9; static const boxName = 'Unspent'; diff --git a/cw_core/lib/wallet_addresses.dart b/cw_core/lib/wallet_addresses.dart index 85f9d0b8e..a34101a88 100644 --- a/cw_core/lib/wallet_addresses.dart +++ b/cw_core/lib/wallet_addresses.dart @@ -1,9 +1,8 @@ import 'package:cw_core/wallet_info.dart'; abstract class WalletAddresses { - WalletAddresses(this.walletInfo) { - addressesMap = {}; - } + WalletAddresses(this.walletInfo) + : addressesMap = {}; final WalletInfo walletInfo; @@ -19,10 +18,6 @@ abstract class WalletAddresses { Future saveAddressesInBox() async { try { - if (walletInfo == null) { - return; - } - walletInfo.address = address; walletInfo.addresses = addressesMap; diff --git a/cw_core/lib/wallet_addresses_with_account.dart b/cw_core/lib/wallet_addresses_with_account.dart index 5691ad320..0dcf88de0 100644 --- a/cw_core/lib/wallet_addresses_with_account.dart +++ b/cw_core/lib/wallet_addresses_with_account.dart @@ -5,9 +5,9 @@ import 'package:cw_core/wallet_info.dart'; abstract class WalletAddressesWithAccount extends WalletAddresses { WalletAddressesWithAccount(WalletInfo walletInfo) : super(walletInfo); - T get account; + // T get account; - set account(T account); + // set account(T account); AccountList get accountList; } \ No newline at end of file diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index 173ba8155..1983e62b7 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -1,12 +1,12 @@ import 'package:mobx/mobx.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_addresses.dart'; import 'package:flutter/foundation.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/pending_transaction.dart'; -import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/sync_status.dart'; @@ -48,15 +48,15 @@ abstract class WalletBase< WalletAddresses get walletAddresses; - HistoryType transactionHistory; + late HistoryType transactionHistory; - Future connectToNode({@required Node node}); + Future connectToNode({required Node node}); Future startSync(); Future createTransaction(Object credentials); - int calculateEstimatedFee(TransactionPriority priority, int amount); + int calculateEstimatedFee(TransactionPriority priority, int? amount); // void fetchTransactionsAsync( // void Function(TransactionType transaction) onTransactionLoaded, @@ -66,7 +66,7 @@ abstract class WalletBase< Future save(); - Future rescan({int height}); + Future rescan({required int height}); void close(); diff --git a/cw_core/lib/wallet_credentials.dart b/cw_core/lib/wallet_credentials.dart index 69c07bfda..e028232e8 100644 --- a/cw_core/lib/wallet_credentials.dart +++ b/cw_core/lib/wallet_credentials.dart @@ -1,10 +1,14 @@ import 'package:cw_core/wallet_info.dart'; abstract class WalletCredentials { - WalletCredentials({this.name, this.password, this.height, this.walletInfo}); + WalletCredentials({ + required this.name, + this.height, + this.walletInfo, + this.password}); final String name; - final int height; - String password; - WalletInfo walletInfo; + final int? height; + String? password; + WalletInfo? walletInfo; } diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart index 4e614811e..130b8ff5f 100644 --- a/cw_core/lib/wallet_info.dart +++ b/cw_core/lib/wallet_info.dart @@ -13,20 +13,20 @@ class WalletInfo extends HiveObject { : _yatLastUsedAddressController = StreamController.broadcast(); factory WalletInfo.external( - {@required String id, - @required String name, - @required WalletType type, - @required bool isRecovery, - @required int restoreHeight, - @required DateTime date, - @required String dirPath, - @required String path, - @required String address, + {required String id, + required String name, + required WalletType type, + required bool isRecovery, + required int restoreHeight, + required DateTime date, + required String dirPath, + required String path, + required String address, + bool? showIntroCakePayCard, String yatEid ='', - String yatLastUsedAddressRaw = '', - bool showIntroCakePayCard}) { + String yatLastUsedAddressRaw = ''}) { return WalletInfo(id, name, type, isRecovery, restoreHeight, - date.millisecondsSinceEpoch ?? 0, dirPath, path, address, + date.millisecondsSinceEpoch, dirPath, path, address, yatEid, yatLastUsedAddressRaw, showIntroCakePayCard); } @@ -61,7 +61,7 @@ class WalletInfo extends HiveObject { String address; @HiveField(10) - Map addresses; + Map? addresses; @HiveField(11) String yatEid; @@ -70,7 +70,7 @@ class WalletInfo extends HiveObject { String yatLastUsedAddressRaw; @HiveField(13) - bool showIntroCakePayCard; + bool? showIntroCakePayCard; String get yatLastUsedAddress => yatLastUsedAddressRaw; @@ -85,7 +85,7 @@ class WalletInfo extends HiveObject { if(showIntroCakePayCard == null) { return type != WalletType.haven; } - return showIntroCakePayCard; + return showIntroCakePayCard!; } DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp); diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index 6a39fa63f..e76e4539e 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -55,7 +55,7 @@ WalletType deserializeFromInt(int raw) { case 3: return WalletType.haven; default: - return null; + throw Exception('Unexpected token: $raw for WalletType deserializeFromInt'); } } @@ -100,6 +100,6 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) { case WalletType.haven: return CryptoCurrency.xhv; default: - return null; + throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency'); } } diff --git a/cw_core/pubspec.lock b/cw_core/pubspec.lock index 9858511de..951a97ffb 100644 --- a/cw_core/pubspec.lock +++ b/cw_core/pubspec.lock @@ -7,35 +7,35 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "14.0.0" + version: "47.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.41.2" + version: "4.7.0" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.6.0" + version: "2.3.1" asn1lib: dependency: transitive description: name: asn1lib url: "https://pub.dartlang.org" source: hosted - version: "0.8.1" + version: "1.1.1" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -49,42 +49,42 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" + version: "2.3.1" build_config: dependency: transitive description: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "0.4.6" + version: "1.1.0" build_daemon: dependency: transitive description: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "2.1.10" + version: "3.1.0" build_resolvers: dependency: "direct dev" description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "1.5.3" + version: "2.0.10" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.11.5" + version: "2.2.1" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "6.1.10" + version: "7.2.4" built_collection: dependency: transitive description: @@ -105,98 +105,77 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" + version: "1.2.1" checked_yaml: dependency: transitive description: name: checked_yaml url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" - cli_util: - dependency: transitive - description: - name: cli_util - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.5" + version: "2.0.1" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" code_builder: dependency: transitive description: name: code_builder url: "https://pub.dartlang.org" source: hosted - version: "3.7.0" + version: "4.3.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" convert: dependency: transitive description: name: convert url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "3.0.2" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "3.0.2" dart_style: dependency: transitive description: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.3.12" - dartx: - dependency: transitive - description: - name: dartx - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.0" + version: "2.2.4" encrypt: dependency: "direct main" description: name: encrypt url: "https://pub.dartlang.org" source: hosted - version: "4.1.0" + version: "5.0.1" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" ffi: dependency: transitive description: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "2.0.1" file: dependency: transitive description: @@ -222,12 +201,19 @@ packages: name: flutter_mobx url: "https://pub.dartlang.org" source: hosted - version: "1.1.0+2" + version: "2.0.6+4" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" glob: dependency: transitive description: @@ -241,42 +227,42 @@ packages: name: graphs url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "2.1.0" hive: dependency: transitive description: name: hive url: "https://pub.dartlang.org" source: hosted - version: "1.4.4+1" + version: "2.2.3" hive_generator: dependency: "direct dev" description: name: hive_generator url: "https://pub.dartlang.org" source: hosted - version: "0.8.2" + version: "1.1.3" http: dependency: "direct main" description: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.2" + version: "0.13.5" http_multi_server: dependency: transitive description: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "3.2.1" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" + version: "4.0.1" intl: dependency: "direct main" description: @@ -290,7 +276,7 @@ packages: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.5" + version: "1.0.3" js: dependency: transitive description: @@ -304,7 +290,7 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "4.0.1" + version: "4.6.0" logging: dependency: transitive description: @@ -318,14 +304,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10" + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.8.0" mime: dependency: transitive description: @@ -339,63 +332,77 @@ packages: name: mobx url: "https://pub.dartlang.org" source: hosted - version: "1.2.1+4" + version: "2.1.0" mobx_codegen: dependency: "direct dev" description: name: mobx_codegen url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "2.0.7+3" package_config: dependency: transitive description: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "2.1.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.2" path_provider: dependency: "direct main" description: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.6.28" + version: "2.0.11" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.20" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" path_provider_linux: dependency: transitive description: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+2" + version: "2.1.7" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+8" + version: "2.0.6" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.4" path_provider_windows: dependency: transitive description: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.5" + version: "2.1.3" pedantic: dependency: transitive description: @@ -416,14 +423,14 @@ packages: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.1.3" pointycastle: dependency: transitive description: name: pointycastle url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "3.6.2" pool: dependency: transitive description: @@ -451,21 +458,21 @@ packages: name: pubspec_parse url: "https://pub.dartlang.org" source: hosted - version: "0.1.8" + version: "1.2.1" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "0.7.9" + version: "1.3.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.4+1" + version: "1.0.2" sky_engine: dependency: transitive description: flutter @@ -477,14 +484,21 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "0.9.10+3" + version: "1.2.3" + source_helper: + dependency: transitive + description: + name: source_helper + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.3" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -512,35 +526,28 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19" - time: - dependency: transitive - description: - name: time - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.1" + version: "0.4.12" timing: dependency: transitive description: name: timing url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+3" + version: "1.0.0" typed_data: dependency: transitive description: @@ -554,7 +561,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.2" watcher: dependency: transitive description: @@ -568,21 +575,21 @@ packages: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.2.0" win32: dependency: transitive description: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "3.0.0" xdg_directories: dependency: transitive description: name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "0.1.2" + version: "0.2.0+2" yaml: dependency: transitive description: @@ -591,5 +598,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.12.0 <3.0.0" - flutter: ">=1.20.0" + dart: ">=2.17.5 <3.0.0" + flutter: ">=3.0.0" diff --git a/cw_core/pubspec.yaml b/cw_core/pubspec.yaml index 4caa92e11..50503361c 100644 --- a/cw_core/pubspec.yaml +++ b/cw_core/pubspec.yaml @@ -6,26 +6,26 @@ author: Cake Wallet homepage: https://cakewallet.com environment: - sdk: ">=2.7.0 <3.0.0" - flutter: ">=1.17.0" + sdk: ">=2.17.5 <3.0.0" + flutter: ">=1.20.0" dependencies: flutter: sdk: flutter - http: ^0.12.0+2 - path_provider: ^1.3.0 - mobx: ^1.2.1+2 - flutter_mobx: ^1.1.0+2 + http: ^0.13.4 + path_provider: ^2.0.11 + mobx: ^2.0.7+4 + flutter_mobx: ^2.0.6+1 intl: ^0.17.0 - encrypt: ^4.0.0 + encrypt: ^5.0.1 dev_dependencies: flutter_test: sdk: flutter - build_runner: ^1.10.3 - build_resolvers: ^1.3.10 - mobx_codegen: ^1.1.0+1 - hive_generator: ^0.8.1 + build_runner: ^2.1.11 + build_resolvers: ^2.0.9 + mobx_codegen: ^2.0.7 + hive_generator: ^1.1.3 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/cw_haven/lib/api/account_list.dart b/cw_haven/lib/api/account_list.dart index 96bf3d654..a05446c8e 100644 --- a/cw_haven/lib/api/account_list.dart +++ b/cw_haven/lib/api/account_list.dart @@ -50,16 +50,16 @@ List getAllAccount() { .toList(); } -void addAccountSync({String label}) { - final labelPointer = Utf8.toUtf8(label); +void addAccountSync({required String label}) { + final labelPointer = label.toNativeUtf8(); accountAddNewNative(labelPointer); - free(labelPointer); + calloc.free(labelPointer); } -void setLabelForAccountSync({int accountIndex, String label}) { - final labelPointer = Utf8.toUtf8(label); +void setLabelForAccountSync({required int accountIndex, required String label}) { + final labelPointer = label.toNativeUtf8(); accountSetLabelNative(accountIndex, labelPointer); - free(labelPointer); + calloc.free(labelPointer); } void _addAccount(String label) => addAccountSync(label: label); @@ -71,12 +71,12 @@ void _setLabelForAccount(Map args) { setLabelForAccountSync(label: label, accountIndex: accountIndex); } -Future addAccount({String label}) async { +Future addAccount({required String label}) async { await compute(_addAccount, label); await store(); } -Future setLabelForAccount({int accountIndex, String label}) async { +Future setLabelForAccount({required int accountIndex, required String label}) async { await compute( _setLabelForAccount, {'accountIndex': accountIndex, 'label': label}); await store(); diff --git a/cw_haven/lib/api/convert_utf8_to_string.dart b/cw_haven/lib/api/convert_utf8_to_string.dart index 7fa5a68df..41a6b648a 100644 --- a/cw_haven/lib/api/convert_utf8_to_string.dart +++ b/cw_haven/lib/api/convert_utf8_to_string.dart @@ -1,8 +1,8 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; -String convertUTF8ToString({Pointer pointer}) { - final str = Utf8.fromUtf8(pointer); - free(pointer); +String convertUTF8ToString({required Pointer pointer}) { + final str = pointer.toDartString(); + calloc.free(pointer); return str; } \ No newline at end of file diff --git a/cw_haven/lib/api/cw_haven.dart b/cw_haven/lib/api/cw_haven.dart index 1d3726e17..0e48276d9 100644 --- a/cw_haven/lib/api/cw_haven.dart +++ b/cw_haven/lib/api/cw_haven.dart @@ -8,7 +8,7 @@ class CwHaven { const MethodChannel('cw_haven'); static Future get platformVersion async { - final String version = await _channel.invokeMethod('getPlatformVersion'); + final String version = await _channel.invokeMethod('getPlatformVersion') ?? ''; return version; } } diff --git a/cw_haven/lib/api/exceptions/connection_to_node_exception.dart b/cw_haven/lib/api/exceptions/connection_to_node_exception.dart index 6ee272b89..483b0a174 100644 --- a/cw_haven/lib/api/exceptions/connection_to_node_exception.dart +++ b/cw_haven/lib/api/exceptions/connection_to_node_exception.dart @@ -1,5 +1,5 @@ class ConnectionToNodeException implements Exception { - ConnectionToNodeException({this.message}); + ConnectionToNodeException({required this.message}); final String message; } \ No newline at end of file diff --git a/cw_haven/lib/api/exceptions/creation_transaction_exception.dart b/cw_haven/lib/api/exceptions/creation_transaction_exception.dart index bb477d673..7b55ec074 100644 --- a/cw_haven/lib/api/exceptions/creation_transaction_exception.dart +++ b/cw_haven/lib/api/exceptions/creation_transaction_exception.dart @@ -1,5 +1,5 @@ class CreationTransactionException implements Exception { - CreationTransactionException({this.message}); + CreationTransactionException({required this.message}); final String message; diff --git a/cw_haven/lib/api/exceptions/setup_wallet_exception.dart b/cw_haven/lib/api/exceptions/setup_wallet_exception.dart index ce43c0ec6..b6e0c1f18 100644 --- a/cw_haven/lib/api/exceptions/setup_wallet_exception.dart +++ b/cw_haven/lib/api/exceptions/setup_wallet_exception.dart @@ -1,5 +1,5 @@ class SetupWalletException implements Exception { - SetupWalletException({this.message}); + SetupWalletException({required this.message}); final String message; } \ No newline at end of file diff --git a/cw_haven/lib/api/exceptions/wallet_creation_exception.dart b/cw_haven/lib/api/exceptions/wallet_creation_exception.dart index 6b00445ad..6052366b9 100644 --- a/cw_haven/lib/api/exceptions/wallet_creation_exception.dart +++ b/cw_haven/lib/api/exceptions/wallet_creation_exception.dart @@ -1,5 +1,5 @@ class WalletCreationException implements Exception { - WalletCreationException({this.message}); + WalletCreationException({required this.message}); final String message; diff --git a/cw_haven/lib/api/exceptions/wallet_opening_exception.dart b/cw_haven/lib/api/exceptions/wallet_opening_exception.dart index 8d84b0f7e..df7a850a4 100644 --- a/cw_haven/lib/api/exceptions/wallet_opening_exception.dart +++ b/cw_haven/lib/api/exceptions/wallet_opening_exception.dart @@ -1,5 +1,5 @@ class WalletOpeningException implements Exception { - WalletOpeningException({this.message}); + WalletOpeningException({required this.message}); final String message; diff --git a/cw_haven/lib/api/exceptions/wallet_restore_from_keys_exception.dart b/cw_haven/lib/api/exceptions/wallet_restore_from_keys_exception.dart index 5f08437d4..c6b6c6ef7 100644 --- a/cw_haven/lib/api/exceptions/wallet_restore_from_keys_exception.dart +++ b/cw_haven/lib/api/exceptions/wallet_restore_from_keys_exception.dart @@ -1,5 +1,5 @@ class WalletRestoreFromKeysException implements Exception { - WalletRestoreFromKeysException({this.message}); + WalletRestoreFromKeysException({required this.message}); final String message; } \ No newline at end of file diff --git a/cw_haven/lib/api/exceptions/wallet_restore_from_seed_exception.dart b/cw_haven/lib/api/exceptions/wallet_restore_from_seed_exception.dart index fd89e4161..004cd7958 100644 --- a/cw_haven/lib/api/exceptions/wallet_restore_from_seed_exception.dart +++ b/cw_haven/lib/api/exceptions/wallet_restore_from_seed_exception.dart @@ -1,5 +1,5 @@ class WalletRestoreFromSeedException implements Exception { - WalletRestoreFromSeedException({this.message}); + WalletRestoreFromSeedException({required this.message}); final String message; } \ No newline at end of file diff --git a/cw_haven/lib/api/monero_output.dart b/cw_haven/lib/api/monero_output.dart index 831ee1f22..a6d735bd3 100644 --- a/cw_haven/lib/api/monero_output.dart +++ b/cw_haven/lib/api/monero_output.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; class MoneroOutput { - MoneroOutput({@required this.address, @required this.amount}); + MoneroOutput({required this.address, required this.amount}); final String address; final String amount; diff --git a/cw_haven/lib/api/signatures.dart b/cw_haven/lib/api/signatures.dart index 9dd1c8dac..774a303d7 100644 --- a/cw_haven/lib/api/signatures.dart +++ b/cw_haven/lib/api/signatures.dart @@ -39,7 +39,7 @@ typedef get_node_height = Int64 Function(); typedef is_connected = Int8 Function(); typedef setup_node = Int8 Function( - Pointer, Pointer, Pointer, Int8, Int8, Pointer); + Pointer, Pointer?, Pointer?, Int8, Int8, Pointer); typedef start_refresh = Void Function(); @@ -86,7 +86,7 @@ typedef account_set_label = Void Function( typedef transactions_refresh = Void Function(); -typedef get_tx_key = Pointer Function(Pointer txId); +typedef get_tx_key = Pointer? Function(Pointer txId); typedef transactions_count = Int64 Function(); diff --git a/cw_haven/lib/api/structs/account_row.dart b/cw_haven/lib/api/structs/account_row.dart index c3fc22de1..aa492ee0f 100644 --- a/cw_haven/lib/api/structs/account_row.dart +++ b/cw_haven/lib/api/structs/account_row.dart @@ -3,9 +3,10 @@ import 'package:ffi/ffi.dart'; class AccountRow extends Struct { @Int64() - int id; - Pointer label; + external int id; + + external Pointer label; - String getLabel() => Utf8.fromUtf8(label); + String getLabel() => label.toDartString(); int getId() => id; } diff --git a/cw_haven/lib/api/structs/haven_balance_row.dart b/cw_haven/lib/api/structs/haven_balance_row.dart index 4b68a7bb6..b0f657bca 100644 --- a/cw_haven/lib/api/structs/haven_balance_row.dart +++ b/cw_haven/lib/api/structs/haven_balance_row.dart @@ -3,9 +3,10 @@ import 'package:ffi/ffi.dart'; class HavenBalanceRow extends Struct { @Int64() - int amount; - Pointer assetType; + external int amount; + + external Pointer assetType; int getAmount() => amount; - String getAssetType() => Utf8.fromUtf8(assetType); + String getAssetType() => assetType.toDartString(); } diff --git a/cw_haven/lib/api/structs/haven_rate.dart b/cw_haven/lib/api/structs/haven_rate.dart index 818615559..48f188135 100644 --- a/cw_haven/lib/api/structs/haven_rate.dart +++ b/cw_haven/lib/api/structs/haven_rate.dart @@ -3,9 +3,10 @@ import 'package:ffi/ffi.dart'; class HavenRate extends Struct { @Int64() - int rate; - Pointer assetType; + external int rate; + + external Pointer assetType; int getRate() => rate; - String getAssetType() => Utf8.fromUtf8(assetType); + String getAssetType() => assetType.toDartString(); } diff --git a/cw_haven/lib/api/structs/pending_transaction.dart b/cw_haven/lib/api/structs/pending_transaction.dart index b492f28a0..12e5233f1 100644 --- a/cw_haven/lib/api/structs/pending_transaction.dart +++ b/cw_haven/lib/api/structs/pending_transaction.dart @@ -3,18 +3,22 @@ import 'package:ffi/ffi.dart'; class PendingTransactionRaw extends Struct { @Int64() - int amount; + external int amount; @Int64() - int fee; + external int fee; - Pointer hash; + external Pointer hash; - String getHash() => Utf8.fromUtf8(hash); + String getHash() => hash.toDartString(); } class PendingTransactionDescription { - PendingTransactionDescription({this.amount, this.fee, this.hash, this.pointerAddress}); + PendingTransactionDescription({ + required this.amount, + required this.fee, + required this.hash, + required this.pointerAddress}); final int amount; final int fee; diff --git a/cw_haven/lib/api/structs/subaddress_row.dart b/cw_haven/lib/api/structs/subaddress_row.dart index 1673e00c7..d593a793d 100644 --- a/cw_haven/lib/api/structs/subaddress_row.dart +++ b/cw_haven/lib/api/structs/subaddress_row.dart @@ -3,11 +3,13 @@ import 'package:ffi/ffi.dart'; class SubaddressRow extends Struct { @Int64() - int id; - Pointer address; - Pointer label; + external int id; + + external Pointer address; + + external Pointer label; - String getLabel() => Utf8.fromUtf8(label); - String getAddress() => Utf8.fromUtf8(address); + String getLabel() => label.toDartString(); + String getAddress() => address.toDartString(); int getId() => id; } \ No newline at end of file diff --git a/cw_haven/lib/api/structs/transaction_info_row.dart b/cw_haven/lib/api/structs/transaction_info_row.dart index 68a84e0a2..177cdfde7 100644 --- a/cw_haven/lib/api/structs/transaction_info_row.dart +++ b/cw_haven/lib/api/structs/transaction_info_row.dart @@ -3,42 +3,42 @@ import 'package:ffi/ffi.dart'; class TransactionInfoRow extends Struct { @Uint64() - int amount; + external int amount; @Uint64() - int fee; + external int fee; @Uint64() - int blockHeight; + external int blockHeight; @Uint64() - int confirmations; + external int confirmations; @Uint32() - int subaddrAccount; + external int subaddrAccount; @Int8() - int direction; + external int direction; @Int8() - int isPending; + external int isPending; @Uint32() - int subaddrIndex; + external int subaddrIndex; - Pointer hash; + external Pointer hash; - Pointer paymentId; + external Pointer paymentId; - Pointer assetType; + external Pointer assetType; @Int64() - int datetime; + external int datetime; int getDatetime() => datetime; int getAmount() => amount >= 0 ? amount : amount * -1; bool getIsPending() => isPending != 0; - String getHash() => Utf8.fromUtf8(hash); - String getPaymentId() => Utf8.fromUtf8(paymentId); - String getAssetType() => Utf8.fromUtf8(assetType); + String getHash() => hash.toDartString(); + String getPaymentId() => paymentId.toDartString(); + String getAssetType() => assetType.toDartString(); } diff --git a/cw_haven/lib/api/structs/ut8_box.dart b/cw_haven/lib/api/structs/ut8_box.dart index a6f41bc75..53e678c88 100644 --- a/cw_haven/lib/api/structs/ut8_box.dart +++ b/cw_haven/lib/api/structs/ut8_box.dart @@ -2,7 +2,7 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; class Utf8Box extends Struct { - Pointer value; + external Pointer value; - String getValue() => Utf8.fromUtf8(value); + String getValue() => value.toDartString(); } diff --git a/cw_haven/lib/api/subaddress_list.dart b/cw_haven/lib/api/subaddress_list.dart index c735400ec..39dbeab78 100644 --- a/cw_haven/lib/api/subaddress_list.dart +++ b/cw_haven/lib/api/subaddress_list.dart @@ -29,7 +29,7 @@ final subaddrressSetLabelNative = havenApi bool isUpdating = false; -void refreshSubaddresses({@required int accountIndex}) { +void refreshSubaddresses({required int accountIndex}) { try { isUpdating = true; subaddressRefreshNative(accountIndex); @@ -50,18 +50,18 @@ List getAllSubaddresses() { .toList(); } -void addSubaddressSync({int accountIndex, String label}) { - final labelPointer = Utf8.toUtf8(label); +void addSubaddressSync({required int accountIndex, required String label}) { + final labelPointer = label.toNativeUtf8(); subaddrressAddNewNative(accountIndex, labelPointer); - free(labelPointer); + calloc.free(labelPointer); } void setLabelForSubaddressSync( - {int accountIndex, int addressIndex, String label}) { - final labelPointer = Utf8.toUtf8(label); + {required int accountIndex, required int addressIndex, required String label}) { + final labelPointer = label.toNativeUtf8(); subaddrressSetLabelNative(accountIndex, addressIndex, labelPointer); - free(labelPointer); + calloc.free(labelPointer); } void _addSubaddress(Map args) { @@ -80,14 +80,14 @@ void _setLabelForSubaddress(Map args) { accountIndex: accountIndex, addressIndex: addressIndex, label: label); } -Future addSubaddress({int accountIndex, String label}) async { +Future addSubaddress({required int accountIndex, required String label}) async { await compute, void>( _addSubaddress, {'accountIndex': accountIndex, 'label': label}); await store(); } Future setLabelForSubaddress( - {int accountIndex, int addressIndex, String label}) async { + {required int accountIndex, required int addressIndex, required String label}) async { await compute, void>(_setLabelForSubaddress, { 'accountIndex': accountIndex, 'addressIndex': addressIndex, diff --git a/cw_haven/lib/api/transaction_history.dart b/cw_haven/lib/api/transaction_history.dart index 185207941..f658133e1 100644 --- a/cw_haven/lib/api/transaction_history.dart +++ b/cw_haven/lib/api/transaction_history.dart @@ -40,16 +40,16 @@ final getTxKeyNative = havenApi .asFunction(); String getTxKey(String txId) { - final txIdPointer = Utf8.toUtf8(txId); + final txIdPointer = txId.toNativeUtf8(); final keyPointer = getTxKeyNative(txIdPointer); - free(txIdPointer); + calloc.free(txIdPointer); if (keyPointer != null) { return convertUTF8ToString(pointer: keyPointer); } - return null; + return ''; } void refreshTransactions() => transactionsRefreshNative(); @@ -67,18 +67,18 @@ List getAllTransations() { } PendingTransactionDescription createTransactionSync( - {String address, - String assetType, - String paymentId, - String amount, - int priorityRaw, + {required String address, + required String assetType, + required String paymentId, + required int priorityRaw, + String? amount, int accountIndex = 0}) { - final addressPointer = Utf8.toUtf8(address); - final assetTypePointer = Utf8.toUtf8(assetType); - final paymentIdPointer = Utf8.toUtf8(paymentId); - final amountPointer = amount != null ? Utf8.toUtf8(amount) : nullptr; - final errorMessagePointer = allocate(); - final pendingTransactionRawPointer = allocate(); + final addressPointer = address.toNativeUtf8(); + final assetTypePointer = assetType.toNativeUtf8(); + final paymentIdPointer = paymentId.toNativeUtf8(); + final amountPointer = amount != null ? amount.toNativeUtf8() : nullptr; + final errorMessagePointer = calloc(); + final pendingTransactionRawPointer = calloc(); final created = transactionCreateNative( addressPointer, assetTypePointer, @@ -90,17 +90,17 @@ PendingTransactionDescription createTransactionSync( pendingTransactionRawPointer) != 0; - free(addressPointer); - free(assetTypePointer); - free(paymentIdPointer); + calloc.free(addressPointer); + calloc.free(assetTypePointer); + calloc.free(paymentIdPointer); if (amountPointer != nullptr) { - free(amountPointer); + calloc.free(amountPointer); } if (!created) { final message = errorMessagePointer.ref.getValue(); - free(errorMessagePointer); + calloc.free(errorMessagePointer); throw CreationTransactionException(message: message); } @@ -112,28 +112,28 @@ PendingTransactionDescription createTransactionSync( } PendingTransactionDescription createTransactionMultDestSync( - {List outputs, - String assetType, - String paymentId, - int priorityRaw, + {required List outputs, + required String assetType, + required String paymentId, + required int priorityRaw, int accountIndex = 0}) { final int size = outputs.length; final List> addressesPointers = outputs.map((output) => - Utf8.toUtf8(output.address)).toList(); - final Pointer> addressesPointerPointer = allocate(count: size); + output.address.toNativeUtf8()).toList(); + final Pointer> addressesPointerPointer = calloc(size); final List> amountsPointers = outputs.map((output) => - Utf8.toUtf8(output.amount)).toList(); - final Pointer> amountsPointerPointer = allocate(count: size); + output.amount.toNativeUtf8()).toList(); + final Pointer> amountsPointerPointer = calloc( size); for (int i = 0; i < size; i++) { addressesPointerPointer[i] = addressesPointers[i]; amountsPointerPointer[i] = amountsPointers[i]; } - final assetTypePointer = Utf8.toUtf8(assetType); - final paymentIdPointer = Utf8.toUtf8(paymentId); - final errorMessagePointer = allocate(); - final pendingTransactionRawPointer = allocate(); + final assetTypePointer = assetType.toNativeUtf8(); + final paymentIdPointer = paymentId.toNativeUtf8(); + final errorMessagePointer = calloc(); + final pendingTransactionRawPointer = calloc(); final created = transactionCreateMultDestNative( addressesPointerPointer, assetTypePointer, @@ -146,18 +146,18 @@ PendingTransactionDescription createTransactionMultDestSync( pendingTransactionRawPointer) != 0; - free(addressesPointerPointer); - free(assetTypePointer); - free(amountsPointerPointer); + calloc.free(addressesPointerPointer); + calloc.free(assetTypePointer); + calloc.free(amountsPointerPointer); - addressesPointers.forEach((element) => free(element)); - amountsPointers.forEach((element) => free(element)); + addressesPointers.forEach((element) => calloc.free(element)); + amountsPointers.forEach((element) => calloc.free(element)); - free(paymentIdPointer); + calloc.free(paymentIdPointer); if (!created) { final message = errorMessagePointer.ref.getValue(); - free(errorMessagePointer); + calloc.free(errorMessagePointer); throw CreationTransactionException(message: message); } @@ -168,17 +168,17 @@ PendingTransactionDescription createTransactionMultDestSync( pointerAddress: pendingTransactionRawPointer.address); } -void commitTransactionFromPointerAddress({int address}) => commitTransaction( +void commitTransactionFromPointerAddress({required int address}) => commitTransaction( transactionPointer: Pointer.fromAddress(address)); -void commitTransaction({Pointer transactionPointer}) { - final errorMessagePointer = allocate(); +void commitTransaction({required Pointer transactionPointer}) { + final errorMessagePointer = calloc(); final isCommited = transactionCommitNative(transactionPointer, errorMessagePointer) != 0; if (!isCommited) { final message = errorMessagePointer.ref.getValue(); - free(errorMessagePointer); + calloc.free(errorMessagePointer); throw CreationTransactionException(message: message); } } @@ -216,11 +216,11 @@ PendingTransactionDescription _createTransactionMultDestSync(Map args) { } Future createTransaction( - {String address, - String assetType, + {required String address, + required String assetType, + required int priorityRaw, + String? amount, String paymentId = '', - String amount, - int priorityRaw, int accountIndex = 0}) => compute(_createTransactionSync, { 'address': address, @@ -232,10 +232,10 @@ Future createTransaction( }); Future createTransactionMultDest( - {List outputs, - String assetType, + {required List outputs, + required int priorityRaw, + String? assetType, String paymentId = '', - int priorityRaw, int accountIndex = 0}) => compute(_createTransactionMultDestSync, { 'outputs': outputs, diff --git a/cw_haven/lib/api/types.dart b/cw_haven/lib/api/types.dart index 878297501..f1dc0e26b 100644 --- a/cw_haven/lib/api/types.dart +++ b/cw_haven/lib/api/types.dart @@ -39,7 +39,7 @@ typedef GetNodeHeight = int Function(); typedef IsConnected = int Function(); typedef SetupNode = int Function( - Pointer, Pointer, Pointer, int, int, Pointer); + Pointer, Pointer?, Pointer?, int, int, Pointer); typedef StartRefresh = void Function(); @@ -84,7 +84,7 @@ typedef AccountSetLabel = void Function(int accountIndex, Pointer label); typedef TransactionsRefresh = void Function(); -typedef GetTxKey = Pointer Function(Pointer txId); +typedef GetTxKey = Pointer? Function(Pointer txId); typedef TransactionsCount = int Function(); diff --git a/cw_haven/lib/api/wallet.dart b/cw_haven/lib/api/wallet.dart index 3370fd3e0..2b85f60e2 100644 --- a/cw_haven/lib/api/wallet.dart +++ b/cw_haven/lib/api/wallet.dart @@ -142,24 +142,24 @@ int getNodeHeightSync() => getNodeHeightNative(); bool isConnectedSync() => isConnectedNative() != 0; bool setupNodeSync( - {String address, - String login, - String password, + {required String address, + String? login, + String? password, bool useSSL = false, bool isLightWallet = false}) { - final addressPointer = Utf8.toUtf8(address); - Pointer loginPointer; - Pointer passwordPointer; + final addressPointer = address.toNativeUtf8(); + Pointer? loginPointer; + Pointer? passwordPointer; if (login != null) { - loginPointer = Utf8.toUtf8(login); + loginPointer = login.toNativeUtf8(); } if (password != null) { - passwordPointer = Utf8.toUtf8(password); + passwordPointer = password.toNativeUtf8(); } - final errorMessagePointer = allocate(); + final errorMessagePointer = ''.toNativeUtf8(); final isSetupNode = setupNodeNative( addressPointer, loginPointer, @@ -169,9 +169,15 @@ bool setupNodeSync( errorMessagePointer) != 0; - free(addressPointer); - free(loginPointer); - free(passwordPointer); + calloc.free(addressPointer); + + if (loginPointer != null) { + calloc.free(loginPointer); + } + + if (passwordPointer != null) { + calloc.free(passwordPointer); + } if (!isSetupNode) { throw SetupWalletException( @@ -185,31 +191,31 @@ void startRefreshSync() => startRefreshNative(); Future connectToNode() async => connecToNodeNative() != 0; -void setRefreshFromBlockHeight({int height}) => +void setRefreshFromBlockHeight({required int height}) => setRefreshFromBlockHeightNative(height); -void setRecoveringFromSeed({bool isRecovery}) => +void setRecoveringFromSeed({required bool isRecovery}) => setRecoveringFromSeedNative(_boolToInt(isRecovery)); void storeSync() { - final pathPointer = Utf8.toUtf8(''); + final pathPointer = ''.toNativeUtf8(); storeNative(pathPointer); - free(pathPointer); + calloc.free(pathPointer); } void setPasswordSync(String password) { - final passwordPointer = Utf8.toUtf8(password); - final errorMessagePointer = allocate(); + final passwordPointer = password.toNativeUtf8(); + final errorMessagePointer = calloc(); final changed = setPasswordNative(passwordPointer, errorMessagePointer) != 0; - free(passwordPointer); + calloc.free(passwordPointer); if (!changed) { final message = errorMessagePointer.ref.getValue(); - free(errorMessagePointer); + calloc.free(errorMessagePointer); throw Exception(message); } - free(errorMessagePointer); + calloc.free(errorMessagePointer); } void closeCurrentWallet() => closeCurrentWalletNative(); @@ -227,16 +233,15 @@ String getPublicSpendKey() => convertUTF8ToString(pointer: getPublicSpendKeyNative()); class SyncListener { - SyncListener(this.onNewBlock, this.onNewTransaction) { - _cachedBlockchainHeight = 0; - _lastKnownBlockHeight = 0; - _initialSyncHeight = 0; - } + SyncListener(this.onNewBlock, this.onNewTransaction) + : _cachedBlockchainHeight = 0, + _lastKnownBlockHeight = 0, + _initialSyncHeight = 0; void Function(int, int, double) onNewBlock; void Function() onNewTransaction; - Timer _updateSyncInfoTimer; + Timer? _updateSyncInfoTimer; int _cachedBlockchainHeight; int _lastKnownBlockHeight; int _initialSyncHeight; @@ -325,13 +330,13 @@ int _getNodeHeight(Object _) => getNodeHeightSync(); void startRefresh() => startRefreshSync(); -Future setupNode( - {String address, - String login, - String password, +Future setupNode( + {required String address, + String? login, + String? password, bool useSSL = false, bool isLightWallet = false}) => - compute, void>(_setupNodeSync, { + compute, void>(_setupNodeSync, { 'address': address, 'login': login, 'password': password, @@ -339,7 +344,7 @@ Future setupNode( 'isLightWallet': isLightWallet }); -Future store() => compute(_storeSync, 0); +Future store() => compute(_storeSync, 0); Future isConnected() => compute(_isConnected, 0); diff --git a/cw_haven/lib/api/wallet_manager.dart b/cw_haven/lib/api/wallet_manager.dart index d134dcbb4..627fc226a 100644 --- a/cw_haven/lib/api/wallet_manager.dart +++ b/cw_haven/lib/api/wallet_manager.dart @@ -38,18 +38,18 @@ final errorStringNative = havenApi .asFunction(); void createWalletSync( - {String path, String password, String language, int nettype = 0}) { - final pathPointer = Utf8.toUtf8(path); - final passwordPointer = Utf8.toUtf8(password); - final languagePointer = Utf8.toUtf8(language); - final errorMessagePointer = allocate(); + {required String path, required String password, required String language, int nettype = 0}) { + final pathPointer = path.toNativeUtf8(); + final passwordPointer = password.toNativeUtf8(); + final languagePointer = language.toNativeUtf8(); + final errorMessagePointer = ''.toNativeUtf8(); final isWalletCreated = createWalletNative(pathPointer, passwordPointer, languagePointer, nettype, errorMessagePointer) != 0; - free(pathPointer); - free(passwordPointer); - free(languagePointer); + calloc.free(pathPointer); + calloc.free(passwordPointer); + calloc.free(languagePointer); if (!isWalletCreated) { throw WalletCreationException( @@ -59,25 +59,25 @@ void createWalletSync( // setupNodeSync(address: "node.moneroworld.com:18089"); } -bool isWalletExistSync({String path}) { - final pathPointer = Utf8.toUtf8(path); +bool isWalletExistSync({required String path}) { + final pathPointer = path.toNativeUtf8(); final isExist = isWalletExistNative(pathPointer) != 0; - free(pathPointer); + calloc.free(pathPointer); return isExist; } void restoreWalletFromSeedSync( - {String path, - String password, - String seed, + {required String path, + required String password, + required String seed, int nettype = 0, int restoreHeight = 0}) { - final pathPointer = Utf8.toUtf8(path); - final passwordPointer = Utf8.toUtf8(password); - final seedPointer = Utf8.toUtf8(seed); - final errorMessagePointer = allocate(); + final pathPointer = path.toNativeUtf8(); + final passwordPointer = password.toNativeUtf8(); + final seedPointer = seed.toNativeUtf8(); + final errorMessagePointer = ''.toNativeUtf8(); final isWalletRestored = restoreWalletFromSeedNative( pathPointer, passwordPointer, @@ -87,9 +87,9 @@ void restoreWalletFromSeedSync( errorMessagePointer) != 0; - free(pathPointer); - free(passwordPointer); - free(seedPointer); + calloc.free(pathPointer); + calloc.free(passwordPointer); + calloc.free(seedPointer); if (!isWalletRestored) { throw WalletRestoreFromSeedException( @@ -98,21 +98,21 @@ void restoreWalletFromSeedSync( } void restoreWalletFromKeysSync( - {String path, - String password, - String language, - String address, - String viewKey, - String spendKey, + {required String path, + required String password, + required String language, + required String address, + required String viewKey, + required String spendKey, int nettype = 0, int restoreHeight = 0}) { - final pathPointer = Utf8.toUtf8(path); - final passwordPointer = Utf8.toUtf8(password); - final languagePointer = Utf8.toUtf8(language); - final addressPointer = Utf8.toUtf8(address); - final viewKeyPointer = Utf8.toUtf8(viewKey); - final spendKeyPointer = Utf8.toUtf8(spendKey); - final errorMessagePointer = allocate(); + final pathPointer = path.toNativeUtf8(); + final passwordPointer = password.toNativeUtf8(); + final languagePointer = language.toNativeUtf8(); + final addressPointer = address.toNativeUtf8(); + final viewKeyPointer = viewKey.toNativeUtf8(); + final spendKeyPointer = spendKey.toNativeUtf8(); + final errorMessagePointer = ''.toNativeUtf8(); final isWalletRestored = restoreWalletFromKeysNative( pathPointer, passwordPointer, @@ -125,12 +125,12 @@ void restoreWalletFromKeysSync( errorMessagePointer) != 0; - free(pathPointer); - free(passwordPointer); - free(languagePointer); - free(addressPointer); - free(viewKeyPointer); - free(spendKeyPointer); + calloc.free(pathPointer); + calloc.free(passwordPointer); + calloc.free(languagePointer); + calloc.free(addressPointer); + calloc.free(viewKeyPointer); + calloc.free(spendKeyPointer); if (!isWalletRestored) { throw WalletRestoreFromKeysException( @@ -138,12 +138,12 @@ void restoreWalletFromKeysSync( } } -void loadWallet({String path, String password, int nettype = 0}) { - final pathPointer = Utf8.toUtf8(path); - final passwordPointer = Utf8.toUtf8(password); +void loadWallet({required String path, required String password, int nettype = 0}) { + final pathPointer = path.toNativeUtf8(); + final passwordPointer = password.toNativeUtf8(); final loaded = loadWalletNative(pathPointer, passwordPointer, nettype) != 0; - free(pathPointer); - free(passwordPointer); + calloc.free(pathPointer); + calloc.free(passwordPointer); if (!loaded) { throw WalletOpeningException( @@ -189,20 +189,20 @@ void _restoreFromKeys(Map args) { } Future _openWallet(Map args) async => - loadWallet(path: args['path'], password: args['password']); + loadWallet(path: args['path'] as String, password: args['password'] as String); bool _isWalletExist(String path) => isWalletExistSync(path: path); -void openWallet({String path, String password, int nettype = 0}) async => +void openWallet({required String path, required String password, int nettype = 0}) async => loadWallet(path: path, password: password, nettype: nettype); Future openWalletAsync(Map args) async => compute(_openWallet, args); Future createWallet( - {String path, - String password, - String language, + {required String path, + required String password, + required String language, int nettype = 0}) async => compute(_createWallet, { 'path': path, @@ -211,10 +211,10 @@ Future createWallet( 'nettype': nettype }); -Future restoreFromSeed( - {String path, - String password, - String seed, +Future restoreFromSeed( + {required String path, + required String password, + required String seed, int nettype = 0, int restoreHeight = 0}) async => compute, void>(_restoreFromSeed, { @@ -225,13 +225,13 @@ Future restoreFromSeed( 'restoreHeight': restoreHeight }); -Future restoreFromKeys( - {String path, - String password, - String language, - String address, - String viewKey, - String spendKey, +Future restoreFromKeys( + {required String path, + required String password, + required String language, + required String address, + required String viewKey, + required String spendKey, int nettype = 0, int restoreHeight = 0}) async => compute, void>(_restoreFromKeys, { @@ -245,4 +245,4 @@ Future restoreFromKeys( 'restoreHeight': restoreHeight }); -Future isWalletExist({String path}) => compute(_isWalletExist, path); +Future isWalletExist({required String path}) => compute(_isWalletExist, path); diff --git a/cw_haven/lib/haven_account_list.dart b/cw_haven/lib/haven_account_list.dart index ad1cd2a7b..9399efc27 100644 --- a/cw_haven/lib/haven_account_list.dart +++ b/cw_haven/lib/haven_account_list.dart @@ -53,13 +53,13 @@ abstract class HavenAccountListBase extends AccountList with Store { .toList(); @override - Future addAccount({String label}) async { + Future addAccount({required String label}) async { await account_list.addAccount(label: label); update(); } @override - Future setLabelAccount({int accountIndex, String label}) async { + Future setLabelAccount({required int accountIndex, required String label}) async { await account_list.setLabelForAccount( accountIndex: accountIndex, label: label); update(); diff --git a/cw_haven/lib/haven_balance.dart b/cw_haven/lib/haven_balance.dart index 17172fa9c..7d257ded9 100644 --- a/cw_haven/lib/haven_balance.dart +++ b/cw_haven/lib/haven_balance.dart @@ -9,7 +9,7 @@ const inactiveBalances = [ CryptoCurrency.xnok, CryptoCurrency.xnzd]; -Map getHavenBalance({int accountIndex}) { +Map getHavenBalance({required int accountIndex}) { final fullBalances = getHavenFullBalance(accountIndex: accountIndex); final unlockedBalances = getHavenUnlockedBalance(accountIndex: accountIndex); final havenBalances = {}; diff --git a/cw_haven/lib/haven_subaddress_list.dart b/cw_haven/lib/haven_subaddress_list.dart index b6213d784..b40b3484c 100644 --- a/cw_haven/lib/haven_subaddress_list.dart +++ b/cw_haven/lib/haven_subaddress_list.dart @@ -10,11 +10,10 @@ class HavenSubaddressList = HavenSubaddressListBase with _$HavenSubaddressList; abstract class HavenSubaddressListBase with Store { - HavenSubaddressListBase() { - _isRefreshing = false; - _isUpdating = false; + HavenSubaddressListBase() + : _isRefreshing = false, + _isUpdating = false, subaddresses = ObservableList(); - } @observable ObservableList subaddresses; @@ -22,7 +21,7 @@ abstract class HavenSubaddressListBase with Store { bool _isRefreshing; bool _isUpdating; - void update({int accountIndex}) { + void update({required int accountIndex}) { if (_isUpdating) { return; } @@ -56,20 +55,20 @@ abstract class HavenSubaddressListBase with Store { .toList(); } - Future addSubaddress({int accountIndex, String label}) async { + Future addSubaddress({required int accountIndex, required String label}) async { await subaddress_list.addSubaddress( accountIndex: accountIndex, label: label); update(accountIndex: accountIndex); } - Future setLabelSubaddress( - {int accountIndex, int addressIndex, String label}) async { + Future setLabelSubaddress( + {required int accountIndex, required int addressIndex, required String label}) async { await subaddress_list.setLabelForSubaddress( accountIndex: accountIndex, addressIndex: addressIndex, label: label); update(accountIndex: accountIndex); } - void refresh({int accountIndex}) { + void refresh({required int accountIndex}) { if (_isRefreshing) { return; } diff --git a/cw_haven/lib/haven_transaction_creation_credentials.dart b/cw_haven/lib/haven_transaction_creation_credentials.dart index 88525f0fd..4de41a504 100644 --- a/cw_haven/lib/haven_transaction_creation_credentials.dart +++ b/cw_haven/lib/haven_transaction_creation_credentials.dart @@ -2,7 +2,10 @@ import 'package:cw_core/monero_transaction_priority.dart'; import 'package:cw_core/output_info.dart'; class HavenTransactionCreationCredentials { - HavenTransactionCreationCredentials({this.outputs, this.priority, this.assetType}); + HavenTransactionCreationCredentials({ + required this.outputs, + required this.priority, + required this.assetType}); final List outputs; final MoneroTransactionPriority priority; diff --git a/cw_haven/lib/haven_transaction_info.dart b/cw_haven/lib/haven_transaction_info.dart index 16f032541..277370467 100644 --- a/cw_haven/lib/haven_transaction_info.dart +++ b/cw_haven/lib/haven_transaction_info.dart @@ -10,20 +10,20 @@ class HavenTransactionInfo extends TransactionInfo { HavenTransactionInfo(this.id, this.height, this.direction, this.date, this.isPending, this.amount, this.accountIndex, this.addressIndex, this.fee); - HavenTransactionInfo.fromMap(Map map) + HavenTransactionInfo.fromMap(Map map) : id = (map['hash'] ?? '') as String, height = (map['height'] ?? 0) as int, direction = parseTransactionDirectionFromNumber(map['direction'] as String) ?? TransactionDirection.incoming, date = DateTime.fromMillisecondsSinceEpoch( - (int.parse(map['timestamp'] as String) ?? 0) * 1000), + int.parse(map['timestamp'] as String? ?? '0') * 1000), isPending = parseBoolFromString(map['isPending'] as String), amount = map['amount'] as int, accountIndex = int.parse(map['accountIndex'] as String), addressIndex = map['addressIndex'] as int, key = getTxKey((map['hash'] ?? '') as String), - fee = map['fee'] as int ?? 0; + fee = map['fee'] as int? ?? 0; HavenTransactionInfo.fromRow(TransactionInfoRow row) : id = row.getHash(), @@ -48,11 +48,10 @@ class HavenTransactionInfo extends TransactionInfo { final int amount; final int fee; final int addressIndex; - String recipientAddress; - String key; - String assetType; - - String _fiatAmount; + late String recipientAddress; + late String assetType; + String? _fiatAmount; + String? key; @override String amountFormatted() => diff --git a/cw_haven/lib/haven_wallet.dart b/cw_haven/lib/haven_wallet.dart index c107d2f52..0e33753d5 100644 --- a/cw_haven/lib/haven_wallet.dart +++ b/cw_haven/lib/haven_wallet.dart @@ -37,15 +37,19 @@ class HavenWallet = HavenWalletBase with _$HavenWallet; abstract class HavenWalletBase extends WalletBase with Store { - HavenWalletBase({WalletInfo walletInfo}) - : super(walletInfo) { + HavenWalletBase({required WalletInfo walletInfo}) + : balance = ObservableMap.of(getHavenBalance(accountIndex: 0)), + _isTransactionUpdating = false, + _hasSyncAfterStartup = false, + walletAddresses = HavenWalletAddresses(walletInfo), + syncStatus = NotConnectedSyncStatus(), + super(walletInfo) { transactionHistory = HavenTransactionHistory(); - balance = ObservableMap.of(getHavenBalance(accountIndex: 0)); - _isTransactionUpdating = false; - _hasSyncAfterStartup = false; - walletAddresses = HavenWalletAddresses(walletInfo); _onAccountChangeReaction = reaction((_) => walletAddresses.account, - (Account account) { + (Account? account) { + if (account == null) { + return; + } balance.addAll(getHavenBalance(accountIndex: account.id)); walletAddresses.updateSubaddressList(accountIndex: account.id); }); @@ -74,15 +78,15 @@ abstract class HavenWalletBase extends WalletBase init() async { await walletAddresses.init(); - balance.addAll(getHavenBalance(accountIndex: walletAddresses.account.id ?? 0)); + balance.addAll(getHavenBalance(accountIndex: walletAddresses.account?.id ?? 0)); _setListeners(); await updateTransactions(); @@ -103,19 +107,19 @@ abstract class HavenWalletBase extends WalletBase connectToNode({@required Node node}) async { + Future connectToNode({required Node node}) async { try { syncStatus = ConnectingSyncStatus(); await haven_wallet.setupNode( address: node.uriRaw, login: node.login, password: node.password, - useSSL: node.useSSL, + useSSL: node.useSSL ?? false, isLightWallet: false); // FIXME: hardcoded value syncStatus = ConnectedSyncStatus(); } catch (e) { @@ -148,8 +152,8 @@ abstract class HavenWalletBase extends WalletBase 1; final assetType = CryptoCurrency.fromString(_credentials.assetType.toLowerCase()); - final balances = getHavenBalance(accountIndex: walletAddresses.account.id); - final unlockedBalance = balances[assetType].unlockedBalance; + final balances = getHavenBalance(accountIndex: walletAddresses.account!.id); + final unlockedBalance = balances[assetType]!.unlockedBalance; PendingTransactionDescription pendingTransactionDescription; @@ -159,12 +163,12 @@ abstract class HavenWalletBase extends WalletBase item.sendAll - || item.formattedCryptoAmount <= 0)) { + || (item.formattedCryptoAmount ?? 0) <= 0)) { throw HavenTransactionCreationException('Wrong balance. Not enough XMR on your balance.'); } final int totalAmount = outputs.fold(0, (acc, value) => - acc + value.formattedCryptoAmount); + acc + (value.formattedCryptoAmount ?? 0)); if (unlockedBalance < totalAmount) { throw HavenTransactionCreationException('Wrong balance. Not enough XMR on your balance.'); @@ -173,21 +177,21 @@ abstract class HavenWalletBase extends WalletBase MoneroOutput( address: output.address, - amount: output.cryptoAmount.replaceAll(',', '.'))) + amount: output.cryptoAmount!.replaceAll(',', '.'))) .toList(); pendingTransactionDescription = await transaction_history.createTransactionMultDest( outputs: moneroOutputs, priorityRaw: _credentials.priority.serialize(), - accountIndex: walletAddresses.account.id); + accountIndex: walletAddresses.account!.id); } else { final output = outputs.first; final address = output.address; final amount = output.sendAll ? null - : output.cryptoAmount.replaceAll(',', '.'); - final formattedAmount = output.sendAll + : output.cryptoAmount!.replaceAll(',', '.'); + final int? formattedAmount = output.sendAll ? null : output.formattedCryptoAmount; @@ -205,14 +209,14 @@ abstract class HavenWalletBase extends WalletBase rescan({int height}) async { + Future rescan({required int height}) async { walletInfo.restoreHeight = height; walletInfo.isRecovery = true; haven_wallet.setRefreshFromBlockHeight(height: height); @@ -346,7 +350,7 @@ abstract class HavenWalletBase extends WalletBase - balance.addAll(getHavenBalance(accountIndex: walletAddresses.account.id)); + balance.addAll(getHavenBalance(accountIndex: walletAddresses.account!.id)); Future _askForUpdateTransactionHistory() async => await updateTransactions(); diff --git a/cw_haven/lib/haven_wallet_addresses.dart b/cw_haven/lib/haven_wallet_addresses.dart index 7afad6205..ff6b6aa3f 100644 --- a/cw_haven/lib/haven_wallet_addresses.dart +++ b/cw_haven/lib/haven_wallet_addresses.dart @@ -12,21 +12,22 @@ class HavenWalletAddresses = HavenWalletAddressesBase with _$HavenWalletAddresses; abstract class HavenWalletAddressesBase extends WalletAddressesWithAccount with Store { - HavenWalletAddressesBase(WalletInfo walletInfo) : super(walletInfo) { - accountList = HavenAccountList(); - subaddressList = HavenSubaddressList(); - } + HavenWalletAddressesBase(WalletInfo walletInfo) + : accountList = HavenAccountList(), + subaddressList = HavenSubaddressList(), + address = '', + super(walletInfo); @override @observable String address; - @override + // @override @observable - Account account; + Account? account; @observable - Subaddress subaddress; + Subaddress? subaddress; HavenSubaddressList subaddressList; @@ -36,7 +37,7 @@ abstract class HavenWalletAddressesBase extends WalletAddressesWithAccount init() async { accountList.update(); account = accountList.accounts.first; - updateSubaddressList(accountIndex: account.id ?? 0); + updateSubaddressList(accountIndex: account?.id ?? 0); await updateAddressesInBox(); } @@ -62,14 +63,14 @@ abstract class HavenWalletAddressesBase extends WalletAddressesWithAccount info.id == WalletBase.idFor(name, getType()), - orElse: () => null); + final walletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(name, getType()))!; final wallet = HavenWallet(walletInfo: walletInfo); final isValid = wallet.walletAddresses.validate(); @@ -155,13 +158,13 @@ class HavenWalletService extends WalletService< final path = await pathForWallet(name: credentials.name, type: getType()); await haven_wallet_manager.restoreFromKeys( path: path, - password: credentials.password, + password: credentials.password!, language: credentials.language, - restoreHeight: credentials.height, + restoreHeight: credentials.height!, address: credentials.address, viewKey: credentials.viewKey, spendKey: credentials.spendKey); - final wallet = HavenWallet(walletInfo: credentials.walletInfo); + final wallet = HavenWallet(walletInfo: credentials.walletInfo!); await wallet.init(); return wallet; @@ -179,10 +182,10 @@ class HavenWalletService extends WalletService< final path = await pathForWallet(name: credentials.name, type: getType()); await haven_wallet_manager.restoreFromSeed( path: path, - password: credentials.password, + password: credentials.password!, seed: credentials.mnemonic, - restoreHeight: credentials.height); - final wallet = HavenWallet(walletInfo: credentials.walletInfo); + restoreHeight: credentials.height!); + final wallet = HavenWallet(walletInfo: credentials.walletInfo!); await wallet.init(); return wallet; diff --git a/cw_haven/lib/pending_haven_transaction.dart b/cw_haven/lib/pending_haven_transaction.dart index d56b5096c..b362cdf34 100644 --- a/cw_haven/lib/pending_haven_transaction.dart +++ b/cw_haven/lib/pending_haven_transaction.dart @@ -2,7 +2,7 @@ import 'package:cw_haven/api/structs/pending_transaction.dart'; import 'package:cw_haven/api/transaction_history.dart' as haven_transaction_history; import 'package:cw_core/crypto_currency.dart'; -import 'package:cake_wallet/core/amount_converter.dart'; +// import 'package:cake_wallet/core/amount_converter.dart'; import 'package:cw_core/pending_transaction.dart'; class DoubleSpendException implements Exception { @@ -25,13 +25,17 @@ class PendingHavenTransaction with PendingTransaction { @override String get hex => ''; + // FIX-ME: AmountConverter @override - String get amountFormatted => AmountConverter.amountIntToString( - cryptoCurrency, pendingTransactionDescription.amount); + String get amountFormatted => ''; + // AmountConverter.amountIntToString( + // cryptoCurrency, pendingTransactionDescription.amount); + // FIX-ME: AmountConverter @override - String get feeFormatted => AmountConverter.amountIntToString( - cryptoCurrency, pendingTransactionDescription.fee); + String get feeFormatted => ''; + // AmountConverter.amountIntToString( + // cryptoCurrency, pendingTransactionDescription.fee); @override Future commit() async { diff --git a/cw_haven/lib/update_haven_rate.dart b/cw_haven/lib/update_haven_rate.dart index 967247af5..a7d5b2c77 100644 --- a/cw_haven/lib/update_haven_rate.dart +++ b/cw_haven/lib/update_haven_rate.dart @@ -1,7 +1,7 @@ //import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/monero_amount_format.dart'; -import 'package:cw_haven/balance_list.dart'; +import 'package:cw_haven/api/balance_list.dart'; //Future updateHavenRate(FiatConversionStore fiatConversionStore) async { // final rate = getRate(); diff --git a/cw_haven/pubspec.lock b/cw_haven/pubspec.lock index f9b6c4890..6d741c268 100644 --- a/cw_haven/pubspec.lock +++ b/cw_haven/pubspec.lock @@ -7,35 +7,35 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "14.0.0" + version: "47.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.41.2" + version: "4.7.0" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.6.0" + version: "2.3.1" asn1lib: dependency: transitive description: name: asn1lib url: "https://pub.dartlang.org" source: hosted - version: "0.8.1" + version: "1.1.1" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -49,42 +49,42 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" + version: "2.3.1" build_config: dependency: transitive description: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "0.4.6" + version: "1.1.0" build_daemon: dependency: transitive description: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "2.1.10" + version: "3.1.0" build_resolvers: dependency: "direct dev" description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "1.5.3" + version: "2.0.10" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.11.5" + version: "2.2.1" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "6.1.10" + version: "7.2.4" built_collection: dependency: transitive description: @@ -105,63 +105,49 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" + version: "1.2.1" checked_yaml: dependency: transitive description: name: checked_yaml url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" - cli_util: - dependency: transitive - description: - name: cli_util - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.5" + version: "2.0.1" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" code_builder: dependency: transitive description: name: code_builder url: "https://pub.dartlang.org" source: hosted - version: "3.7.0" + version: "4.3.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" convert: dependency: transitive description: name: convert url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "3.0.2" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "3.0.2" cw_core: dependency: "direct main" description: @@ -175,35 +161,28 @@ packages: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.3.12" - dartx: - dependency: transitive - description: - name: dartx - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.0" + version: "2.2.4" encrypt: dependency: transitive description: name: encrypt url: "https://pub.dartlang.org" source: hosted - version: "4.1.0" + version: "5.0.1" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" ffi: dependency: "direct main" description: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "1.2.1" file: dependency: transitive description: @@ -229,12 +208,19 @@ packages: name: flutter_mobx url: "https://pub.dartlang.org" source: hosted - version: "1.1.0+2" + version: "2.0.6+4" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" glob: dependency: transitive description: @@ -248,42 +234,42 @@ packages: name: graphs url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "2.1.0" hive: dependency: transitive description: name: hive url: "https://pub.dartlang.org" source: hosted - version: "1.4.4+1" + version: "2.2.3" hive_generator: dependency: "direct dev" description: name: hive_generator url: "https://pub.dartlang.org" source: hosted - version: "0.8.2" + version: "1.1.3" http: dependency: "direct main" description: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.2" + version: "0.13.5" http_multi_server: dependency: transitive description: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "3.2.1" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" + version: "4.0.1" intl: dependency: "direct main" description: @@ -297,7 +283,7 @@ packages: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.5" + version: "1.0.3" js: dependency: transitive description: @@ -311,7 +297,7 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "4.0.1" + version: "4.6.0" logging: dependency: transitive description: @@ -325,14 +311,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10" + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.8.0" mime: dependency: transitive description: @@ -346,63 +339,77 @@ packages: name: mobx url: "https://pub.dartlang.org" source: hosted - version: "1.2.1+4" + version: "2.1.0" mobx_codegen: dependency: "direct dev" description: name: mobx_codegen url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "2.0.7+3" package_config: dependency: transitive description: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "2.1.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.2" path_provider: dependency: "direct main" description: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.6.28" + version: "2.0.11" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.20" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" path_provider_linux: dependency: transitive description: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+2" + version: "2.1.7" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+8" + version: "2.0.6" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.4" path_provider_windows: dependency: transitive description: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+3" + version: "2.0.7" pedantic: dependency: transitive description: @@ -423,14 +430,14 @@ packages: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.1.3" pointycastle: dependency: transitive description: name: pointycastle url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "3.6.2" pool: dependency: transitive description: @@ -458,21 +465,21 @@ packages: name: pubspec_parse url: "https://pub.dartlang.org" source: hosted - version: "0.1.8" + version: "1.2.1" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "0.7.9" + version: "1.3.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.4+1" + version: "1.0.2" sky_engine: dependency: transitive description: flutter @@ -484,14 +491,21 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "0.9.10+3" + version: "1.2.3" + source_helper: + dependency: transitive + description: + name: source_helper + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.3" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -519,35 +533,28 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19" - time: - dependency: transitive - description: - name: time - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.1" + version: "0.4.12" timing: dependency: transitive description: name: timing url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+3" + version: "1.0.0" typed_data: dependency: transitive description: @@ -561,7 +568,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.2" watcher: dependency: transitive description: @@ -575,21 +582,21 @@ packages: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.2.0" win32: dependency: transitive description: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "1.7.4+1" + version: "2.6.1" xdg_directories: dependency: transitive description: name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "0.1.2" + version: "0.2.0+2" yaml: dependency: transitive description: @@ -598,5 +605,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.12.0 <3.0.0" - flutter: ">=1.20.0" + dart: ">=2.17.5 <3.0.0" + flutter: ">=2.8.1" diff --git a/cw_haven/pubspec.yaml b/cw_haven/pubspec.yaml index a8d8417be..28f2c315e 100644 --- a/cw_haven/pubspec.yaml +++ b/cw_haven/pubspec.yaml @@ -6,17 +6,17 @@ author: Cake Wallet homepage: https://cakewallet.com environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.17.5 <3.0.0" flutter: ">=1.20.0" dependencies: flutter: sdk: flutter - ffi: ^0.1.3 - path_provider: ^1.4.0 - http: ^0.12.0+2 - mobx: ^1.2.1+2 - flutter_mobx: ^1.1.0+2 + ffi: ^1.1.2 + http: ^0.13.4 + path_provider: ^2.0.11 + mobx: ^2.0.7+4 + flutter_mobx: ^2.0.6+1 intl: ^0.17.0 cw_core: path: ../cw_core @@ -24,10 +24,10 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - build_runner: ^1.10.3 - build_resolvers: ^1.3.10 - mobx_codegen: ^1.1.0+1 - hive_generator: ^0.8.1 + build_runner: ^2.1.11 + mobx_codegen: ^2.0.7 + build_resolvers: ^2.0.9 + hive_generator: ^1.1.3 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/cw_monero/lib/api/account_list.dart b/cw_monero/lib/api/account_list.dart index b8f717d0f..451ba5033 100644 --- a/cw_monero/lib/api/account_list.dart +++ b/cw_monero/lib/api/account_list.dart @@ -50,16 +50,16 @@ List getAllAccount() { .toList(); } -void addAccountSync({String label}) { - final labelPointer = Utf8.toUtf8(label); +void addAccountSync({required String label}) { + final labelPointer = label.toNativeUtf8(); accountAddNewNative(labelPointer); - free(labelPointer); + calloc.free(labelPointer); } -void setLabelForAccountSync({int accountIndex, String label}) { - final labelPointer = Utf8.toUtf8(label); +void setLabelForAccountSync({required int accountIndex, required String label}) { + final labelPointer = label.toNativeUtf8(); accountSetLabelNative(accountIndex, labelPointer); - free(labelPointer); + calloc.free(labelPointer); } void _addAccount(String label) => addAccountSync(label: label); @@ -71,12 +71,12 @@ void _setLabelForAccount(Map args) { setLabelForAccountSync(label: label, accountIndex: accountIndex); } -Future addAccount({String label}) async { +Future addAccount({required String label}) async { await compute(_addAccount, label); await store(); } -Future setLabelForAccount({int accountIndex, String label}) async { +Future setLabelForAccount({required int accountIndex, required String label}) async { await compute( _setLabelForAccount, {'accountIndex': accountIndex, 'label': label}); await store(); diff --git a/cw_monero/lib/api/convert_utf8_to_string.dart b/cw_monero/lib/api/convert_utf8_to_string.dart index 7fa5a68df..41a6b648a 100644 --- a/cw_monero/lib/api/convert_utf8_to_string.dart +++ b/cw_monero/lib/api/convert_utf8_to_string.dart @@ -1,8 +1,8 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; -String convertUTF8ToString({Pointer pointer}) { - final str = Utf8.fromUtf8(pointer); - free(pointer); +String convertUTF8ToString({required Pointer pointer}) { + final str = pointer.toDartString(); + calloc.free(pointer); return str; } \ No newline at end of file diff --git a/cw_monero/lib/api/exceptions/connection_to_node_exception.dart b/cw_monero/lib/api/exceptions/connection_to_node_exception.dart index 6ee272b89..483b0a174 100644 --- a/cw_monero/lib/api/exceptions/connection_to_node_exception.dart +++ b/cw_monero/lib/api/exceptions/connection_to_node_exception.dart @@ -1,5 +1,5 @@ class ConnectionToNodeException implements Exception { - ConnectionToNodeException({this.message}); + ConnectionToNodeException({required this.message}); final String message; } \ No newline at end of file diff --git a/cw_monero/lib/api/exceptions/creation_transaction_exception.dart b/cw_monero/lib/api/exceptions/creation_transaction_exception.dart index bb477d673..7b55ec074 100644 --- a/cw_monero/lib/api/exceptions/creation_transaction_exception.dart +++ b/cw_monero/lib/api/exceptions/creation_transaction_exception.dart @@ -1,5 +1,5 @@ class CreationTransactionException implements Exception { - CreationTransactionException({this.message}); + CreationTransactionException({required this.message}); final String message; diff --git a/cw_monero/lib/api/exceptions/setup_wallet_exception.dart b/cw_monero/lib/api/exceptions/setup_wallet_exception.dart index ce43c0ec6..b6e0c1f18 100644 --- a/cw_monero/lib/api/exceptions/setup_wallet_exception.dart +++ b/cw_monero/lib/api/exceptions/setup_wallet_exception.dart @@ -1,5 +1,5 @@ class SetupWalletException implements Exception { - SetupWalletException({this.message}); + SetupWalletException({required this.message}); final String message; } \ No newline at end of file diff --git a/cw_monero/lib/api/exceptions/wallet_creation_exception.dart b/cw_monero/lib/api/exceptions/wallet_creation_exception.dart index 6b00445ad..6052366b9 100644 --- a/cw_monero/lib/api/exceptions/wallet_creation_exception.dart +++ b/cw_monero/lib/api/exceptions/wallet_creation_exception.dart @@ -1,5 +1,5 @@ class WalletCreationException implements Exception { - WalletCreationException({this.message}); + WalletCreationException({required this.message}); final String message; diff --git a/cw_monero/lib/api/exceptions/wallet_opening_exception.dart b/cw_monero/lib/api/exceptions/wallet_opening_exception.dart index 8d84b0f7e..df7a850a4 100644 --- a/cw_monero/lib/api/exceptions/wallet_opening_exception.dart +++ b/cw_monero/lib/api/exceptions/wallet_opening_exception.dart @@ -1,5 +1,5 @@ class WalletOpeningException implements Exception { - WalletOpeningException({this.message}); + WalletOpeningException({required this.message}); final String message; diff --git a/cw_monero/lib/api/exceptions/wallet_restore_from_keys_exception.dart b/cw_monero/lib/api/exceptions/wallet_restore_from_keys_exception.dart index 5f08437d4..c6b6c6ef7 100644 --- a/cw_monero/lib/api/exceptions/wallet_restore_from_keys_exception.dart +++ b/cw_monero/lib/api/exceptions/wallet_restore_from_keys_exception.dart @@ -1,5 +1,5 @@ class WalletRestoreFromKeysException implements Exception { - WalletRestoreFromKeysException({this.message}); + WalletRestoreFromKeysException({required this.message}); final String message; } \ No newline at end of file diff --git a/cw_monero/lib/api/exceptions/wallet_restore_from_seed_exception.dart b/cw_monero/lib/api/exceptions/wallet_restore_from_seed_exception.dart index fd89e4161..004cd7958 100644 --- a/cw_monero/lib/api/exceptions/wallet_restore_from_seed_exception.dart +++ b/cw_monero/lib/api/exceptions/wallet_restore_from_seed_exception.dart @@ -1,5 +1,5 @@ class WalletRestoreFromSeedException implements Exception { - WalletRestoreFromSeedException({this.message}); + WalletRestoreFromSeedException({required this.message}); final String message; } \ No newline at end of file diff --git a/cw_monero/lib/api/monero_output.dart b/cw_monero/lib/api/monero_output.dart index 831ee1f22..5b468cb48 100644 --- a/cw_monero/lib/api/monero_output.dart +++ b/cw_monero/lib/api/monero_output.dart @@ -1,7 +1,5 @@ -import 'package:flutter/foundation.dart'; - class MoneroOutput { - MoneroOutput({@required this.address, @required this.amount}); + MoneroOutput({required this.address, required this.amount}); final String address; final String amount; diff --git a/cw_monero/lib/api/signatures.dart b/cw_monero/lib/api/signatures.dart index 16f983480..f9e88153e 100644 --- a/cw_monero/lib/api/signatures.dart +++ b/cw_monero/lib/api/signatures.dart @@ -35,7 +35,7 @@ typedef get_node_height = Int64 Function(); typedef is_connected = Int8 Function(); typedef setup_node = Int8 Function( - Pointer, Pointer, Pointer, Int8, Int8, Pointer); + Pointer, Pointer?, Pointer?, Int8, Int8, Pointer); typedef start_refresh = Void Function(); @@ -82,7 +82,7 @@ typedef account_set_label = Void Function( typedef transactions_refresh = Void Function(); -typedef get_tx_key = Pointer Function(Pointer txId); +typedef get_tx_key = Pointer? Function(Pointer txId); typedef transactions_count = Int64 Function(); diff --git a/cw_monero/lib/api/structs/account_row.dart b/cw_monero/lib/api/structs/account_row.dart index c3fc22de1..aa492ee0f 100644 --- a/cw_monero/lib/api/structs/account_row.dart +++ b/cw_monero/lib/api/structs/account_row.dart @@ -3,9 +3,10 @@ import 'package:ffi/ffi.dart'; class AccountRow extends Struct { @Int64() - int id; - Pointer label; + external int id; + + external Pointer label; - String getLabel() => Utf8.fromUtf8(label); + String getLabel() => label.toDartString(); int getId() => id; } diff --git a/cw_monero/lib/api/structs/pending_transaction.dart b/cw_monero/lib/api/structs/pending_transaction.dart index edbd2d0ff..656ed333f 100644 --- a/cw_monero/lib/api/structs/pending_transaction.dart +++ b/cw_monero/lib/api/structs/pending_transaction.dart @@ -3,32 +3,32 @@ import 'package:ffi/ffi.dart'; class PendingTransactionRaw extends Struct { @Int64() - int amount; + external int amount; @Int64() - int fee; + external int fee; - Pointer hash; + external Pointer hash; - Pointer hex; + external Pointer hex; - Pointer txKey; + external Pointer txKey; - String getHash() => Utf8.fromUtf8(hash); + String getHash() => hash.toDartString(); - String getHex() => Utf8.fromUtf8(hex); + String getHex() => hex.toDartString(); - String getKey() => Utf8.fromUtf8(txKey); + String getKey() => txKey.toDartString(); } class PendingTransactionDescription { PendingTransactionDescription({ - this.amount, - this.fee, - this.hash, - this.hex, - this.txKey, - this.pointerAddress}); + required this.amount, + required this.fee, + required this.hash, + required this.hex, + required this.txKey, + required this.pointerAddress}); final int amount; final int fee; diff --git a/cw_monero/lib/api/structs/subaddress_row.dart b/cw_monero/lib/api/structs/subaddress_row.dart index 1673e00c7..d593a793d 100644 --- a/cw_monero/lib/api/structs/subaddress_row.dart +++ b/cw_monero/lib/api/structs/subaddress_row.dart @@ -3,11 +3,13 @@ import 'package:ffi/ffi.dart'; class SubaddressRow extends Struct { @Int64() - int id; - Pointer address; - Pointer label; + external int id; + + external Pointer address; + + external Pointer label; - String getLabel() => Utf8.fromUtf8(label); - String getAddress() => Utf8.fromUtf8(address); + String getLabel() => label.toDartString(); + String getAddress() => address.toDartString(); int getId() => id; } \ No newline at end of file diff --git a/cw_monero/lib/api/structs/transaction_info_row.dart b/cw_monero/lib/api/structs/transaction_info_row.dart index 37b0d02e8..bdcc64d3f 100644 --- a/cw_monero/lib/api/structs/transaction_info_row.dart +++ b/cw_monero/lib/api/structs/transaction_info_row.dart @@ -3,39 +3,39 @@ import 'package:ffi/ffi.dart'; class TransactionInfoRow extends Struct { @Uint64() - int amount; + external int amount; @Uint64() - int fee; + external int fee; @Uint64() - int blockHeight; + external int blockHeight; @Uint64() - int confirmations; + external int confirmations; @Uint32() - int subaddrAccount; + external int subaddrAccount; @Int8() - int direction; + external int direction; @Int8() - int isPending; + external int isPending; @Uint32() - int subaddrIndex; + external int subaddrIndex; - Pointer hash; + external Pointer hash; - Pointer paymentId; + external Pointer paymentId; @Int64() - int datetime; + external int datetime; int getDatetime() => datetime; int getAmount() => amount >= 0 ? amount : amount * -1; bool getIsPending() => isPending != 0; - String getHash() => Utf8.fromUtf8(hash); - String getPaymentId() => Utf8.fromUtf8(paymentId); + String getHash() => hash.toDartString(); + String getPaymentId() => paymentId.toDartString(); } diff --git a/cw_monero/lib/api/structs/ut8_box.dart b/cw_monero/lib/api/structs/ut8_box.dart index a6f41bc75..53e678c88 100644 --- a/cw_monero/lib/api/structs/ut8_box.dart +++ b/cw_monero/lib/api/structs/ut8_box.dart @@ -2,7 +2,7 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; class Utf8Box extends Struct { - Pointer value; + external Pointer value; - String getValue() => Utf8.fromUtf8(value); + String getValue() => value.toDartString(); } diff --git a/cw_monero/lib/api/subaddress_list.dart b/cw_monero/lib/api/subaddress_list.dart index 88acb743a..1c1f1253f 100644 --- a/cw_monero/lib/api/subaddress_list.dart +++ b/cw_monero/lib/api/subaddress_list.dart @@ -29,7 +29,7 @@ final subaddrressSetLabelNative = moneroApi bool isUpdating = false; -void refreshSubaddresses({@required int accountIndex}) { +void refreshSubaddresses({required int accountIndex}) { try { isUpdating = true; subaddressRefreshNative(accountIndex); @@ -50,18 +50,18 @@ List getAllSubaddresses() { .toList(); } -void addSubaddressSync({int accountIndex, String label}) { - final labelPointer = Utf8.toUtf8(label); +void addSubaddressSync({required int accountIndex, required String label}) { + final labelPointer = label.toNativeUtf8(); subaddrressAddNewNative(accountIndex, labelPointer); - free(labelPointer); + calloc.free(labelPointer); } void setLabelForSubaddressSync( - {int accountIndex, int addressIndex, String label}) { - final labelPointer = Utf8.toUtf8(label); + {required int accountIndex, required int addressIndex, required String label}) { + final labelPointer = label.toNativeUtf8(); subaddrressSetLabelNative(accountIndex, addressIndex, labelPointer); - free(labelPointer); + calloc.free(labelPointer); } void _addSubaddress(Map args) { @@ -80,14 +80,14 @@ void _setLabelForSubaddress(Map args) { accountIndex: accountIndex, addressIndex: addressIndex, label: label); } -Future addSubaddress({int accountIndex, String label}) async { +Future addSubaddress({required int accountIndex, required String label}) async { await compute, void>( _addSubaddress, {'accountIndex': accountIndex, 'label': label}); await store(); } -Future setLabelForSubaddress( - {int accountIndex, int addressIndex, String label}) async { +Future setLabelForSubaddress( + {required int accountIndex, required int addressIndex, required String label}) async { await compute, void>(_setLabelForSubaddress, { 'accountIndex': accountIndex, 'addressIndex': addressIndex, diff --git a/cw_monero/lib/api/transaction_history.dart b/cw_monero/lib/api/transaction_history.dart index 9546a93d3..e9e161f42 100644 --- a/cw_monero/lib/api/transaction_history.dart +++ b/cw_monero/lib/api/transaction_history.dart @@ -40,16 +40,16 @@ final getTxKeyNative = moneroApi .asFunction(); String getTxKey(String txId) { - final txIdPointer = Utf8.toUtf8(txId); + final txIdPointer = txId.toNativeUtf8(); final keyPointer = getTxKeyNative(txIdPointer); - free(txIdPointer); + calloc.free(txIdPointer); if (keyPointer != null) { return convertUTF8ToString(pointer: keyPointer); } - return null; + return ''; } void refreshTransactions() => transactionsRefreshNative(); @@ -67,16 +67,16 @@ List getAllTransations() { } PendingTransactionDescription createTransactionSync( - {String address, - String paymentId, - String amount, - int priorityRaw, + {required String address, + required String paymentId, + required int priorityRaw, + String? amount, int accountIndex = 0}) { - final addressPointer = Utf8.toUtf8(address); - final paymentIdPointer = Utf8.toUtf8(paymentId); - final amountPointer = amount != null ? Utf8.toUtf8(amount) : nullptr; - final errorMessagePointer = allocate(); - final pendingTransactionRawPointer = allocate(); + final addressPointer = address.toNativeUtf8(); + final paymentIdPointer = paymentId.toNativeUtf8(); + final amountPointer = amount != null ? amount.toNativeUtf8() : nullptr; + final errorMessagePointer = calloc(); + final pendingTransactionRawPointer = calloc(); final created = transactionCreateNative( addressPointer, paymentIdPointer, @@ -87,16 +87,16 @@ PendingTransactionDescription createTransactionSync( pendingTransactionRawPointer) != 0; - free(addressPointer); - free(paymentIdPointer); + calloc.free(addressPointer); + calloc.free(paymentIdPointer); if (amountPointer != nullptr) { - free(amountPointer); + calloc.free(amountPointer); } if (!created) { final message = errorMessagePointer.ref.getValue(); - free(errorMessagePointer); + calloc.free(errorMessagePointer); throw CreationTransactionException(message: message); } @@ -110,26 +110,26 @@ PendingTransactionDescription createTransactionSync( } PendingTransactionDescription createTransactionMultDestSync( - {List outputs, - String paymentId, - int priorityRaw, + {required List outputs, + required String paymentId, + required int priorityRaw, int accountIndex = 0}) { final int size = outputs.length; final List> addressesPointers = outputs.map((output) => - Utf8.toUtf8(output.address)).toList(); - final Pointer> addressesPointerPointer = allocate(count: size); + output.address.toNativeUtf8()).toList(); + final Pointer> addressesPointerPointer = calloc(size); final List> amountsPointers = outputs.map((output) => - Utf8.toUtf8(output.amount)).toList(); - final Pointer> amountsPointerPointer = allocate(count: size); + output.amount.toNativeUtf8()).toList(); + final Pointer> amountsPointerPointer = calloc(size); for (int i = 0; i < size; i++) { addressesPointerPointer[i] = addressesPointers[i]; amountsPointerPointer[i] = amountsPointers[i]; } - final paymentIdPointer = Utf8.toUtf8(paymentId); - final errorMessagePointer = allocate(); - final pendingTransactionRawPointer = allocate(); + final paymentIdPointer = paymentId.toNativeUtf8(); + final errorMessagePointer = calloc(); + final pendingTransactionRawPointer = calloc(); final created = transactionCreateMultDestNative( addressesPointerPointer, paymentIdPointer, @@ -141,17 +141,17 @@ PendingTransactionDescription createTransactionMultDestSync( pendingTransactionRawPointer) != 0; - free(addressesPointerPointer); - free(amountsPointerPointer); + calloc.free(addressesPointerPointer); + calloc.free(amountsPointerPointer); - addressesPointers.forEach((element) => free(element)); - amountsPointers.forEach((element) => free(element)); + addressesPointers.forEach((element) => calloc.free(element)); + amountsPointers.forEach((element) => calloc.free(element)); - free(paymentIdPointer); + calloc.free(paymentIdPointer); if (!created) { final message = errorMessagePointer.ref.getValue(); - free(errorMessagePointer); + calloc.free(errorMessagePointer); throw CreationTransactionException(message: message); } @@ -164,17 +164,17 @@ PendingTransactionDescription createTransactionMultDestSync( pointerAddress: pendingTransactionRawPointer.address); } -void commitTransactionFromPointerAddress({int address}) => commitTransaction( +void commitTransactionFromPointerAddress({required int address}) => commitTransaction( transactionPointer: Pointer.fromAddress(address)); -void commitTransaction({Pointer transactionPointer}) { - final errorMessagePointer = allocate(); +void commitTransaction({required Pointer transactionPointer}) { + final errorMessagePointer = calloc(); final isCommited = transactionCommitNative(transactionPointer, errorMessagePointer) != 0; if (!isCommited) { final message = errorMessagePointer.ref.getValue(); - free(errorMessagePointer); + calloc.free(errorMessagePointer); throw CreationTransactionException(message: message); } } @@ -208,10 +208,10 @@ PendingTransactionDescription _createTransactionMultDestSync(Map args) { } Future createTransaction( - {String address, + {required String address, + required int priorityRaw, + String? amount, String paymentId = '', - String amount, - int priorityRaw, int accountIndex = 0}) => compute(_createTransactionSync, { 'address': address, @@ -222,9 +222,9 @@ Future createTransaction( }); Future createTransactionMultDest( - {List outputs, + {required List outputs, + required int priorityRaw, String paymentId = '', - int priorityRaw, int accountIndex = 0}) => compute(_createTransactionMultDestSync, { 'outputs': outputs, diff --git a/cw_monero/lib/api/types.dart b/cw_monero/lib/api/types.dart index 3438b89fc..468ce93e2 100644 --- a/cw_monero/lib/api/types.dart +++ b/cw_monero/lib/api/types.dart @@ -35,7 +35,7 @@ typedef GetNodeHeight = int Function(); typedef IsConnected = int Function(); typedef SetupNode = int Function( - Pointer, Pointer, Pointer, int, int, Pointer); + Pointer, Pointer?, Pointer?, int, int, Pointer); typedef StartRefresh = void Function(); @@ -80,7 +80,7 @@ typedef AccountSetLabel = void Function(int accountIndex, Pointer label); typedef TransactionsRefresh = void Function(); -typedef GetTxKey = Pointer Function(Pointer txId); +typedef GetTxKey = Pointer? Function(Pointer txId); typedef TransactionsCount = int Function(); diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 9e84d7865..5c2f1bd27 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -146,24 +146,24 @@ int getNodeHeightSync() => getNodeHeightNative(); bool isConnectedSync() => isConnectedNative() != 0; bool setupNodeSync( - {String address, - String login, - String password, + {required String address, + String? login, + String? password, bool useSSL = false, bool isLightWallet = false}) { - final addressPointer = Utf8.toUtf8(address); - Pointer loginPointer; - Pointer passwordPointer; + final addressPointer = address.toNativeUtf8(); + Pointer? loginPointer; + Pointer? passwordPointer; if (login != null) { - loginPointer = Utf8.toUtf8(login); + loginPointer = login.toNativeUtf8(); } if (password != null) { - passwordPointer = Utf8.toUtf8(password); + passwordPointer = password.toNativeUtf8(); } - final errorMessagePointer = allocate(); + final errorMessagePointer = ''.toNativeUtf8(); final isSetupNode = setupNodeNative( addressPointer, loginPointer, @@ -173,9 +173,15 @@ bool setupNodeSync( errorMessagePointer) != 0; - free(addressPointer); - free(loginPointer); - free(passwordPointer); + calloc.free(addressPointer); + + if (loginPointer != null) { + calloc.free(loginPointer); + } + + if (passwordPointer != null) { + calloc.free(passwordPointer); + } if (!isSetupNode) { throw SetupWalletException( @@ -189,31 +195,31 @@ void startRefreshSync() => startRefreshNative(); Future connectToNode() async => connecToNodeNative() != 0; -void setRefreshFromBlockHeight({int height}) => +void setRefreshFromBlockHeight({required int height}) => setRefreshFromBlockHeightNative(height); -void setRecoveringFromSeed({bool isRecovery}) => +void setRecoveringFromSeed({required bool isRecovery}) => setRecoveringFromSeedNative(_boolToInt(isRecovery)); void storeSync() { - final pathPointer = Utf8.toUtf8(''); + final pathPointer = ''.toNativeUtf8(); storeNative(pathPointer); - free(pathPointer); + calloc.free(pathPointer); } void setPasswordSync(String password) { - final passwordPointer = Utf8.toUtf8(password); - final errorMessagePointer = allocate(); + final passwordPointer = password.toNativeUtf8(); + final errorMessagePointer = calloc(); final changed = setPasswordNative(passwordPointer, errorMessagePointer) != 0; - free(passwordPointer); + calloc.free(passwordPointer); if (!changed) { final message = errorMessagePointer.ref.getValue(); - free(errorMessagePointer); + calloc.free(errorMessagePointer); throw Exception(message); } - free(errorMessagePointer); + calloc.free(errorMessagePointer); } void closeCurrentWallet() => closeCurrentWalletNative(); @@ -231,16 +237,16 @@ String getPublicSpendKey() => convertUTF8ToString(pointer: getPublicSpendKeyNative()); class SyncListener { - SyncListener(this.onNewBlock, this.onNewTransaction) { - _cachedBlockchainHeight = 0; - _lastKnownBlockHeight = 0; + SyncListener(this.onNewBlock, this.onNewTransaction) + : _cachedBlockchainHeight = 0, + _lastKnownBlockHeight = 0, _initialSyncHeight = 0; - } + void Function(int, int, double) onNewBlock; void Function() onNewTransaction; - Timer _updateSyncInfoTimer; + Timer? _updateSyncInfoTimer; int _cachedBlockchainHeight; int _lastKnownBlockHeight; int _initialSyncHeight; @@ -260,7 +266,7 @@ class SyncListener { _updateSyncInfoTimer ??= Timer.periodic(Duration(milliseconds: 1200), (_) async { if (isNewTransactionExist()) { - onNewTransaction?.call(); + onNewTransaction(); } var syncHeight = getSyncingHeight(); @@ -308,7 +314,7 @@ void onStartup() => onStartupNative(); void _storeSync(Object _) => storeSync(); -bool _setupNodeSync(Map args) { +bool _setupNodeSync(Map args) { final address = args['address'] as String; final login = (args['login'] ?? '') as String; final password = (args['password'] ?? '') as String; @@ -329,21 +335,21 @@ int _getNodeHeight(Object _) => getNodeHeightSync(); void startRefresh() => startRefreshSync(); -Future setupNode( - {String address, - String login, - String password, +Future setupNode( + {required String address, + String? login, + String? password, bool useSSL = false, bool isLightWallet = false}) => - compute, void>(_setupNodeSync, { + compute, void>(_setupNodeSync, { 'address': address, - 'login': login, + 'login': login , 'password': password, 'useSSL': useSSL, 'isLightWallet': isLightWallet }); -Future store() => compute(_storeSync, 0); +Future store() => compute(_storeSync, 0); Future isConnected() => compute(_isConnected, 0); diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart index b414c8b0a..093d7e63b 100644 --- a/cw_monero/lib/api/wallet_manager.dart +++ b/cw_monero/lib/api/wallet_manager.dart @@ -38,18 +38,21 @@ final errorStringNative = moneroApi .asFunction(); void createWalletSync( - {String path, String password, String language, int nettype = 0}) { - final pathPointer = Utf8.toUtf8(path); - final passwordPointer = Utf8.toUtf8(password); - final languagePointer = Utf8.toUtf8(language); - final errorMessagePointer = allocate(); + {required String path, + required String password, + required String language, + int nettype = 0}) { + final pathPointer = path.toNativeUtf8(); + final passwordPointer = password.toNativeUtf8(); + final languagePointer = language.toNativeUtf8(); + final errorMessagePointer = ''.toNativeUtf8(); final isWalletCreated = createWalletNative(pathPointer, passwordPointer, languagePointer, nettype, errorMessagePointer) != 0; - free(pathPointer); - free(passwordPointer); - free(languagePointer); + calloc.free(pathPointer); + calloc.free(passwordPointer); + calloc.free(languagePointer); if (!isWalletCreated) { throw WalletCreationException( @@ -59,25 +62,25 @@ void createWalletSync( // setupNodeSync(address: "node.moneroworld.com:18089"); } -bool isWalletExistSync({String path}) { - final pathPointer = Utf8.toUtf8(path); +bool isWalletExistSync({required String path}) { + final pathPointer = path.toNativeUtf8(); final isExist = isWalletExistNative(pathPointer) != 0; - free(pathPointer); + calloc.free(pathPointer); return isExist; } void restoreWalletFromSeedSync( - {String path, - String password, - String seed, + {required String path, + required String password, + required String seed, int nettype = 0, int restoreHeight = 0}) { - final pathPointer = Utf8.toUtf8(path); - final passwordPointer = Utf8.toUtf8(password); - final seedPointer = Utf8.toUtf8(seed); - final errorMessagePointer = allocate(); + final pathPointer = path.toNativeUtf8(); + final passwordPointer = password.toNativeUtf8(); + final seedPointer = seed.toNativeUtf8(); + final errorMessagePointer = ''.toNativeUtf8(); final isWalletRestored = restoreWalletFromSeedNative( pathPointer, passwordPointer, @@ -87,9 +90,9 @@ void restoreWalletFromSeedSync( errorMessagePointer) != 0; - free(pathPointer); - free(passwordPointer); - free(seedPointer); + calloc.free(pathPointer); + calloc.free(passwordPointer); + calloc.free(seedPointer); if (!isWalletRestored) { throw WalletRestoreFromSeedException( @@ -98,21 +101,21 @@ void restoreWalletFromSeedSync( } void restoreWalletFromKeysSync( - {String path, - String password, - String language, - String address, - String viewKey, - String spendKey, + {required String path, + required String password, + required String language, + required String address, + required String viewKey, + required String spendKey, int nettype = 0, int restoreHeight = 0}) { - final pathPointer = Utf8.toUtf8(path); - final passwordPointer = Utf8.toUtf8(password); - final languagePointer = Utf8.toUtf8(language); - final addressPointer = Utf8.toUtf8(address); - final viewKeyPointer = Utf8.toUtf8(viewKey); - final spendKeyPointer = Utf8.toUtf8(spendKey); - final errorMessagePointer = allocate(); + final pathPointer = path.toNativeUtf8(); + final passwordPointer = password.toNativeUtf8(); + final languagePointer = language.toNativeUtf8(); + final addressPointer = address.toNativeUtf8(); + final viewKeyPointer = viewKey.toNativeUtf8(); + final spendKeyPointer = spendKey.toNativeUtf8(); + final errorMessagePointer = ''.toNativeUtf8(); final isWalletRestored = restoreWalletFromKeysNative( pathPointer, passwordPointer, @@ -125,12 +128,12 @@ void restoreWalletFromKeysSync( errorMessagePointer) != 0; - free(pathPointer); - free(passwordPointer); - free(languagePointer); - free(addressPointer); - free(viewKeyPointer); - free(spendKeyPointer); + calloc.free(pathPointer); + calloc.free(passwordPointer); + calloc.free(languagePointer); + calloc.free(addressPointer); + calloc.free(viewKeyPointer); + calloc.free(spendKeyPointer); if (!isWalletRestored) { throw WalletRestoreFromKeysException( @@ -138,12 +141,15 @@ void restoreWalletFromKeysSync( } } -void loadWallet({String path, String password, int nettype = 0}) { - final pathPointer = Utf8.toUtf8(path); - final passwordPointer = Utf8.toUtf8(password); +void loadWallet({ + required String path, + required String password, + int nettype = 0}) { + final pathPointer = path.toNativeUtf8(); + final passwordPointer = password.toNativeUtf8(); final loaded = loadWalletNative(pathPointer, passwordPointer, nettype) != 0; - free(pathPointer); - free(passwordPointer); + calloc.free(pathPointer); + calloc.free(passwordPointer); if (!loaded) { throw WalletOpeningException( @@ -189,20 +195,20 @@ void _restoreFromKeys(Map args) { } Future _openWallet(Map args) async => - loadWallet(path: args['path'], password: args['password']); + loadWallet(path: args['path'] as String, password: args['password'] as String); bool _isWalletExist(String path) => isWalletExistSync(path: path); -void openWallet({String path, String password, int nettype = 0}) async => +void openWallet({required String path, required String password, int nettype = 0}) async => loadWallet(path: path, password: password, nettype: nettype); Future openWalletAsync(Map args) async => compute(_openWallet, args); Future createWallet( - {String path, - String password, - String language, + {required String path, + required String password, + required String language, int nettype = 0}) async => compute(_createWallet, { 'path': path, @@ -211,10 +217,10 @@ Future createWallet( 'nettype': nettype }); -Future restoreFromSeed( - {String path, - String password, - String seed, +Future restoreFromSeed( + {required String path, + required String password, + required String seed, int nettype = 0, int restoreHeight = 0}) async => compute, void>(_restoreFromSeed, { @@ -225,13 +231,13 @@ Future restoreFromSeed( 'restoreHeight': restoreHeight }); -Future restoreFromKeys( - {String path, - String password, - String language, - String address, - String viewKey, - String spendKey, +Future restoreFromKeys( + {required String path, + required String password, + required String language, + required String address, + required String viewKey, + required String spendKey, int nettype = 0, int restoreHeight = 0}) async => compute, void>(_restoreFromKeys, { @@ -245,4 +251,4 @@ Future restoreFromKeys( 'restoreHeight': restoreHeight }); -Future isWalletExist({String path}) => compute(_isWalletExist, path); +Future isWalletExist({required String path}) => compute(_isWalletExist, path); diff --git a/cw_monero/lib/monero_account_list.dart b/cw_monero/lib/monero_account_list.dart index 12cda5680..f618cf57a 100644 --- a/cw_monero/lib/monero_account_list.dart +++ b/cw_monero/lib/monero_account_list.dart @@ -12,7 +12,6 @@ abstract class MoneroAccountListBase with Store { _isRefreshing = false, _isUpdating = false { refresh(); - print(account_list.accountSizeNative()); } @observable @@ -49,12 +48,12 @@ abstract class MoneroAccountListBase with Store { label: accountRow.getLabel())) .toList(); - Future addAccount({String label}) async { + Future addAccount({required String label}) async { await account_list.addAccount(label: label); update(); } - Future setLabelAccount({int accountIndex, String label}) async { + Future setLabelAccount({required int accountIndex, required String label}) async { await account_list.setLabelForAccount( accountIndex: accountIndex, label: label); update(); diff --git a/cw_monero/lib/monero_subaddress_list.dart b/cw_monero/lib/monero_subaddress_list.dart index e59052207..8d8eeb469 100644 --- a/cw_monero/lib/monero_subaddress_list.dart +++ b/cw_monero/lib/monero_subaddress_list.dart @@ -10,11 +10,10 @@ class MoneroSubaddressList = MoneroSubaddressListBase with _$MoneroSubaddressList; abstract class MoneroSubaddressListBase with Store { - MoneroSubaddressListBase() { - _isRefreshing = false; - _isUpdating = false; - subaddresses = ObservableList(); - } + MoneroSubaddressListBase() + : _isRefreshing = false, + _isUpdating = false, + subaddresses = ObservableList(); @observable ObservableList subaddresses; @@ -22,7 +21,7 @@ abstract class MoneroSubaddressListBase with Store { bool _isRefreshing; bool _isUpdating; - void update({int accountIndex}) { + void update({required int accountIndex}) { if (_isUpdating) { return; } @@ -59,20 +58,20 @@ abstract class MoneroSubaddressListBase with Store { .toList(); } - Future addSubaddress({int accountIndex, String label}) async { + Future addSubaddress({required int accountIndex, required String label}) async { await subaddress_list.addSubaddress( accountIndex: accountIndex, label: label); update(accountIndex: accountIndex); } - Future setLabelSubaddress( - {int accountIndex, int addressIndex, String label}) async { + Future setLabelSubaddress( + {required int accountIndex, required int addressIndex, required String label}) async { await subaddress_list.setLabelForSubaddress( accountIndex: accountIndex, addressIndex: addressIndex, label: label); update(accountIndex: accountIndex); } - void refresh({int accountIndex}) { + void refresh({required int accountIndex}) { if (_isRefreshing) { return; } diff --git a/cw_monero/lib/monero_transaction_creation_credentials.dart b/cw_monero/lib/monero_transaction_creation_credentials.dart index 3f0051046..96f2b1637 100644 --- a/cw_monero/lib/monero_transaction_creation_credentials.dart +++ b/cw_monero/lib/monero_transaction_creation_credentials.dart @@ -2,7 +2,7 @@ import 'package:cw_core/monero_transaction_priority.dart'; import 'package:cw_core/output_info.dart'; class MoneroTransactionCreationCredentials { - MoneroTransactionCreationCredentials({this.outputs, this.priority}); + MoneroTransactionCreationCredentials({required this.outputs, required this.priority}); final List outputs; final MoneroTransactionPriority priority; diff --git a/cw_monero/lib/monero_transaction_info.dart b/cw_monero/lib/monero_transaction_info.dart index db393497a..2dfdaf408 100644 --- a/cw_monero/lib/monero_transaction_info.dart +++ b/cw_monero/lib/monero_transaction_info.dart @@ -10,7 +10,7 @@ class MoneroTransactionInfo extends TransactionInfo { MoneroTransactionInfo(this.id, this.height, this.direction, this.date, this.isPending, this.amount, this.accountIndex, this.addressIndex, this.fee); - MoneroTransactionInfo.fromMap(Map map) + MoneroTransactionInfo.fromMap(Map map) : id = (map['hash'] ?? '') as String, height = (map['height'] ?? 0) as int, direction = @@ -24,7 +24,7 @@ class MoneroTransactionInfo extends TransactionInfo { addressIndex = map['addressIndex'] as int, key = getTxKey((map['hash'] ?? '') as String), fee = map['fee'] as int ?? 0 { - additionalInfo = { + additionalInfo = { 'key': key, 'accountIndex': accountIndex, 'addressIndex': addressIndex @@ -43,7 +43,7 @@ class MoneroTransactionInfo extends TransactionInfo { addressIndex = row.subaddrIndex, key = getTxKey(row.getHash()), fee = row.fee { - additionalInfo = { + additionalInfo = { 'key': key, 'accountIndex': accountIndex, 'addressIndex': addressIndex @@ -59,10 +59,9 @@ class MoneroTransactionInfo extends TransactionInfo { final int amount; final int fee; final int addressIndex; - String recipientAddress; - String key; - - String _fiatAmount; + String? recipientAddress; + String? key; + String? _fiatAmount; @override String amountFormatted() => diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 175bd96f5..956c9b9fc 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -36,19 +36,24 @@ class MoneroWallet = MoneroWalletBase with _$MoneroWallet; abstract class MoneroWalletBase extends WalletBase with Store { - MoneroWalletBase({WalletInfo walletInfo}) - : super(walletInfo) { + MoneroWalletBase({required WalletInfo walletInfo}) + : balance = ObservableMap.of({ + CryptoCurrency.xmr: MoneroBalance( + fullBalance: monero_wallet.getFullBalance(accountIndex: 0), + unlockedBalance: monero_wallet.getFullBalance(accountIndex: 0)) + }), + _isTransactionUpdating = false, + _hasSyncAfterStartup = false, + walletAddresses = MoneroWalletAddresses(walletInfo), + syncStatus = NotConnectedSyncStatus(), + super(walletInfo) { transactionHistory = MoneroTransactionHistory(); - balance = ObservableMap.of({ - CryptoCurrency.xmr: MoneroBalance( - fullBalance: monero_wallet.getFullBalance(accountIndex: 0), - unlockedBalance: monero_wallet.getFullBalance(accountIndex: 0)) - }); - _isTransactionUpdating = false; - _hasSyncAfterStartup = false; - walletAddresses = MoneroWalletAddresses(walletInfo); _onAccountChangeReaction = reaction((_) => walletAddresses.account, - (Account account) { + (Account? account) { + if (account == null) { + return; + } + balance = ObservableMap.of( { currency: MoneroBalance( @@ -83,19 +88,19 @@ abstract class MoneroWalletBase extends WalletBase init() async { await walletAddresses.init(); balance = ObservableMap.of( { currency: MoneroBalance( - fullBalance: monero_wallet.getFullBalance(accountIndex: walletAddresses.account.id), - unlockedBalance: monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id)) + fullBalance: monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id), + unlockedBalance: monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id)) }); _setListeners(); await updateTransactions(); @@ -117,12 +122,12 @@ abstract class MoneroWalletBase extends WalletBase connectToNode({@required Node node}) async { + Future connectToNode({required Node node}) async { try { syncStatus = ConnectingSyncStatus(); await monero_wallet.setupNode( @@ -162,7 +167,7 @@ abstract class MoneroWalletBase extends WalletBase 1; final unlockedBalance = - monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id); + monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id); PendingTransactionDescription pendingTransactionDescription; @@ -172,32 +177,32 @@ abstract class MoneroWalletBase extends WalletBase item.sendAll - || item.formattedCryptoAmount <= 0)) { + || (item.formattedCryptoAmount ?? 0) <= 0)) { throw MoneroTransactionCreationException('Wrong balance. Not enough XMR on your balance.'); } final int totalAmount = outputs.fold(0, (acc, value) => - acc + value.formattedCryptoAmount); + acc + (value.formattedCryptoAmount ?? 0)); if (unlockedBalance < totalAmount) { throw MoneroTransactionCreationException('Wrong balance. Not enough XMR on your balance.'); } final moneroOutputs = outputs.map((output) { - final outputAddress = output.isParsedAddress - ? output.extractedAddress - : output.address; + final outputAddress = output.isParsedAddress + ? output.extractedAddress + : output.address; - return MoneroOutput( - address: outputAddress, - amount: output.cryptoAmount.replaceAll(',', '.')); + return MoneroOutput( + address: outputAddress!, + amount: output.cryptoAmount!.replaceAll(',', '.')); }).toList(); pendingTransactionDescription = await transaction_history.createTransactionMultDest( outputs: moneroOutputs, priorityRaw: _credentials.priority.serialize(), - accountIndex: walletAddresses.account.id); + accountIndex: walletAddresses.account!.id); } else { final output = outputs.first; final address = output.isParsedAddress @@ -205,7 +210,7 @@ abstract class MoneroWalletBase extends WalletBase rescan({int height}) async { + Future rescan({required int height}) async { walletInfo.restoreHeight = height; walletInfo.isRecovery = true; monero_wallet.setRefreshFromBlockHeight(height: height); @@ -372,8 +377,8 @@ abstract class MoneroWalletBase extends WalletBase - monero_wallet.getFullBalance(accountIndex: walletAddresses.account.id); + monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id); int _getUnlockedBalance() => - monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id); + monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id); void _onNewBlock(int height, int blocksLeft, double ptc) async { try { diff --git a/cw_monero/lib/monero_wallet_addresses.dart b/cw_monero/lib/monero_wallet_addresses.dart index c84e8c8e4..2002e789a 100644 --- a/cw_monero/lib/monero_wallet_addresses.dart +++ b/cw_monero/lib/monero_wallet_addresses.dart @@ -12,20 +12,21 @@ class MoneroWalletAddresses = MoneroWalletAddressesBase with _$MoneroWalletAddresses; abstract class MoneroWalletAddressesBase extends WalletAddresses with Store { - MoneroWalletAddressesBase(WalletInfo walletInfo) : super(walletInfo) { - accountList = MoneroAccountList(); - subaddressList = MoneroSubaddressList(); - } + MoneroWalletAddressesBase(WalletInfo walletInfo) + : accountList = MoneroAccountList(), + subaddressList = MoneroSubaddressList(), + address = '', + super(walletInfo); @override @observable String address; + + @observable + Account? account; @observable - Account account; - - @observable - Subaddress subaddress; + Subaddress? subaddress; MoneroSubaddressList subaddressList; @@ -35,7 +36,7 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store { Future init() async { accountList.update(); account = accountList.accounts.first; - updateSubaddressList(accountIndex: account.id ?? 0); + updateSubaddressList(accountIndex: account?.id ?? 0); await updateAddressesInBox(); } @@ -61,14 +62,14 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store { bool validate() { accountList.update(); - final accountListLength = accountList.accounts?.length ?? 0; + final accountListLength = accountList.accounts.length ?? 0; if (accountListLength <= 0) { return false; } subaddressList.update(accountIndex: accountList.accounts.first.id); - final subaddressListLength = subaddressList.subaddresses?.length ?? 0; + final subaddressListLength = subaddressList.subaddresses.length ?? 0; if (subaddressListLength <= 0) { return false; @@ -77,9 +78,9 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store { return true; } - void updateSubaddressList({int accountIndex}) { + void updateSubaddressList({required int accountIndex}) { subaddressList.update(accountIndex: accountIndex); subaddress = subaddressList.subaddresses.first; - address = subaddress.address; + address = subaddress!.address; } } \ No newline at end of file diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index d0461de72..095fe83bb 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -13,7 +13,7 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; class MoneroNewWalletCredentials extends WalletCredentials { - MoneroNewWalletCredentials({String name, String password, this.language}) + MoneroNewWalletCredentials({required String name, required this.language, String? password}) : super(name: name, password: password); final String language; @@ -21,7 +21,7 @@ class MoneroNewWalletCredentials extends WalletCredentials { class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials { MoneroRestoreWalletFromSeedCredentials( - {String name, String password, int height, this.mnemonic}) + {required String name, required this.mnemonic, int height = 0, String? password}) : super(name: name, password: password, height: height); final String mnemonic; @@ -34,13 +34,13 @@ class MoneroWalletLoadingException implements Exception { class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials { MoneroRestoreWalletFromKeysCredentials( - {String name, - String password, - this.language, - this.address, - this.viewKey, - this.spendKey, - int height}) + {required String name, + required String password, + required this.language, + required this.address, + required this.viewKey, + required this.spendKey, + int height = 0}) : super(name: name, password: password, height: height); final String language; @@ -69,9 +69,9 @@ class MoneroWalletService extends WalletService< final path = await pathForWallet(name: credentials.name, type: getType()); await monero_wallet_manager.createWallet( path: path, - password: credentials.password, + password: credentials.password!, language: credentials.language); - final wallet = MoneroWallet(walletInfo: credentials.walletInfo); + final wallet = MoneroWallet(walletInfo: credentials.walletInfo!); await wallet.init(); return wallet; @@ -106,8 +106,7 @@ class MoneroWalletService extends WalletService< await monero_wallet_manager .openWalletAsync({'path': path, 'password': password}); final walletInfo = walletInfoSource.values.firstWhere( - (info) => info.id == WalletBase.idFor(name, getType()), - orElse: () => null); + (info) => info.id == WalletBase.idFor(name, getType())); final wallet = MoneroWallet(walletInfo: walletInfo); final isValid = wallet.walletAddresses.validate(); @@ -156,13 +155,13 @@ class MoneroWalletService extends WalletService< final path = await pathForWallet(name: credentials.name, type: getType()); await monero_wallet_manager.restoreFromKeys( path: path, - password: credentials.password, + password: credentials.password!, language: credentials.language, - restoreHeight: credentials.height, + restoreHeight: credentials.height!, address: credentials.address, viewKey: credentials.viewKey, spendKey: credentials.spendKey); - final wallet = MoneroWallet(walletInfo: credentials.walletInfo); + final wallet = MoneroWallet(walletInfo: credentials.walletInfo!); await wallet.init(); return wallet; @@ -180,10 +179,10 @@ class MoneroWalletService extends WalletService< final path = await pathForWallet(name: credentials.name, type: getType()); await monero_wallet_manager.restoreFromSeed( path: path, - password: credentials.password, + password: credentials.password!, seed: credentials.mnemonic, - restoreHeight: credentials.height); - final wallet = MoneroWallet(walletInfo: credentials.walletInfo); + restoreHeight: credentials.height!); + final wallet = MoneroWallet(walletInfo: credentials.walletInfo!); await wallet.init(); return wallet; diff --git a/cw_monero/lib/pending_monero_transaction.dart b/cw_monero/lib/pending_monero_transaction.dart index d32bab2ce..7488cfc08 100644 --- a/cw_monero/lib/pending_monero_transaction.dart +++ b/cw_monero/lib/pending_monero_transaction.dart @@ -2,7 +2,7 @@ import 'package:cw_monero/api/structs/pending_transaction.dart'; import 'package:cw_monero/api/transaction_history.dart' as monero_transaction_history; import 'package:cw_core/crypto_currency.dart'; -import 'package:cake_wallet/core/amount_converter.dart'; +// import 'package:cake_wallet/core/amount_converter.dart'; import 'package:cw_core/pending_transaction.dart'; @@ -27,13 +27,17 @@ class PendingMoneroTransaction with PendingTransaction { String get txKey => pendingTransactionDescription.txKey; + // FIX-ME: AmountConverter @override - String get amountFormatted => AmountConverter.amountIntToString( - CryptoCurrency.xmr, pendingTransactionDescription.amount); + String get amountFormatted => ''; + // AmountConverter.amountIntToString( + // CryptoCurrency.xmr, pendingTransactionDescription.amount); + // FIX-ME: AmountConverter @override - String get feeFormatted => AmountConverter.amountIntToString( - CryptoCurrency.xmr, pendingTransactionDescription.fee); + String get feeFormatted => ''; + // AmountConverter.amountIntToString( + // CryptoCurrency.xmr, pendingTransactionDescription.fee); @override Future commit() async { diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 82c5f78f1..557550754 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -7,35 +7,35 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "14.0.0" + version: "47.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.41.2" + version: "4.7.0" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.6.0" + version: "2.3.1" asn1lib: dependency: transitive description: name: asn1lib url: "https://pub.dartlang.org" source: hosted - version: "0.8.1" + version: "1.1.1" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -49,42 +49,42 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" + version: "2.3.1" build_config: dependency: transitive description: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "0.4.6" + version: "1.1.0" build_daemon: dependency: transitive description: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "2.1.10" + version: "3.1.0" build_resolvers: dependency: "direct dev" description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "1.5.3" + version: "2.0.10" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.11.5" + version: "2.2.1" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "6.1.10" + version: "7.2.4" built_collection: dependency: transitive description: @@ -105,63 +105,49 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" + version: "1.2.1" checked_yaml: dependency: transitive description: name: checked_yaml url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" - cli_util: - dependency: transitive - description: - name: cli_util - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.5" + version: "2.0.1" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" code_builder: dependency: transitive description: name: code_builder url: "https://pub.dartlang.org" source: hosted - version: "3.7.0" + version: "4.3.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" convert: dependency: transitive description: name: convert url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "3.0.2" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "3.0.2" cw_core: dependency: "direct main" description: @@ -175,35 +161,28 @@ packages: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.3.12" - dartx: - dependency: transitive - description: - name: dartx - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.0" + version: "2.2.4" encrypt: - dependency: transitive + dependency: "direct main" description: name: encrypt url: "https://pub.dartlang.org" source: hosted - version: "4.1.0" + version: "5.0.1" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" ffi: dependency: "direct main" description: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "1.2.1" file: dependency: transitive description: @@ -229,12 +208,19 @@ packages: name: flutter_mobx url: "https://pub.dartlang.org" source: hosted - version: "1.1.0+2" + version: "2.0.6+4" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" glob: dependency: transitive description: @@ -248,42 +234,42 @@ packages: name: graphs url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "2.1.0" hive: dependency: transitive description: name: hive url: "https://pub.dartlang.org" source: hosted - version: "1.4.4+1" + version: "2.2.3" hive_generator: dependency: "direct dev" description: name: hive_generator url: "https://pub.dartlang.org" source: hosted - version: "0.8.2" + version: "1.1.3" http: dependency: "direct main" description: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.2" + version: "0.13.5" http_multi_server: dependency: transitive description: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "3.2.1" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" + version: "4.0.1" intl: dependency: "direct main" description: @@ -297,7 +283,7 @@ packages: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.5" + version: "1.0.3" js: dependency: transitive description: @@ -311,7 +297,7 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "4.0.1" + version: "4.6.0" logging: dependency: transitive description: @@ -325,14 +311,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10" + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.8.0" mime: dependency: transitive description: @@ -346,35 +339,77 @@ packages: name: mobx url: "https://pub.dartlang.org" source: hosted - version: "1.2.1+4" + version: "2.1.0" mobx_codegen: dependency: "direct dev" description: name: mobx_codegen url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "2.0.7+3" package_config: dependency: transitive description: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "2.1.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.2" path_provider: dependency: "direct main" description: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.4.0" + version: "2.0.11" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.20" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.7" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" pedantic: dependency: transitive description: @@ -388,14 +423,21 @@ packages: name: platform url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" pointycastle: dependency: transitive description: name: pointycastle url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "3.6.2" pool: dependency: transitive description: @@ -403,6 +445,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.5.0" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" pub_semver: dependency: transitive description: @@ -416,21 +465,21 @@ packages: name: pubspec_parse url: "https://pub.dartlang.org" source: hosted - version: "0.1.8" + version: "1.2.1" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "0.7.9" + version: "1.3.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.4+1" + version: "1.0.2" sky_engine: dependency: transitive description: flutter @@ -442,14 +491,21 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "0.9.10+3" + version: "1.2.3" + source_helper: + dependency: transitive + description: + name: source_helper + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.3" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -477,35 +533,28 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19" - time: - dependency: transitive - description: - name: time - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.1" + version: "0.4.12" timing: dependency: transitive description: name: timing url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+3" + version: "1.0.0" typed_data: dependency: transitive description: @@ -519,7 +568,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.2" watcher: dependency: transitive description: @@ -533,7 +582,21 @@ packages: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.2.0" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.6.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0+2" yaml: dependency: transitive description: @@ -542,5 +605,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.12.0 <3.0.0" - flutter: ">=1.17.0" + dart: ">=2.17.5 <3.0.0" + flutter: ">=2.8.1" diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 29584e194..23e8782cb 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -6,27 +6,29 @@ author: Cake Wallet homepage: https://cakewallet.com environment: - sdk: ">=2.6.0 <3.0.0" + sdk: ">=2.17.5 <3.0.0" + flutter: ">=1.20.0" dependencies: flutter: sdk: flutter - ffi: ^0.1.3 - path_provider: ^1.4.0 - http: ^0.12.0+2 - mobx: ^1.2.1+2 - flutter_mobx: ^1.1.0+2 + ffi: ^1.1.2 + http: ^0.13.4 + path_provider: ^2.0.11 + mobx: ^2.0.7+4 + flutter_mobx: ^2.0.6+1 intl: ^0.17.0 + encrypt: ^5.0.1 cw_core: path: ../cw_core dev_dependencies: flutter_test: sdk: flutter - build_runner: ^1.10.3 - build_resolvers: ^1.3.10 - mobx_codegen: ^1.1.0+1 - hive_generator: ^0.8.1 + build_runner: ^2.1.11 + build_resolvers: ^2.0.9 + mobx_codegen: ^2.0.7 + hive_generator: ^1.1.3 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 66762582e..ec8e4f2e3 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - barcode_scan (0.0.1): + - barcode_scan2 (0.0.1): - Flutter - MTBBarcodeScanner - SwiftProtobuf @@ -98,22 +98,20 @@ PODS: - DKPhotoGallery/Resource (0.0.17): - SDWebImage - SwiftyGif - - esys_flutter_share (0.0.1): - - Flutter - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter - Flutter (1.0.0) - flutter_secure_storage (3.3.1): - Flutter - - local_auth (0.0.1): + - local_auth_ios (0.0.1): - Flutter - MTBBarcodeScanner (5.0.11) - package_info (0.0.1): - Flutter - - path_provider (0.0.1): + - path_provider_ios (0.0.1): - Flutter - - "permission_handler (5.1.0+2)": + - permission_handler_apple (9.0.4): - Flutter - platform_device_id (0.0.1): - Flutter @@ -123,22 +121,22 @@ PODS: - SDWebImage/Core (5.9.1) - share (0.0.1): - Flutter - - shared_preferences (0.0.1): + - shared_preferences_ios (0.0.1): - Flutter - - SwiftProtobuf (1.12.0) + - SwiftProtobuf (1.18.0) - SwiftyGif (5.3.0) - uni_links (0.0.1): - Flutter - UnstoppableDomainsResolution (4.0.0): - BigInt - CryptoSwift - - url_launcher (0.0.1): + - url_launcher_ios (0.0.1): - Flutter - - webview_flutter (0.0.1): + - webview_flutter_wkwebview (0.0.1): - Flutter DEPENDENCIES: - - barcode_scan (from `.symlinks/plugins/barcode_scan/ios`) + - barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`) - connectivity (from `.symlinks/plugins/connectivity/ios`) - CryptoSwift - cw_haven (from `.symlinks/plugins/cw_haven/ios`) @@ -147,21 +145,20 @@ DEPENDENCIES: - device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`) - device_info (from `.symlinks/plugins/device_info/ios`) - devicelocale (from `.symlinks/plugins/devicelocale/ios`) - - esys_flutter_share (from `.symlinks/plugins/esys_flutter_share/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - - local_auth (from `.symlinks/plugins/local_auth/ios`) + - local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`) - package_info (from `.symlinks/plugins/package_info/ios`) - - path_provider (from `.symlinks/plugins/path_provider/ios`) - - permission_handler (from `.symlinks/plugins/permission_handler/ios`) + - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) + - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - platform_device_id (from `.symlinks/plugins/platform_device_id/ios`) - share (from `.symlinks/plugins/share/ios`) - - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) + - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) - uni_links (from `.symlinks/plugins/uni_links/ios`) - UnstoppableDomainsResolution (~> 4.0.0) - - url_launcher (from `.symlinks/plugins/url_launcher/ios`) - - webview_flutter (from `.symlinks/plugins/webview_flutter/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) SPEC REPOS: https://github.com/CocoaPods/Specs.git: @@ -177,8 +174,8 @@ SPEC REPOS: - UnstoppableDomainsResolution EXTERNAL SOURCES: - barcode_scan: - :path: ".symlinks/plugins/barcode_scan/ios" + barcode_scan2: + :path: ".symlinks/plugins/barcode_scan2/ios" connectivity: :path: ".symlinks/plugins/connectivity/ios" cw_haven: @@ -193,37 +190,35 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/device_info/ios" devicelocale: :path: ".symlinks/plugins/devicelocale/ios" - esys_flutter_share: - :path: ".symlinks/plugins/esys_flutter_share/ios" file_picker: :path: ".symlinks/plugins/file_picker/ios" Flutter: :path: Flutter flutter_secure_storage: :path: ".symlinks/plugins/flutter_secure_storage/ios" - local_auth: - :path: ".symlinks/plugins/local_auth/ios" + local_auth_ios: + :path: ".symlinks/plugins/local_auth_ios/ios" package_info: :path: ".symlinks/plugins/package_info/ios" - path_provider: - :path: ".symlinks/plugins/path_provider/ios" - permission_handler: - :path: ".symlinks/plugins/permission_handler/ios" + path_provider_ios: + :path: ".symlinks/plugins/path_provider_ios/ios" + permission_handler_apple: + :path: ".symlinks/plugins/permission_handler_apple/ios" platform_device_id: :path: ".symlinks/plugins/platform_device_id/ios" share: :path: ".symlinks/plugins/share/ios" - shared_preferences: - :path: ".symlinks/plugins/shared_preferences/ios" + shared_preferences_ios: + :path: ".symlinks/plugins/shared_preferences_ios/ios" uni_links: :path: ".symlinks/plugins/uni_links/ios" - url_launcher: - :path: ".symlinks/plugins/url_launcher/ios" - webview_flutter: - :path: ".symlinks/plugins/webview_flutter/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + webview_flutter_wkwebview: + :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" SPEC CHECKSUMS: - barcode_scan: a5c27959edfafaa0c771905bad0b29d6d39e4479 + barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0 BigInt: f668a80089607f521586bbe29513d708491ef2f7 connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467 CryptoSwift: 093499be1a94b0cae36e6c26b70870668cb56060 @@ -235,26 +230,25 @@ SPEC CHECKSUMS: devicelocale: b22617f40038496deffba44747101255cee005b0 DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 - esys_flutter_share: 403498dab005b36ce1f8d7aff377e81f0621b0b4 - file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 - Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c + file_picker: 817ab1d8cd2da9d2da412a417162deee3500fc95 + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec - local_auth: 25938960984c3a7f6e3253e3f8d962fdd16852bd + local_auth_ios: 0d333dde7780f669e66f19d2ff6005f3ea84008d MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 - path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c - permission_handler: ccb20a9fad0ee9b1314a52b70b76b473c5f8dab0 + path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 + permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce platform_device_id: 81b3e2993881f87d0c82ef151dc274df4869aef5 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 SDWebImage: a990c053fff71e388a10f3357edb0be17929c9c5 share: 0b2c3e82132f5888bccca3351c504d0003b3b410 - shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d - SwiftProtobuf: 4ef85479c18ca85b5482b343df9c319c62bda699 + shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad + SwiftProtobuf: c3c12645230d9b09c72267e0de89468c5543bd86 SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40 uni_links: d97da20c7701486ba192624d99bffaaffcfc298a UnstoppableDomainsResolution: c3c67f4d0a5e2437cb00d4bd50c2e00d6e743841 - url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef - webview_flutter: 3603125dfd3bcbc9d8d418c3f80aeecf331c068b + url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de + webview_flutter_wkwebview: b7e70ef1ddded7e69c796c7390ee74180182971f PODFILE CHECKSUM: ae71bdf0eb731a1ffc399c122f6aa4dea0cb5f6f diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index a0808e686..8d62665c3 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -162,7 +162,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -368,6 +368,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 32J6BB6VUS; + DISABLED_ARCHS = x86_64; ENABLE_BITCODE = NO; EXCLUDED_SOURCE_FILE_NAMES = ""; FRAMEWORK_SEARCH_PATHS = ( @@ -390,6 +391,7 @@ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; + VALID_ARCHS = arm64; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; @@ -512,6 +514,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 32J6BB6VUS; + DISABLED_ARCHS = x86_64; ENABLE_BITCODE = NO; EXCLUDED_SOURCE_FILE_NAMES = ""; FRAMEWORK_SEARCH_PATHS = ( @@ -535,6 +538,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; + VALID_ARCHS = arm64; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -548,6 +552,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 32J6BB6VUS; + DISABLED_ARCHS = x86_64; ENABLE_BITCODE = NO; EXCLUDED_SOURCE_FILE_NAMES = ""; FRAMEWORK_SEARCH_PATHS = ( @@ -570,6 +575,7 @@ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; + VALID_ARCHS = arm64; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index fb2dffc49..c87d15a33 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ obj) { final instructions = (obj['instructions'] as List) @@ -45,13 +45,13 @@ class AnyPayPayment { .fold(0, (int outAcc, out) => outAcc + out.amount)); switch (chain) { case AnyPayChain.xmr: - return monero.formatterMoneroAmountToString(amount: total); + return monero!.formatterMoneroAmountToString(amount: total); case AnyPayChain.btc: - return bitcoin.formatterBitcoinAmountToString(amount: total); + return bitcoin!.formatterBitcoinAmountToString(amount: total); case AnyPayChain.ltc: - return bitcoin.formatterBitcoinAmountToString(amount: total); + return bitcoin!.formatterBitcoinAmountToString(amount: total); default: - return null; + return ''; } } diff --git a/lib/anypay/any_pay_payment_committed_info.dart b/lib/anypay/any_pay_payment_committed_info.dart index 126b3d92e..12adea003 100644 --- a/lib/anypay/any_pay_payment_committed_info.dart +++ b/lib/anypay/any_pay_payment_committed_info.dart @@ -3,11 +3,11 @@ import 'package:cake_wallet/anypay/any_pay_trasnaction.dart'; class AnyPayPaymentCommittedInfo { const AnyPayPaymentCommittedInfo({ - @required this.uri, - @required this.currency, - @required this.chain, - @required this.transactions, - @required this.memo}); + required this.uri, + required this.currency, + required this.chain, + required this.transactions, + required this.memo}); final String uri; final String currency; diff --git a/lib/anypay/any_pay_payment_instruction.dart b/lib/anypay/any_pay_payment_instruction.dart index 41d2ee82d..6477e6649 100644 --- a/lib/anypay/any_pay_payment_instruction.dart +++ b/lib/anypay/any_pay_payment_instruction.dart @@ -3,11 +3,11 @@ import 'package:cake_wallet/anypay/any_pay_payment_instruction_output.dart'; class AnyPayPaymentInstruction { AnyPayPaymentInstruction({ - @required this.type, - @required this.requiredFeeRate, - @required this.txKey, - @required this.txHash, - @required this.outputs}); + required this.type, + required this.requiredFeeRate, + required this.txKey, + required this.txHash, + required this.outputs}); factory AnyPayPaymentInstruction.fromMap(Map obj) { final outputs = (obj['outputs'] as List) diff --git a/lib/anypay/any_pay_trasnaction.dart b/lib/anypay/any_pay_trasnaction.dart index 29f8a0152..af736cbf6 100644 --- a/lib/anypay/any_pay_trasnaction.dart +++ b/lib/anypay/any_pay_trasnaction.dart @@ -1,9 +1,7 @@ -import 'package:flutter/foundation.dart'; - class AnyPayTransaction { - const AnyPayTransaction(this.tx, {@required this.id, @required this.key}); + const AnyPayTransaction(this.tx, {required this.id, required this.key}); final String tx; final String id; - final String key; + final String? key; } \ No newline at end of file diff --git a/lib/anypay/anypay_api.dart b/lib/anypay/anypay_api.dart index c0727bc29..b53a3ebd1 100644 --- a/lib/anypay/anypay_api.dart +++ b/lib/anypay/anypay_api.dart @@ -33,15 +33,15 @@ class AnyPayApi { case 'litecoin': return CryptoCurrency.ltc; default: - return null; + throw Exception('Unexpected scheme: ${scheme}'); } } Future paymentRequest(String uri) async { final fragments = uri.split(':?r='); final scheme = fragments.first; - final url = fragments[1]; - final headers = { + final url = Uri.parse(fragments[1]); + final headers = { 'Content-Type': contentTypePaymentRequest, 'X-Paypro-Version': xPayproVersion, 'Accept': '*/*',}; @@ -50,20 +50,20 @@ class AnyPayApi { 'currency': currencyByScheme(scheme).title}; final response = await post(url, headers: headers, body: utf8.encode(json.encode(body))); - if (response.statusCode != 200) { - return null; + if (response.statusCode != 200) { + throw Exception('Unexpected response http code: ${response.statusCode}'); } - final decodedBody = json.decode(response.body) as Map; - return AnyPayPayment.fromMap(decodedBody); + final decodedBody = json.decode(response.body) as Map; + return AnyPayPayment.fromMap(decodedBody); } Future payment( String uri, - {@required String chain, - @required String currency, - @required List transactions}) async { - final headers = { + {required String chain, + required String currency, + required List transactions}) async { + final headers = { 'Content-Type': contentTypePayment, 'X-Paypro-Version': xPayproVersion, 'Accept': '*/*',}; @@ -71,7 +71,7 @@ class AnyPayApi { 'chain': chain, 'currency': currency, 'transactions': transactions.map((tx) => {'tx': tx.tx, 'tx_hash': tx.id, 'tx_key': tx.key}).toList()}; - final response = await post(uri, headers: headers, body: utf8.encode(json.encode(body))); + final response = await post(Uri.parse(uri), headers: headers, body: utf8.encode(json.encode(body))); if (response.statusCode == 400) { final decodedBody = json.decode(response.body) as Map; throw Exception(decodedBody['message'] as String); diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 46ef89172..9d5484b79 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -5,15 +5,24 @@ class CWBitcoin extends Bitcoin { TransactionPriority getMediumTransactionPriority() => BitcoinTransactionPriority.medium; @override - WalletCredentials createBitcoinRestoreWalletFromSeedCredentials({String name, String mnemonic, String password}) + WalletCredentials createBitcoinRestoreWalletFromSeedCredentials({ + required String name, + required String mnemonic, + required String password}) => BitcoinRestoreWalletFromSeedCredentials(name: name, mnemonic: mnemonic, password: password); @override - WalletCredentials createBitcoinRestoreWalletFromWIFCredentials({String name, String password, String wif, WalletInfo walletInfo}) + WalletCredentials createBitcoinRestoreWalletFromWIFCredentials({ + required String name, + required String password, + required String wif, + WalletInfo? walletInfo}) => BitcoinRestoreWalletFromWIFCredentials(name: name, password: password, wif: wif, walletInfo: walletInfo); @override - WalletCredentials createBitcoinNewWalletCredentials({String name, WalletInfo walletInfo}) + WalletCredentials createBitcoinNewWalletCredentials({ + required String name, + WalletInfo? walletInfo}) => BitcoinNewWalletCredentials(name: name, walletInfo: walletInfo); @override @@ -55,7 +64,7 @@ class CWBitcoin extends Bitcoin { } @override - Object createBitcoinTransactionCredentials(List outputs, {TransactionPriority priority, int feeRate}) + Object createBitcoinTransactionCredentials(List outputs, {required TransactionPriority priority, int? feeRate}) => BitcoinTransactionCredentials( outputs.map((out) => OutputInfo( fiatAmount: out.fiatAmount, @@ -71,7 +80,7 @@ class CWBitcoin extends Bitcoin { feeRate: feeRate); @override - Object createBitcoinTransactionCredentialsRaw(List outputs, {TransactionPriority priority, int feeRate}) + Object createBitcoinTransactionCredentialsRaw(List outputs, {TransactionPriority? priority, required int feeRate}) => BitcoinTransactionCredentials( outputs, priority: priority != null ? priority as BitcoinTransactionPriority : null, @@ -92,11 +101,11 @@ class CWBitcoin extends Bitcoin { } @override - String formatterBitcoinAmountToString({int amount}) + String formatterBitcoinAmountToString({required int amount}) => bitcoinAmountToString(amount: amount); @override - double formatterBitcoinAmountToDouble({int amount}) + double formatterBitcoinAmountToDouble({required int amount}) => bitcoinAmountToDouble(amount: amount); @override diff --git a/lib/buy/buy_amount.dart b/lib/buy/buy_amount.dart index 1a1b3ae28..e41bb1148 100644 --- a/lib/buy/buy_amount.dart +++ b/lib/buy/buy_amount.dart @@ -2,13 +2,13 @@ import 'package:flutter/foundation.dart'; class BuyAmount { BuyAmount({ - @required this.sourceAmount, - @required this.destAmount, + required this.sourceAmount, + required this.destAmount, this.achSourceAmount, this.minAmount = 0}); final double sourceAmount; final double destAmount; - final double achSourceAmount; + final double? achSourceAmount; final int minAmount; } \ No newline at end of file diff --git a/lib/buy/buy_exception.dart b/lib/buy/buy_exception.dart index 28064fdfc..edc6a7db0 100644 --- a/lib/buy/buy_exception.dart +++ b/lib/buy/buy_exception.dart @@ -2,7 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:cake_wallet/buy/buy_provider_description.dart'; class BuyException implements Exception { - BuyException({@required this.description, @required this.text}); + BuyException({required this.description, required this.text}); final BuyProviderDescription description; final String text; diff --git a/lib/buy/buy_provider.dart b/lib/buy/buy_provider.dart index 0f496bad2..10a13ed94 100644 --- a/lib/buy/buy_provider.dart +++ b/lib/buy/buy_provider.dart @@ -5,7 +5,7 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; abstract class BuyProvider { - BuyProvider({this.wallet, this.isTestEnvironment}); + BuyProvider({required this.wallet, required this.isTestEnvironment}); final WalletBase wallet; final bool isTestEnvironment; diff --git a/lib/buy/buy_provider_description.dart b/lib/buy/buy_provider_description.dart index bcb581d05..07c7ff08b 100644 --- a/lib/buy/buy_provider_description.dart +++ b/lib/buy/buy_provider_description.dart @@ -2,20 +2,20 @@ import 'package:cw_core/enumerable_item.dart'; class BuyProviderDescription extends EnumerableItem with Serializable { - const BuyProviderDescription({String title, int raw}) + const BuyProviderDescription({required String title, required int raw}) : super(title: title, raw: raw); static const wyre = BuyProviderDescription(title: 'Wyre', raw: 0); static const moonPay = BuyProviderDescription(title: 'MoonPay', raw: 1); - static BuyProviderDescription deserialize({int raw}) { + static BuyProviderDescription deserialize({required int raw}) { switch (raw) { case 0: return wyre; case 1: return moonPay; default: - return null; + throw Exception('Incorrect token $raw for BuyProviderDescription deserialize'); } } } \ No newline at end of file diff --git a/lib/buy/get_buy_provider_icon.dart b/lib/buy/get_buy_provider_icon.dart index 672dfdd9d..c755d9615 100644 --- a/lib/buy/get_buy_provider_icon.dart +++ b/lib/buy/get_buy_provider_icon.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:cake_wallet/buy/buy_provider_description.dart'; -Image getBuyProviderIcon(BuyProviderDescription providerDescription, +Image? getBuyProviderIcon(BuyProviderDescription providerDescription, {Color iconColor = Colors.black}) { final _wyreIcon = diff --git a/lib/buy/moonpay/moonpay_buy_provider.dart b/lib/buy/moonpay/moonpay_buy_provider.dart index 4248df483..4ff3fb04c 100644 --- a/lib/buy/moonpay/moonpay_buy_provider.dart +++ b/lib/buy/moonpay/moonpay_buy_provider.dart @@ -23,7 +23,7 @@ class MoonPaySellProvider { final bool isTest; final String baseUrl; - Future requestUrl({CryptoCurrency currency, String refundWalletAddress}) async { + Future requestUrl({required CryptoCurrency currency, required String refundWalletAddress}) async { final originalUri = Uri.https( baseUrl, '', { 'apiKey': _apiKey, @@ -48,10 +48,9 @@ class MoonPaySellProvider { } class MoonPayBuyProvider extends BuyProvider { - MoonPayBuyProvider({WalletBase wallet, bool isTestEnvironment = false}) - : super(wallet: wallet, isTestEnvironment: isTestEnvironment) { - baseUrl = isTestEnvironment ? _baseTestUrl : _baseProductUrl; - } + MoonPayBuyProvider({required WalletBase wallet, bool isTestEnvironment = false}) + : baseUrl = isTestEnvironment ? _baseTestUrl : _baseProductUrl, + super(wallet: wallet, isTestEnvironment: isTestEnvironment); static const _baseTestUrl = 'https://buy-staging.moonpay.com'; static const _baseProductUrl = 'https://buy.moonpay.com'; @@ -109,8 +108,8 @@ class MoonPayBuyProvider extends BuyProvider { _quoteSuffix + '/?apiKey=' + _apiKey + '&baseCurrencyAmount=' + amount + '&baseCurrencyCode=' + sourceCurrency.toLowerCase(); - - final response = await get(url); + final uri = Uri.parse(url); + final response = await get(uri); if (response.statusCode != 200) { throw BuyException( @@ -133,8 +132,8 @@ class MoonPayBuyProvider extends BuyProvider { Future findOrderById(String id) async { final url = _apiUrl + _transactionsSuffix + '/$id' + '?apiKey=' + _apiKey; - - final response = await get(url); + final uri = Uri.parse(url); + final response = await get(uri); if (response.statusCode != 200) { throw BuyException( @@ -164,8 +163,8 @@ class MoonPayBuyProvider extends BuyProvider { static Future onEnabled() async { final url = _apiUrl + _ipAddressSuffix + '?apiKey=' + _apiKey; var isBuyEnable = false; - - final response = await get(url); + final uri = Uri.parse(url); + final response = await get(uri); try { final responseJSON = json.decode(response.body) as Map; diff --git a/lib/buy/order.dart b/lib/buy/order.dart index 24c5f58db..16dfbb4e0 100644 --- a/lib/buy/order.dart +++ b/lib/buy/order.dart @@ -8,18 +8,23 @@ part 'order.g.dart'; @HiveType(typeId: Order.typeId) class Order extends HiveObject { Order( - {this.id, - BuyProviderDescription provider, - this.transferId, + {required this.id, + required this.transferId, + required this.createdAt, + required this.amount, + required this.receiveAddress, + required this.walletId, + BuyProviderDescription? provider, + TradeState? state, this.from, - this.to, - TradeState state, - this.createdAt, - this.amount, - this.receiveAddress, - this.walletId}) - : providerRaw = provider?.raw, - stateRaw = state?.raw; + this.to}) { + if (provider != null) { + providerRaw = provider.raw; + } + if (state != null) { + stateRaw = state.raw; + } + } static const typeId = 8; static const boxName = 'Orders'; @@ -32,13 +37,13 @@ class Order extends HiveObject { String transferId; @HiveField(2) - String from; + String? from; @HiveField(3) - String to; + String? to; @HiveField(4) - String stateRaw; + late String stateRaw; TradeState get state => TradeState.deserialize(raw: stateRaw); @@ -55,7 +60,7 @@ class Order extends HiveObject { String walletId; @HiveField(9) - int providerRaw; + late int providerRaw; BuyProviderDescription get provider => BuyProviderDescription.deserialize(raw: providerRaw); diff --git a/lib/buy/wyre/wyre_buy_provider.dart b/lib/buy/wyre/wyre_buy_provider.dart index aac8c3db6..652c92f58 100644 --- a/lib/buy/wyre/wyre_buy_provider.dart +++ b/lib/buy/wyre/wyre_buy_provider.dart @@ -11,12 +11,11 @@ import 'package:cake_wallet/exchange/trade_state.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; class WyreBuyProvider extends BuyProvider { - WyreBuyProvider({WalletBase wallet, bool isTestEnvironment = false}) - : super(wallet: wallet, isTestEnvironment: isTestEnvironment) { - baseApiUrl = isTestEnvironment + WyreBuyProvider({required WalletBase wallet, bool isTestEnvironment = false}) + : baseApiUrl = isTestEnvironment ? _baseTestApiUrl - : _baseProductApiUrl; - } + : _baseProductApiUrl, + super(wallet: wallet, isTestEnvironment: isTestEnvironment); static const _baseTestApiUrl = 'https://api.testwyre.com'; static const _baseProductApiUrl = 'https://api.sendwyre.com'; @@ -50,6 +49,7 @@ class WyreBuyProvider extends BuyProvider { final timestamp = DateTime.now().millisecondsSinceEpoch.toString(); final url = baseApiUrl + _ordersSuffix + _reserveSuffix + _timeStampSuffix + timestamp; + final uri = Uri.parse(url); final body = { 'amount': amount, 'sourceCurrency': sourceCurrency, @@ -58,8 +58,7 @@ class WyreBuyProvider extends BuyProvider { 'referrerAccountId': _accountId, 'lockFields': ['amount', 'sourceCurrency', 'destCurrency', 'dest'] }; - - final response = await post(url, + final response = await post(uri, headers: { 'Authorization': 'Bearer $_secretKey', 'Content-Type': 'application/json', @@ -89,8 +88,8 @@ class WyreBuyProvider extends BuyProvider { 'accountId': _accountId, 'country': _countryCode }; - - final response = await post(quoteUrl, + final uri = Uri.parse(quoteUrl); + final response = await post(uri, headers: { 'Authorization': 'Bearer $_secretKey', 'Content-Type': 'application/json', @@ -115,7 +114,8 @@ class WyreBuyProvider extends BuyProvider { @override Future findOrderById(String id) async { final orderUrl = baseApiUrl + _ordersSuffix + '/$id'; - final orderResponse = await get(orderUrl); + final orderUri = Uri.parse(orderUrl); + final orderResponse = await get(orderUri); if (orderResponse.statusCode != 200) { throw BuyException( @@ -136,7 +136,8 @@ class WyreBuyProvider extends BuyProvider { final transferUrl = baseApiUrl + _transferSuffix + transferId + _trackSuffix; - final transferResponse = await get(transferUrl); + final transferUri = Uri.parse(transferUrl); + final transferResponse = await get(transferUri); if (transferResponse.statusCode != 200) { throw BuyException( diff --git a/lib/core/address_label_validator.dart b/lib/core/address_label_validator.dart index 2b30272db..908e3a2e8 100644 --- a/lib/core/address_label_validator.dart +++ b/lib/core/address_label_validator.dart @@ -3,7 +3,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cw_core/wallet_type.dart'; class AddressLabelValidator extends TextValidator { - AddressLabelValidator({WalletType type}) + AddressLabelValidator({WalletType? type}) : super( errorMessage: S.current.error_text_subaddress_name, pattern: '''^[^`,'"]{1,20}\$''', diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index 9ef8d3340..e3c4ed334 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -4,7 +4,7 @@ import 'package:cake_wallet/core/validator.dart'; import 'package:cw_core/crypto_currency.dart'; class AddressValidator extends TextValidator { - AddressValidator({@required CryptoCurrency type}) + AddressValidator({required CryptoCurrency type}) : super( errorMessage: S.current.error_text_address, pattern: getPattern(type), @@ -79,7 +79,7 @@ class AddressValidator extends TextValidator { } } - static List getLength(CryptoCurrency type) { + static List? getLength(CryptoCurrency type) { switch (type) { case CryptoCurrency.xmr: return null; diff --git a/lib/core/amount.dart b/lib/core/amount.dart index 62e59db18..3d64f1d66 100644 --- a/lib/core/amount.dart +++ b/lib/core/amount.dart @@ -1,37 +1,37 @@ -abstract class Amount { - Amount(this.value); +// abstract class Amount { +// Amount(this.value); - int value; +// int value; - int minorDigits; +// int minorDigits; - String code; +// String code; - String formatted(); -} +// String formatted(); +// } -class MoneroAmount extends Amount { - MoneroAmount(int value) : super(value) { - minorDigits = 12; - code = 'XMR'; - } +// class MoneroAmount extends Amount { +// MoneroAmount(int value) : super(value) { +// minorDigits = 12; +// code = 'XMR'; +// } - // const moneroAmountLength = 12; - // const moneroAmountDivider = 1000000000000; - // final moneroAmountFormat = NumberFormat() - // ..maximumFractionDigits = moneroAmountLength - // ..minimumFractionDigits = 1; +// // const moneroAmountLength = 12; +// // const moneroAmountDivider = 1000000000000; +// // final moneroAmountFormat = NumberFormat() +// // ..maximumFractionDigits = moneroAmountLength +// // ..minimumFractionDigits = 1; - // String moneroAmountToString({int amount}) => - // moneroAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: moneroAmountDivider)); +// // String moneroAmountToString({int amount}) => +// // moneroAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: moneroAmountDivider)); - // double moneroAmountToDouble({int amount}) => cryptoAmountToDouble(amount: amount, divider: moneroAmountDivider); +// // double moneroAmountToDouble({int amount}) => cryptoAmountToDouble(amount: amount, divider: moneroAmountDivider); - // int moneroParseAmount({String amount}) => moneroAmountFormat.parse(amount).toInt(); +// // int moneroParseAmount({String amount}) => moneroAmountFormat.parse(amount).toInt(); - @override - String formatted() { - // TODO: implement formatted - throw UnimplementedError(); - } -} +// @override +// String formatted() { +// // TODO: implement formatted +// throw UnimplementedError(); +// } +// } diff --git a/lib/core/amount_converter.dart b/lib/core/amount_converter.dart index b3c976e25..a11907ef2 100644 --- a/lib/core/amount_converter.dart +++ b/lib/core/amount_converter.dart @@ -47,7 +47,7 @@ class AmountConverter { case CryptoCurrency.xusd: return _moneroAmountToDouble(amount); default: - return null; + return 0.0; } } @@ -71,7 +71,7 @@ class AmountConverter { case CryptoCurrency.xusd: return _moneroParseAmount(amount); default: - return null; + return 0; } } @@ -97,11 +97,11 @@ class AmountConverter { case CryptoCurrency.xusd: return _moneroAmountToString(amount); default: - return null; + return ''; } } - static double cryptoAmountToDouble({num amount, num divider}) => + static double cryptoAmountToDouble({required num amount, required num divider}) => amount / divider; static String _moneroAmountToString(int amount) => _moneroAmountFormat.format( diff --git a/lib/core/amount_validator.dart b/lib/core/amount_validator.dart index 52e75969f..3b14b0832 100644 --- a/lib/core/amount_validator.dart +++ b/lib/core/amount_validator.dart @@ -3,7 +3,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cw_core/wallet_type.dart'; class AmountValidator extends TextValidator { - AmountValidator({WalletType type, bool isAutovalidate = false}) + AmountValidator({required WalletType type, bool isAutovalidate = false}) : super( errorMessage: S.current.error_text_amount, pattern: _pattern(type), diff --git a/lib/core/auth_service.dart b/lib/core/auth_service.dart index f2c07d265..2ae37e2b0 100644 --- a/lib/core/auth_service.dart +++ b/lib/core/auth_service.dart @@ -6,12 +6,12 @@ import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cake_wallet/entities/encrypt.dart'; class AuthService with Store { - AuthService({this.secureStorage, this.sharedPreferences}); + AuthService({required this.secureStorage, required this.sharedPreferences}); final FlutterSecureStorage secureStorage; final SharedPreferences sharedPreferences; - Future setPassword(String password) async { + Future setPassword(String password) async { final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); final encodedPassword = encodedPinCode(pin: password); await secureStorage.write(key: key, value: encodedPassword); @@ -24,7 +24,7 @@ class AuthService with Store { var password = ''; try { - password = await secureStorage.read(key: key); + password = await secureStorage.read(key: key) ?? ''; } catch (e) { print(e); } @@ -35,7 +35,7 @@ class AuthService with Store { Future authenticate(String pin) async { final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); final encodedPin = await secureStorage.read(key: key); - final decodedPin = decodedPinCode(pin: encodedPin); + final decodedPin = decodedPinCode(pin: encodedPin!); return decodedPin == pin; } diff --git a/lib/core/auth_state.dart b/lib/core/auth_state.dart index 01e4c2576..44d83eb33 100644 --- a/lib/core/auth_state.dart +++ b/lib/core/auth_state.dart @@ -7,13 +7,13 @@ class AuthenticationInProgress extends AuthState {} class AuthenticatedSuccessfully extends AuthState {} class AuthenticationFailure extends AuthState { - AuthenticationFailure({this.error}); + AuthenticationFailure({required this.error}); final String error; } class AuthenticationBanned extends AuthState { - AuthenticationBanned({this.error}); + AuthenticationBanned({required this.error}); final String error; } diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index 227af2d6c..850e9560d 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -20,7 +20,8 @@ import 'package:cake_wallet/wallet_types.g.dart'; class BackupService { BackupService(this._flutterSecureStorage, this._walletInfoSource, this._keyService, this._sharedPreferences) - : _cipher = chacha20Poly1305Aead; + : _cipher = Cryptography.instance.chacha20Poly1305Aead(), + _correctWallets = []; static const currentVersion = _v1; @@ -54,7 +55,7 @@ class BackupService { case _v1: return await _exportBackupV1(password, nonce: nonce); default: - return null; + throw Exception('Incorrect version: $version for exportBackup'); } } @@ -91,8 +92,8 @@ class BackupService { }); await keychainDumpFile.writeAsBytes(keychainDump.toList()); await preferencesDumpFile.writeAsString(preferencesDump); - zipEncoder.addFile(preferencesDumpFile, '~_preferences_dump'); - zipEncoder.addFile(keychainDumpFile, '~_keychain_dump'); + await zipEncoder.addFile(preferencesDumpFile, '~_preferences_dump'); + await zipEncoder.addFile(keychainDumpFile, '~_keychain_dump'); zipEncoder.close(); final content = File(archivePath).readAsBytesSync(); @@ -103,7 +104,7 @@ class BackupService { } Future _importBackupV1(Uint8List data, String password, - {@required String nonce}) async { + {required String nonce}) async { final appDir = await getApplicationDocumentsDirectory(); final decryptedData = await _decrypt(data, password, nonce); final zip = ZipDecoder().decodeBytes(decryptedData); @@ -214,7 +215,7 @@ class BackupService { } Future _importKeychainDump(String password, - {@required String nonce, + {required String nonce, String keychainSalt = secrets.backupKeychainSalt}) async { final appDir = await getApplicationDocumentsDirectory(); final keychainDumpFile = File('${appDir.path}/~_keychain_dump'); @@ -251,11 +252,11 @@ class BackupService { } Future _exportKeychainDump(String password, - {@required String nonce, + {required String nonce, String keychainSalt = secrets.backupKeychainSalt}) async { final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); final encodedPin = await _flutterSecureStorage.read(key: key); - final decodedPin = decodedPinCode(pin: encodedPin); + final decodedPin = decodedPinCode(pin: encodedPin!); final wallets = await Future.wait(_walletInfoSource.values.map((walletInfo) async { return { @@ -281,41 +282,42 @@ class BackupService { } Future _exportPreferencesJSON() async { + // FIX-ME: Force unwrap final preferences = { PreferencesKey.currentWalletName: - _sharedPreferences.getString(PreferencesKey.currentWalletName), + _sharedPreferences.getString(PreferencesKey.currentWalletName)!, PreferencesKey.currentNodeIdKey: - _sharedPreferences.getInt(PreferencesKey.currentNodeIdKey), + _sharedPreferences.getInt(PreferencesKey.currentNodeIdKey)!, PreferencesKey.currentBalanceDisplayModeKey: _sharedPreferences - .getInt(PreferencesKey.currentBalanceDisplayModeKey), + .getInt(PreferencesKey.currentBalanceDisplayModeKey)!, PreferencesKey.currentWalletType: - _sharedPreferences.getInt(PreferencesKey.currentWalletType), + _sharedPreferences.getInt(PreferencesKey.currentWalletType)!, PreferencesKey.currentFiatCurrencyKey: - _sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey), + _sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey)!, PreferencesKey.shouldSaveRecipientAddressKey: _sharedPreferences - .getBool(PreferencesKey.shouldSaveRecipientAddressKey), + .getBool(PreferencesKey.shouldSaveRecipientAddressKey)!, PreferencesKey.isDarkThemeLegacy: - _sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy), + _sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy)!, PreferencesKey.currentPinLength: - _sharedPreferences.getInt(PreferencesKey.currentPinLength), + _sharedPreferences.getInt(PreferencesKey.currentPinLength)!, PreferencesKey.currentTransactionPriorityKeyLegacy: _sharedPreferences - .getInt(PreferencesKey.currentTransactionPriorityKeyLegacy), + .getInt(PreferencesKey.currentTransactionPriorityKeyLegacy)!, PreferencesKey.allowBiometricalAuthenticationKey: _sharedPreferences - .getBool(PreferencesKey.allowBiometricalAuthenticationKey), + .getBool(PreferencesKey.allowBiometricalAuthenticationKey)!, PreferencesKey.currentBitcoinElectrumSererIdKey: _sharedPreferences - .getInt(PreferencesKey.currentBitcoinElectrumSererIdKey), + .getInt(PreferencesKey.currentBitcoinElectrumSererIdKey)!, PreferencesKey.currentLanguageCode: - _sharedPreferences.getString(PreferencesKey.currentLanguageCode), + _sharedPreferences.getString(PreferencesKey.currentLanguageCode)!, PreferencesKey.displayActionListModeKey: - _sharedPreferences.getInt(PreferencesKey.displayActionListModeKey), + _sharedPreferences.getInt(PreferencesKey.displayActionListModeKey)!, PreferencesKey.currentTheme: - _sharedPreferences.getInt(PreferencesKey.currentTheme), + _sharedPreferences.getInt(PreferencesKey.currentTheme)!, PreferencesKey.currentDefaultSettingsMigrationVersion: _sharedPreferences - .getInt(PreferencesKey.currentDefaultSettingsMigrationVersion), + .getInt(PreferencesKey.currentDefaultSettingsMigrationVersion)!, PreferencesKey.bitcoinTransactionPriority: - _sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority), + _sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority)!, PreferencesKey.moneroTransactionPriority: - _sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority), + _sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!, }; return json.encode(preferences); @@ -330,17 +332,26 @@ class BackupService { Future _encrypt( Uint8List data, String secretKeySource, String nonceBase64) async { - final secretKeyHash = await sha256.hash(utf8.encode(secretKeySource)); + final secretKeyHash = await Cryptography.instance.sha256().hash(utf8.encode(secretKeySource)); final secretKey = SecretKey(secretKeyHash.bytes); - final nonce = Nonce(base64.decode(nonceBase64)); - return await _cipher.encrypt(data, secretKey: secretKey, nonce: nonce); + final nonce = base64.decode(nonceBase64).toList(); + final box = await _cipher.encrypt(data.toList(), secretKey: secretKey, nonce: nonce); + return Uint8List.fromList(box.cipherText); } Future _decrypt( Uint8List data, String secretKeySource, String nonceBase64) async { - final secretKeyHash = await sha256.hash(utf8.encode(secretKeySource)); - final secretKey = SecretKey(secretKeyHash.bytes); - final nonce = Nonce(base64.decode(nonceBase64)); - return await _cipher.decrypt(data, secretKey: secretKey, nonce: nonce); + throw Exception('Unimplemented'); + //final secretKeyHash = await sha256.hash(utf8.encode(secretKeySource)); + //final secretKey = SecretKey(secretKeyHash.bytes); + //final nonce = Nonce(base64.decode(nonceBase64)); + //return await _cipher.decrypt(data, secretKey: secretKey, nonce: nonce); + + // final secretKeyHash = await Cryptography.instance.sha256().hash(utf8.encode(secretKeySource)); + // final secretKey = SecretKey(secretKeyHash.bytes); + // final nonce = base64.decode(nonceBase64).toList(); + // final box = SecretBox(data.toList(), nonce: nonce, mac: Mac); + // final plainData = await _cipher.decrypt(box, secretKey: secretKey); + // return Uint8List.fromList(plainData); } } diff --git a/lib/core/fiat_conversion_service.dart b/lib/core/fiat_conversion_service.dart index 7a0513d5c..f4ec3775b 100644 --- a/lib/core/fiat_conversion_service.dart +++ b/lib/core/fiat_conversion_service.dart @@ -16,7 +16,7 @@ Future _fetchPrice(Map args) async { final fiatStringified = fiat.toString(); final uri = Uri.https(fiatApiAuthority, fiatApiPath, {'convert': fiatStringified}); - final response = await get(uri.toString()); + final response = await get(uri); if (response.statusCode != 200) { return 0.0; diff --git a/lib/core/key_service.dart b/lib/core/key_service.dart index eafca2b0a..1fe99623e 100644 --- a/lib/core/key_service.dart +++ b/lib/core/key_service.dart @@ -7,15 +7,14 @@ class KeyService { final FlutterSecureStorage _secureStorage; - Future getWalletPassword({String walletName}) async { + Future getWalletPassword({required String walletName}) async { final key = generateStoreKeyFor( key: SecretStoreKey.moneroWalletPassword, walletName: walletName); final encodedPassword = await _secureStorage.read(key: key); - - return decodeWalletPassword(password: encodedPassword); + return decodeWalletPassword(password: encodedPassword!); } - Future saveWalletPassword({String walletName, String password}) async { + Future saveWalletPassword({required String walletName, required String password}) async { final key = generateStoreKeyFor( key: SecretStoreKey.moneroWalletPassword, walletName: walletName); final encodedPassword = encodeWalletPassword(password: password); diff --git a/lib/core/monero_account_label_validator.dart b/lib/core/monero_account_label_validator.dart index 7e287959b..ef4cf0f9a 100644 --- a/lib/core/monero_account_label_validator.dart +++ b/lib/core/monero_account_label_validator.dart @@ -4,7 +4,7 @@ import 'package:cake_wallet/core/validator.dart'; import 'package:cw_core/crypto_currency.dart'; class MoneroLabelValidator extends TextValidator { - MoneroLabelValidator({@required CryptoCurrency type}) + MoneroLabelValidator() : super( errorMessage: S.current.error_text_account_name, pattern: '^[a-zA-Z0-9_ ]{1,15}\$', diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index e1e5920fd..fe9a25f85 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -7,23 +7,24 @@ import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/utils/language_list.dart'; class SeedValidator extends Validator { - SeedValidator({this.type, this.language}) - : _words = getWordList(type: type, language: language); + SeedValidator({required this.type, required this.language}) + : _words = getWordList(type: type, language: language), + super(errorMessage: 'Wrong seed mnemonic'); final WalletType type; final String language; final List _words; - static List getWordList({WalletType type, String language}) { + static List getWordList({required WalletType type, required String language}) { switch (type) { case WalletType.bitcoin: return getBitcoinWordList(language); case WalletType.litecoin: return getBitcoinWordList(language); case WalletType.monero: - return monero.getMoneroWordList(language); + return monero!.getMoneroWordList(language); case WalletType.haven: - return haven.getMoneroWordList(language); + return haven!.getMoneroWordList(language); default: return []; } @@ -31,9 +32,9 @@ class SeedValidator extends Validator { static List getBitcoinWordList(String language) { assert(language.toLowerCase() == LanguageList.english.toLowerCase()); - return bitcoin.getWordList(); + return bitcoin!.getWordList(); } @override - bool isValid(MnemonicItem value) => _words.contains(value.text); + bool isValid(MnemonicItem? value) => _words.contains(value?.text); } diff --git a/lib/core/sync_status_title.dart b/lib/core/sync_status_title.dart index e6b04c245..e46ec2490 100644 --- a/lib/core/sync_status_title.dart +++ b/lib/core/sync_status_title.dart @@ -33,4 +33,6 @@ String syncStatusTitle(SyncStatus syncStatus) { if (syncStatus is LostConnectionSyncStatus) { return S.current.sync_status_failed_connect; } + + return ''; } \ No newline at end of file diff --git a/lib/core/validator.dart b/lib/core/validator.dart index 055439347..96c382b7d 100644 --- a/lib/core/validator.dart +++ b/lib/core/validator.dart @@ -1,13 +1,13 @@ import 'package:flutter/foundation.dart'; abstract class Validator { - Validator({@required this.errorMessage}); + Validator({required this.errorMessage}); final String errorMessage; - bool isValid(T value); + bool isValid(T? value); - String call(T value) => !isValid(value) ? errorMessage : null; + String? call(T? value) => !isValid(value) ? errorMessage : null; } class TextValidator extends Validator { @@ -15,28 +15,28 @@ class TextValidator extends Validator { {this.minLength, this.maxLength, this.pattern, + String errorMessage = '', this.length, - this.isAutovalidate = false, - String errorMessage}) + this.isAutovalidate = false}) : super(errorMessage: errorMessage); - final int minLength; - final int maxLength; - final List length; + final int? minLength; + final int? maxLength; + final List? length; final bool isAutovalidate; - String pattern; + String? pattern; @override - bool isValid(String value) { + bool isValid(String? value) { if (value == null || value.isEmpty) { return isAutovalidate ? true : false; } return value.length > (minLength ?? 0) && (length?.contains(value.length) ?? true) && - ((maxLength ?? 0) > 0 ? (value.length <= maxLength) : true) && + ((maxLength ?? 0) > 0 ? (value.length <= maxLength!) : true) && (pattern != null ? match(value) : true); } - bool match(String value) => RegExp(pattern).hasMatch(value); + bool match(String value) => pattern != null ? RegExp(pattern!).hasMatch(value) : false; } diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart index 1fe36f374..3b28f36c3 100644 --- a/lib/core/wallet_creation_service.dart +++ b/lib/core/wallet_creation_service.dart @@ -14,15 +14,13 @@ import 'package:cw_core/wallet_type.dart'; class WalletCreationService { WalletCreationService( - {WalletType initialType, - this.secureStorage, - this.keyService, - this.sharedPreferences, - this.walletInfoSource}) + {required WalletType initialType, + required this.secureStorage, + required this.keyService, + required this.sharedPreferences, + required this.walletInfoSource}) : type = initialType { - if (type != null) { - changeWalletType(type: type); - } + changeWalletType(type: type); } WalletType type; @@ -30,11 +28,11 @@ class WalletCreationService { final SharedPreferences sharedPreferences; final KeyService keyService; final Box walletInfoSource; - WalletService _service; + WalletService? _service; static const _isNewMoneroWalletPasswordUpdated = true; - void changeWalletType({@required WalletType type}) { + void changeWalletType({required WalletType type}) { this.type = type; _service = getIt.get(param1: type); } @@ -64,7 +62,7 @@ class WalletCreationService { credentials.password = password; await keyService.saveWalletPassword( password: password, walletName: credentials.name); - final wallet = await _service.create(credentials); + final wallet = await _service!.create(credentials); if (wallet.type == WalletType.monero) { await sharedPreferences @@ -82,7 +80,7 @@ class WalletCreationService { credentials.password = password; await keyService.saveWalletPassword( password: password, walletName: credentials.name); - final wallet = await _service.restoreFromKeys(credentials); + final wallet = await _service!.restoreFromKeys(credentials); if (wallet.type == WalletType.monero) { await sharedPreferences @@ -100,7 +98,7 @@ class WalletCreationService { credentials.password = password; await keyService.saveWalletPassword( password: password, walletName: credentials.name); - final wallet = await _service.restoreFromSeed(credentials); + final wallet = await _service!.restoreFromSeed(credentials); if (wallet.type == WalletType.monero) { await sharedPreferences diff --git a/lib/core/wallet_creation_state.dart b/lib/core/wallet_creation_state.dart index c732a1899..728bc477f 100644 --- a/lib/core/wallet_creation_state.dart +++ b/lib/core/wallet_creation_state.dart @@ -7,7 +7,7 @@ class WalletCreating extends WalletCreationState {} class WalletCreatedSuccessfully extends WalletCreationState {} class WalletCreationFailure extends WalletCreationState { - WalletCreationFailure({@required this.error}); + WalletCreationFailure({required this.error}); final String error; } \ No newline at end of file diff --git a/lib/core/wallet_loading_service.dart b/lib/core/wallet_loading_service.dart index 844e5c1ca..5bae5b346 100644 --- a/lib/core/wallet_loading_service.dart +++ b/lib/core/wallet_loading_service.dart @@ -17,18 +17,15 @@ class WalletLoadingService { final WalletService Function(WalletType type) walletServiceFactory; Future load(WalletType type, String name) async { - if (walletServiceFactory == null) { - throw Exception('WalletLoadingService.walletServiceFactory is not set'); - } - final walletService = walletServiceFactory?.call(type); + final walletService = walletServiceFactory.call(type); final password = await keyService.getWalletPassword(walletName: name); - final wallet = await walletService.openWallet(name, password); + final wallet = await walletService.openWallet(name, password); - if (type == WalletType.monero) { - await upateMoneroWalletPassword(wallet); - } + if (type == WalletType.monero) { + await upateMoneroWalletPassword(wallet); + } - return wallet; + return wallet; } Future upateMoneroWalletPassword(WalletBase wallet) async { diff --git a/lib/di.dart b/lib/di.dart index 5cc698d27..5a2eb7285 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -156,26 +156,26 @@ import 'package:cake_wallet/core/wallet_loading_service.dart'; final getIt = GetIt.instance; var _isSetupFinished = false; -Box _walletInfoSource; -Box _nodeSource; -Box _contactSource; -Box _tradesSource; -Box