diff --git a/asset_sources/svg/campfire/rsfiro.svg b/asset_sources/svg/campfire/rsfiro.svg new file mode 100644 index 0000000000..2fb405f45e --- /dev/null +++ b/asset_sources/svg/campfire/rsfiro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/asset_sources/svg/stack_duo/rsfiro.svg b/asset_sources/svg/stack_duo/rsfiro.svg new file mode 100644 index 0000000000..3f1f885e17 --- /dev/null +++ b/asset_sources/svg/stack_duo/rsfiro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/asset_sources/svg/stack_wallet/rsfiro.svg b/asset_sources/svg/stack_wallet/rsfiro.svg new file mode 100644 index 0000000000..2fb405f45e --- /dev/null +++ b/asset_sources/svg/stack_wallet/rsfiro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/app_config.dart b/lib/app_config.dart index 4e926ad8ea..d594cf5d9e 100644 --- a/lib/app_config.dart +++ b/lib/app_config.dart @@ -1,6 +1,8 @@ // ignore: unused_import import 'dart:io'; +import 'models/isar/models/ethereum/eth_contract.dart'; +import 'utilities/default_eth_tokens.dart'; import 'wallets/crypto_currency/crypto_currency.dart'; import 'wallets/crypto_currency/intermediate/frost_currency.dart'; @@ -28,6 +30,8 @@ abstract class AppConfig { static List get coins => _supportedCoins; + static List get defaultEthTokens => _defaultEthTokens; + static ({String from, String fromFuzzyNet, String to, String toFuzzyNet}) get swapDefaults => _swapDefaults; diff --git a/lib/db/db_version_migration.dart b/lib/db/db_version_migration.dart index 2df1213c5c..15ad3b9bcb 100644 --- a/lib/db/db_version_migration.dart +++ b/lib/db/db_version_migration.dart @@ -24,6 +24,7 @@ import '../services/mixins/wallet_db.dart'; import '../services/wallets_service.dart'; import '../utilities/amount/amount.dart'; import '../utilities/constants.dart'; +import '../utilities/default_eth_tokens.dart'; import '../utilities/flutter_secure_storage_interface.dart'; import '../utilities/logger.dart'; import '../utilities/prefs.dart'; @@ -379,9 +380,8 @@ class DbVersionMigrator with WalletDB { final mwcMigrationWalletsService = WalletsService(); final mwcMigrationWalletNames = await mwcMigrationWalletsService.walletNames; - final mwcIdentifier = Mimblewimblecoin( - CryptoCurrencyNetwork.main, - ).identifier; + final mwcIdentifier = Mimblewimblecoin(CryptoCurrencyNetwork.main) + .identifier; for (final walletId in mwcMigrationWalletNames.keys) { if (mwcMigrationWalletNames[walletId]!.coinIdentifier == mwcIdentifier) { @@ -399,6 +399,30 @@ class DbVersionMigrator with WalletDB { // try to continue migrating return await migrate(16, secureStore: secureStore); + case 16: + if (AppConfig.defaultEthTokens.contains(DefaultTokens.rsFiro)) { + final db = MainDB.instance; + await db.initMainDB(); + if (await db.getEthContracts().isNotEmpty() && + await db + .getEthContracts() + .filter() + .addressEqualTo( + DefaultTokens.rsFiro.address, + caseSensitive: false, + ) + .isEmpty()) { + await db.putEthContract(DefaultTokens.rsFiro); + } + } + + await DB.instance.put( + boxName: DB.boxNameDBInfo, + key: "hive_data_version", + value: 17, + ); + return await migrate(17, secureStore: secureStore); + default: // finally return return; diff --git a/lib/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart b/lib/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart index d81afe51e5..749f2351f3 100644 --- a/lib/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart +++ b/lib/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart @@ -15,6 +15,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:isar_community/isar.dart'; +import '../../../app_config.dart'; import '../../../db/isar/main_db.dart'; import '../../../models/isar/models/ethereum/eth_contract.dart'; import '../../../models/isar/models/solana/sol_contract.dart'; @@ -25,7 +26,6 @@ import '../../../providers/global/wallets_provider.dart'; import '../../../themes/stack_colors.dart'; import '../../../utilities/assets.dart'; import '../../../utilities/constants.dart'; -import '../../../utilities/default_eth_tokens.dart'; import '../../../utilities/default_sol_tokens.dart'; import '../../../utilities/text_styles.dart'; import '../../../utilities/util.dart'; @@ -100,11 +100,10 @@ class _EditWalletTokensViewState extends ConsumerState { } Future onNextPressed() async { - final selectedTokens = - tokenEntities - .where((e) => e.selected) - .map((e) => e.token.address) - .toList(); + final selectedTokens = tokenEntities + .where((e) => e.selected) + .map((e) => e.token.address) + .toList(); final wallet = ref.read(pWallets).getWallet(widget.walletId); @@ -119,13 +118,11 @@ class _EditWalletTokensViewState extends ConsumerState { Navigator.of(context).pop(42); } else { if (isDesktop) { - Navigator.of( - context, - ).popUntil(ModalRoute.withName(DesktopHomeView.routeName)); + Navigator.of(context) + .popUntil(ModalRoute.withName(DesktopHomeView.routeName)); } else { - await Navigator.of( - context, - ).pushNamedAndRemoveUntil(HomeView.routeName, (route) => false); + await Navigator.of(context) + .pushNamedAndRemoveUntil(HomeView.routeName, (route) => false); } if (mounted) { unawaited( @@ -160,9 +157,8 @@ class _EditWalletTokensViewState extends ConsumerState { ), ); } else { - final result = await Navigator.of( - context, - ).pushNamed(AddCustomTokenView.routeName); + final result = await Navigator.of(context) + .pushNamed(AddCustomTokenView.routeName); contract = result as EthContract?; } @@ -177,7 +173,9 @@ class _EditWalletTokensViewState extends ConsumerState { tokenEntities.add( AddTokenListElementData(contract!)..selected = true, ); - tokenEntities.sort((a, b) => a.token.name.compareTo(b.token.name)); + tokenEntities.sort( + (a, b) => a.token.name.compareTo(b.token.name), + ); } }); } @@ -199,9 +197,7 @@ class _EditWalletTokensViewState extends ConsumerState { ), ); } else { - final result = await Navigator.of( - context, - ).pushNamed( + final result = await Navigator.of(context).pushNamed( AddCustomSolanaTokenView.routeName, arguments: widget.walletId, ); @@ -228,9 +224,7 @@ class _EditWalletTokensViewState extends ConsumerState { if (tokenEntities .where((e) => e.token.address == token!.address) .isEmpty) { - tokenEntities.add( - AddTokenListElementData(token!)..selected = true, - ); + tokenEntities.add(AddTokenListElementData(token!)..selected = true); tokenEntities.sort((a, b) => a.token.name.compareTo(b.token.name)); } }); @@ -268,7 +262,7 @@ class _EditWalletTokensViewState extends ConsumerState { .findAllSync(); if (contracts.isEmpty) { - contracts.addAll(DefaultTokens.list); + contracts.addAll(AppConfig.defaultEthTokens); MainDB.instance .putEthContracts(contracts) .then( @@ -448,9 +442,8 @@ class _EditWalletTokensViewState extends ConsumerState { _searchTerm = value; }); }, - style: STextStyles.desktopTextMedium( - context, - ).copyWith(height: 2), + style: STextStyles.desktopTextMedium(context) + .copyWith(height: 2), decoration: standardInputDecoration( "Search", @@ -516,9 +509,9 @@ class _EditWalletTokensViewState extends ConsumerState { } else { return Background( child: Scaffold( - backgroundColor: Theme.of( - context, - ).extension()!.background, + backgroundColor: Theme.of(context) + .extension()! + .background, appBar: AppBar( leading: AppBarBackButton( onPressed: () { @@ -533,14 +526,14 @@ class _EditWalletTokensViewState extends ConsumerState { child: AppBarIconButton( size: 36, shadows: const [], - color: Theme.of( - context, - ).extension()!.background, + color: Theme.of(context) + .extension()! + .background, icon: SvgPicture.asset( Assets.svg.circlePlusFilled, - color: Theme.of( - context, - ).extension()!.topNavIconPrimary, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, width: 20, height: 20, ), diff --git a/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart index eecf914d73..65088fbc94 100644 --- a/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart +++ b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart @@ -20,6 +20,7 @@ import '../../../../services/exchange/change_now/change_now_exchange.dart'; import '../../../../services/exchange/exchange_data_loading_service.dart'; import '../../../../themes/stack_colors.dart'; import '../../../../utilities/assets.dart'; +import '../../../../utilities/default_eth_tokens.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; import '../../../../widgets/app_icon.dart'; @@ -54,18 +55,14 @@ class _AddTokenListElementState extends ConsumerState { super.initState(); ExchangeDataLoadingService.instance.isar.then((isar) async { - final currency = - await isar.currencies - .where() - .exchangeNameEqualTo(ChangeNowExchange.exchangeName) - .filter() - .tokenContractEqualTo( - widget.data.token.address, - caseSensitive: false, - ) - .and() - .imageIsNotEmpty() - .findFirst(); + final currency = await isar.currencies + .where() + .exchangeNameEqualTo(ChangeNowExchange.exchangeName) + .filter() + .tokenContractEqualTo(widget.data.token.address, caseSensitive: false) + .and() + .imageIsNotEmpty() + .findFirst(); if (mounted) { WidgetsBinding.instance.addPostFrameCallback((_) { @@ -86,50 +83,54 @@ class _AddTokenListElementState extends ConsumerState { return RoundedWhiteContainer( padding: EdgeInsets.all(isDesktop ? 16 : 12), - borderColor: - isDesktop - ? Theme.of(context).extension()!.backgroundAppBar - : null, + borderColor: isDesktop + ? Theme.of(context).extension()!.backgroundAppBar + : null, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ - currency != null - ? SvgPicture.network( - currency!.image, - width: iconSize, - height: iconSize, - placeholderBuilder: - (_) => AppIcon(width: iconSize, height: iconSize), - ) - : AppIcon(width: iconSize, height: iconSize), + if (widget.data.token is EthContract && + widget.data.token.address.toLowerCase() == + DefaultTokens.rsFiro.address) + SvgPicture.asset( + Assets.svg.rsFiro, + width: iconSize, + height: iconSize, + ) + else + currency != null + ? SvgPicture.network( + currency!.image, + width: iconSize, + height: iconSize, + placeholderBuilder: (_) => + AppIcon(width: iconSize, height: iconSize), + ) + : AppIcon(width: iconSize, height: iconSize), const SizedBox(width: 12), ConditionalParent( condition: isDesktop, - builder: - (child) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - child, - const SizedBox(height: 2), - Text( - widget.data.token.symbol, - style: STextStyles.desktopTextExtraExtraSmall( - context, - ), - overflow: TextOverflow.ellipsis, - ), - ], + builder: (child) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + child, + const SizedBox(height: 2), + Text( + widget.data.token.symbol, + style: STextStyles.desktopTextExtraExtraSmall(context), + overflow: TextOverflow.ellipsis, ), + ], + ), child: Text( isDesktop ? mainLabel : "$mainLabel (${widget.data.token.symbol})", - style: - isDesktop - ? STextStyles.desktopTextSmall(context) - : STextStyles.w600_14(context), + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w600_14(context), overflow: TextOverflow.ellipsis, ), ), @@ -138,21 +139,20 @@ class _AddTokenListElementState extends ConsumerState { const SizedBox(width: 4), isDesktop ? Checkbox( - value: widget.data.selected, - onChanged: - (newValue) => - setState(() => widget.data.selected = newValue!), - ) + value: widget.data.selected, + onChanged: (newValue) => + setState(() => widget.data.selected = newValue!), + ) : SizedBox( - height: 20, - width: 40, - child: DraggableSwitchButton( - isOn: widget.data.selected, - onValueChanged: (newValue) { - widget.data.selected = newValue; - }, + height: 20, + width: 40, + child: DraggableSwitchButton( + isOn: widget.data.selected, + onValueChanged: (newValue) { + widget.data.selected = newValue; + }, + ), ), - ), ], ), ); diff --git a/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart b/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart index 13cb7a022e..6c8b8079c8 100644 --- a/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart +++ b/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart @@ -28,7 +28,6 @@ import '../../../providers/providers.dart'; import '../../../themes/stack_colors.dart'; import '../../../utilities/assets.dart'; import '../../../utilities/constants.dart'; -import '../../../utilities/default_eth_tokens.dart'; import '../../../utilities/default_sol_tokens.dart'; import '../../../utilities/text_styles.dart'; import '../../../utilities/util.dart'; @@ -103,17 +102,15 @@ class _AddWalletViewState extends ConsumerState { if (isDesktop) { contract = await showDialog( context: context, - builder: - (context) => const DesktopDialog( - maxWidth: 580, - maxHeight: 500, - child: AddCustomTokenView(), - ), + builder: (context) => const DesktopDialog( + maxWidth: 580, + maxHeight: 500, + child: AddCustomTokenView(), + ), ); } else { - contract = await Navigator.of( - context, - ).pushNamed(AddCustomTokenView.routeName); + contract = await Navigator.of(context) + .pushNamed(AddCustomTokenView.routeName); } if (contract != null) { @@ -137,17 +134,15 @@ class _AddWalletViewState extends ConsumerState { if (isDesktop) { token = await showDialog( context: context, - builder: - (context) => const DesktopDialog( - maxWidth: 580, - maxHeight: 500, - child: AddCustomSolanaTokenView(), - ), + builder: (context) => const DesktopDialog( + maxWidth: 580, + maxHeight: 500, + child: AddCustomSolanaTokenView(), + ), ); } else { - token = await Navigator.of( - context, - ).pushNamed(AddCustomSolanaTokenView.routeName); + token = await Navigator.of(context) + .pushNamed(AddCustomSolanaTokenView.routeName); } if (token != null) { @@ -176,11 +171,13 @@ class _AddWalletViewState extends ConsumerState { } if (AppConfig.coins.whereType().isNotEmpty) { - final contracts = - MainDB.instance.getEthContracts().sortByName().findAllSync(); + final contracts = MainDB.instance + .getEthContracts() + .sortByName() + .findAllSync(); if (contracts.isEmpty) { - contracts.addAll(DefaultTokens.list); + contracts.addAll(AppConfig.defaultEthTokens); MainDB.instance .putEthContracts(contracts) .then( @@ -274,60 +271,60 @@ class _AddWalletViewState extends ConsumerState { _searchTerm = value; }); }, - style: STextStyles.desktopTextMedium( - context, - ).copyWith(height: 2), - decoration: standardInputDecoration( - "Search", - _searchFocusNode, - context, - ).copyWith( - contentPadding: const EdgeInsets.symmetric( - vertical: 10, - ), - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - // vertical: 20, - ), - child: SvgPicture.asset( - Assets.svg.search, - width: 24, - height: 24, - color: - Theme.of(context) + style: STextStyles.desktopTextMedium(context) + .copyWith(height: 2), + decoration: + standardInputDecoration( + "Search", + _searchFocusNode, + context, + ).copyWith( + contentPadding: const EdgeInsets.symmetric( + vertical: 10, + ), + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + // vertical: 20, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 24, + height: 24, + color: Theme.of(context) .extension()! .textFieldDefaultSearchIconLeft, - ), - ), - suffixIcon: - _searchFieldController.text.isNotEmpty + ), + ), + suffixIcon: + _searchFieldController.text.isNotEmpty ? Padding( - padding: const EdgeInsets.only( - right: 10, - ), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon( - width: 24, - height: 24, + padding: const EdgeInsets.only( + right: 10, + ), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon( + width: 24, + height: 24, + ), + onTap: () async { + setState(() { + _searchFieldController + .text = + ""; + _searchTerm = ""; + }); + }, ), - onTap: () async { - setState(() { - _searchFieldController - .text = ""; - _searchTerm = ""; - }); - }, - ), - ], + ], + ), ), - ), - ) + ) : null, - ), + ), ), ), ), @@ -365,7 +362,10 @@ class _AddWalletViewState extends ConsumerState { if (solTokenEntities.isNotEmpty) ExpandingSubListItem( title: "Solana tokens", - entities: filter(_searchTerm, solTokenEntities), + entities: filter( + _searchTerm, + solTokenEntities, + ), initialState: ExpandableState.expanded, animationDurationMultiplier: 0.5, trailing: AddCustomTokenSelector( @@ -395,8 +395,9 @@ class _AddWalletViewState extends ConsumerState { } else { return Background( child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, + backgroundColor: Theme.of(context) + .extension()! + .background, appBar: AppBar( leading: AppBarBackButton( onPressed: () { @@ -419,8 +420,7 @@ class _AddWalletViewState extends ConsumerState { Constants.size.circularBorderRadius, ), child: Semantics( - label: - "Search Text Field. Inputs Text To Search In Wallets.", + label: "Search Text Field. Inputs Text To Search In Wallets.", excludeSemantics: true, child: TextField( autofocus: isDesktop, @@ -428,49 +428,53 @@ class _AddWalletViewState extends ConsumerState { enableSuggestions: !isDesktop, controller: _searchFieldController, focusNode: _searchFocusNode, - onChanged: - (value) => setState(() => _searchTerm = value), + onChanged: (value) => + setState(() => _searchTerm = value), style: STextStyles.field(context), - decoration: standardInputDecoration( - "Search", - _searchFocusNode, - context, - desktopMed: isDesktop, - ).copyWith( - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 16, - ), - child: SvgPicture.asset( - Assets.svg.search, - width: 16, - height: 16, - ), - ), - suffixIcon: - _searchFieldController.text.isNotEmpty + decoration: + standardInputDecoration( + "Search", + _searchFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, + ), + ), + suffixIcon: + _searchFieldController.text.isNotEmpty ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _searchFieldController.text = - ""; - _searchTerm = ""; - }); - }, - ), - ], + padding: const EdgeInsets.only( + right: 0, + ), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchFieldController + .text = + ""; + _searchTerm = ""; + }); + }, + ), + ], + ), ), - ), - ) + ) : null, - ), + ), ), ), ), diff --git a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart index 9254847fcb..95f3f4a790 100644 --- a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart +++ b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart @@ -26,6 +26,7 @@ import '../../../../themes/coin_icon_provider.dart'; import '../../../../themes/stack_colors.dart'; import '../../../../utilities/assets.dart'; import '../../../../utilities/constants.dart'; +import '../../../../utilities/default_eth_tokens.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; import '../../../../widgets/app_icon.dart'; @@ -137,40 +138,46 @@ class _CoinSelectItemState extends ConsumerState { constraints: BoxConstraints(minHeight: isDesktop ? 70 : 0), child: Row( children: [ - tokenImageUri != null - ? tokenImageUri!.toLowerCase().endsWith(".svg") - ? SvgPicture.network( - tokenImageUri!, - width: 26, - height: 26, - placeholderBuilder: (_) => - const AppIcon(width: 26, height: 26), - ) - : Image.network( - tokenImageUri!, - width: 26, - height: 26, - errorBuilder: (_, _, _) => SvgPicture.file( - File( - ref.watch( - coinIconProvider( - widget.entity.cryptoCurrency, + if (widget.entity is EthTokenEntity && + (widget.entity as EthTokenEntity).token.address + .toLowerCase() == + DefaultTokens.rsFiro.address) + SvgPicture.asset(Assets.svg.rsFiro, width: 26, height: 26) + else + tokenImageUri != null + ? tokenImageUri!.toLowerCase().endsWith(".svg") + ? SvgPicture.network( + tokenImageUri!, + width: 26, + height: 26, + placeholderBuilder: (_) => + const AppIcon(width: 26, height: 26), + ) + : Image.network( + tokenImageUri!, + width: 26, + height: 26, + errorBuilder: (_, _, _) => SvgPicture.file( + File( + ref.watch( + coinIconProvider( + widget.entity.cryptoCurrency, + ), ), ), + width: 26, + height: 26, ), - width: 26, - height: 26, - ), - ) - : SvgPicture.file( - File( - ref.watch( - coinIconProvider(widget.entity.cryptoCurrency), + ) + : SvgPicture.file( + File( + ref.watch( + coinIconProvider(widget.entity.cryptoCurrency), + ), ), + width: 26, + height: 26, ), - width: 26, - height: 26, - ), SizedBox(width: isDesktop ? 12 : 10), Text( "${widget.entity.name} (${widget.entity.ticker})", @@ -187,9 +194,9 @@ class _CoinSelectItemState extends ConsumerState { height: 24, child: SvgPicture.asset( Assets.svg.check, - color: Theme.of( - context, - ).extension()!.accentColorDark, + color: Theme.of(context) + .extension()! + .accentColorDark, ), ), ), diff --git a/lib/pages/shopinbit/shopinbit_payment_shared.dart b/lib/pages/shopinbit/shopinbit_payment_shared.dart index db5172ccd8..5eaef38019 100644 --- a/lib/pages/shopinbit/shopinbit_payment_shared.dart +++ b/lib/pages/shopinbit/shopinbit_payment_shared.dart @@ -20,9 +20,7 @@ import '../../widgets/background.dart'; import '../../widgets/custom_buttons/app_bar_icon_button.dart'; import 'shopinbit_send_from_view.dart'; -final String kShopInBitUsdtContractAddress = DefaultTokens.list - .firstWhere((t) => t.symbol == "USDT") - .address; +final String kShopInBitUsdtContractAddress = DefaultTokens.usdt.address; // Address + amount pulled out of one of the API's payment_links entries. class ShopInBitPaymentTarget { diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index ac6693a02e..509c9d6c01 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -117,6 +117,8 @@ class _SVG { final coinControl = const _COIN_CONTROL(); + String get rsFiro => "assets/svg/rsfiro.svg"; + String get peers => "assets/svg/peers.svg"; String get fusing => "assets/svg/fusing.svg"; String get upFromLine => "assets/svg/up-from-line.svg"; diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index f7b495ee06..51de11e062 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -40,7 +40,7 @@ abstract class Constants { // Enable Logger.print statements static const bool disableLogger = false; - static const int currentDataVersion = 16; + static const int currentDataVersion = 17; static const int rescanV1 = 1; diff --git a/lib/utilities/default_eth_tokens.dart b/lib/utilities/default_eth_tokens.dart index 63cd7f62b3..98ccbd42bf 100644 --- a/lib/utilities/default_eth_tokens.dart +++ b/lib/utilities/default_eth_tokens.dart @@ -11,41 +11,46 @@ import '../models/isar/models/ethereum/eth_contract.dart'; abstract class DefaultTokens { - static List list = [ - EthContract( - address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - name: "USD Coin", - symbol: "USDC", - decimals: 6, - type: EthContractType.erc20, - ), - EthContract( - address: "0xdac17f958d2ee523a2206206994597c13d831ec7", - name: "Tether", - symbol: "USDT", - decimals: 6, - type: EthContractType.erc20, - ), - EthContract( - address: "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce", - name: "Shiba Inu", - symbol: "SHIB", - decimals: 18, - type: EthContractType.erc20, - ), - EthContract( - address: "0x514910771af9ca656af840dff83e8264ecf986ca", - name: "Chainlink", - symbol: "LINK", - decimals: 18, - type: EthContractType.erc20, - ), - EthContract( - address: "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", - name: "Uniswap", - symbol: "UNI", - decimals: 18, - type: EthContractType.erc20, - ), - ]; + static final usdc = EthContract( + address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + name: "USD Coin", + symbol: "USDC", + decimals: 6, + type: EthContractType.erc20, + ); + static final usdt = EthContract( + address: "0xdac17f958d2ee523a2206206994597c13d831ec7", + name: "Tether", + symbol: "USDT", + decimals: 6, + type: EthContractType.erc20, + ); + static final shib = EthContract( + address: "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce", + name: "Shiba Inu", + symbol: "SHIB", + decimals: 18, + type: EthContractType.erc20, + ); + static final link = EthContract( + address: "0x514910771af9ca656af840dff83e8264ecf986ca", + name: "Chainlink", + symbol: "LINK", + decimals: 18, + type: EthContractType.erc20, + ); + static final uni = EthContract( + address: "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + name: "Uniswap", + symbol: "UNI", + decimals: 18, + type: EthContractType.erc20, + ); + static final rsFiro = EthContract( + address: "0x2744ea5ac9b11cb5e3cd63d3a88e858336aeddc2", + name: "rsFIRO", + symbol: "rsFIRO", + decimals: 8, + type: EthContractType.erc20, + ); } diff --git a/lib/widgets/icon_widgets/eth_token_icon.dart b/lib/widgets/icon_widgets/eth_token_icon.dart index 856474283e..eb88200916 100644 --- a/lib/widgets/icon_widgets/eth_token_icon.dart +++ b/lib/widgets/icon_widgets/eth_token_icon.dart @@ -19,6 +19,8 @@ import '../../models/isar/exchange_cache/currency.dart'; import '../../services/exchange/change_now/change_now_exchange.dart'; import '../../services/exchange/exchange_data_loading_service.dart'; import '../../themes/coin_icon_provider.dart'; +import '../../utilities/assets.dart'; +import '../../utilities/default_eth_tokens.dart'; import '../../wallets/crypto_currency/crypto_currency.dart'; import '../loading_indicator.dart'; @@ -38,10 +40,13 @@ class EthTokenIcon extends ConsumerStatefulWidget { class _EthTokenIconState extends ConsumerState { String? imageUrl; + bool get _isRsFiro => + widget.contractAddress.toLowerCase() == DefaultTokens.rsFiro.address; @override void initState() { super.initState(); + if (_isRsFiro) return; ExchangeDataLoadingService.instance.isar.then((isar) async { final currency = await isar.currencies @@ -67,6 +72,14 @@ class _EthTokenIconState extends ConsumerState { @override Widget build(BuildContext context) { + if (_isRsFiro) { + return SvgPicture.asset( + Assets.svg.rsFiro, + width: widget.size, + height: widget.size, + ); + } + if (imageUrl == null || imageUrl!.isEmpty) { return SvgPicture.file( File(ref.watch(coinIconProvider(Ethereum(.main)))), diff --git a/scripts/app_config/configure_campfire.sh b/scripts/app_config/configure_campfire.sh index 40e03dfe26..6af9e01ec8 100755 --- a/scripts/app_config/configure_campfire.sh +++ b/scripts/app_config/configure_campfire.sh @@ -75,8 +75,15 @@ const ({String light, String dark})? _appIconAsset = ( final List _supportedCoins = List.unmodifiable([ Firo(CryptoCurrencyNetwork.main), + Ethereum(CryptoCurrencyNetwork.main), ]); +final List _defaultEthTokens = [ + DefaultTokens.rsFiro, + DefaultTokens.usdc, + DefaultTokens.usdt, +]; + final ({String from, String fromFuzzyNet, String to, String toFuzzyNet}) _swapDefaults = ( from: "BTC", @@ -85,4 +92,4 @@ _swapDefaults = ( toFuzzyNet: "firo", ); -EOF \ No newline at end of file +EOF diff --git a/scripts/app_config/configure_stack_duo.sh b/scripts/app_config/configure_stack_duo.sh index d0bf3694e6..2949d80cd0 100755 --- a/scripts/app_config/configure_stack_duo.sh +++ b/scripts/app_config/configure_stack_duo.sh @@ -80,6 +80,8 @@ final List _supportedCoins = List.unmodifiable([ BitcoinFrost(CryptoCurrencyNetwork.test4), ]); +const List _defaultEthTokens = []; + final ({String from, String fromFuzzyNet, String to, String toFuzzyNet}) _swapDefaults = ( from: "BTC", @@ -88,4 +90,4 @@ _swapDefaults = ( toFuzzyNet: "xmr", ); -EOF \ No newline at end of file +EOF diff --git a/scripts/app_config/configure_stack_wallet.sh b/scripts/app_config/configure_stack_wallet.sh index ae16b70ae1..6f41d27b7c 100755 --- a/scripts/app_config/configure_stack_wallet.sh +++ b/scripts/app_config/configure_stack_wallet.sh @@ -135,6 +135,15 @@ final List _supportedCoins = List.unmodifiable([ Xelis(CryptoCurrencyNetwork.test), ]); +final List _defaultEthTokens = [ + DefaultTokens.usdc, + DefaultTokens.usdt, + DefaultTokens.shib, + DefaultTokens.link, + DefaultTokens.uni, + DefaultTokens.rsFiro, +]; + final ({String from, String fromFuzzyNet, String to, String toFuzzyNet}) _swapDefaults = ( from: "BTC", @@ -143,4 +152,4 @@ _swapDefaults = ( toFuzzyNet: "xmr", ); -EOF \ No newline at end of file +EOF diff --git a/scripts/ensure_test_app_config.sh b/scripts/ensure_test_app_config.sh index 417da291a3..696c853b4e 100755 --- a/scripts/ensure_test_app_config.sh +++ b/scripts/ensure_test_app_config.sh @@ -80,6 +80,15 @@ final List _supportedCoins = List.unmodifiable([ Xelis(CryptoCurrencyNetwork.test), ]); +final List _defaultEthTokens = [ + DefaultTokens.usdc, + DefaultTokens.usdt, + DefaultTokens.shib, + DefaultTokens.link, + DefaultTokens.uni, + DefaultTokens.rsFiro, +]; + final ({String from, String fromFuzzyNet, String to, String toFuzzyNet}) _swapDefaults = ( from: "BTC", diff --git a/test/db_version_migration_test.dart b/test/db_version_migration_test.dart index 4f4ac0d4ed..cd4454b5c7 100644 --- a/test/db_version_migration_test.dart +++ b/test/db_version_migration_test.dart @@ -1,14 +1,61 @@ -// import 'package:flutter_test/flutter_test.dart'; -// import 'package:hive_test/hive_test.dart'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:isar_community/isar.dart'; +import 'package:stackwallet/db/db_version_migration.dart'; +import 'package:stackwallet/db/hive/db.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/default_eth_tokens.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; + +import 'hive/hive_ce_test_utils.dart'; void main() { - // setUp(() async { - // await setUpTestHive(); - // }); - // - // // no migration to test yet - // - // tearDown(() async { - // await tearDownTestHive(); - // }); + test('v17 adds rsFIRO once and preserves the token catalog', () async { + await setUpHiveCeTest(); + addTearDown(tearDownHiveCeTest); + final info = await DB.instance.hive.openBox(DB.boxNameDBInfo); + final directory = await Directory.systemTemp.createTemp( + 'rsfiro_migration_', + ); + addTearDown(() => directory.delete(recursive: true)); + await Isar.initializeIsarCore(download: true); + final isar = await Isar.open( + [EthContractSchema], + directory: directory.path, + inspector: false, + ); + addTearDown(() => isar.close(deleteFromDisk: true)); + final db = MainDB.instance; + await db.initMainDB(mock: isar); + Future migrate(int version) => + DbVersionMigrator().migrate(version, secureStore: FakeSecureStorage()); + + await migrate(16); + expect(Constants.currentDataVersion, 17); + expect(info.get('hive_data_version'), 17); + expect(await db.getEthContracts().isEmpty(), isTrue); + + final custom = DefaultTokens.usdc.copyWith(name: 'Custom token'); + await db.putEthContract(custom); + await migrate(16); + expect(await db.getEthContracts().count(), 2); + expect((await db.getEthContract(custom.address))!.name, 'Custom token'); + + final existing = (await db.getEthContract(DefaultTokens.rsFiro.address))! + .copyWith( + address: DefaultTokens.rsFiro.address.toUpperCase(), + abi: 'keep', + ); + await db.putEthContract(existing); + await migrate(16); + expect(await db.getEthContracts().count(), 2); + expect((await db.getEthContract(existing.address))!.abi, 'keep'); + + await isar.writeTxn(() => isar.ethContracts.delete(existing.id)); + await migrate(info.get('hive_data_version') as int); + expect(await db.getEthContracts().count(), 1); + }); } diff --git a/test/widgets/eth_token_icon_test.dart b/test/widgets/eth_token_icon_test.dart new file mode 100644 index 0000000000..af6a062e05 --- /dev/null +++ b/test/widgets/eth_token_icon_test.dart @@ -0,0 +1,32 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/default_eth_tokens.dart'; +import 'package:stackwallet/widgets/icon_widgets/eth_token_icon.dart'; + +void main() { + testWidgets('rsFIRO uses the per-app SVG by contract address', ( + tester, + ) async { + final token = DefaultTokens.rsFiro; + expect(token.address, '0x2744ea5ac9b11cb5e3cd63d3a88e858336aeddc2'); + expect(token.decimals, 8); + await tester.pumpWidget( + ProviderScope( + child: Center( + child: EthTokenIcon( + contractAddress: token.address.toUpperCase(), + size: 26, + ), + ), + ), + ); + await tester.pumpAndSettle(); + final svg = tester.widget(find.byType(SvgPicture)); + expect((svg.bytesLoader as SvgAssetLoader).assetName, Assets.svg.rsFiro); + expect(tester.getSize(find.byType(SvgPicture)), const Size.square(26)); + expect(tester.takeException(), isNull); + }); +}