diff --git a/.vscode/launch.json b/.vscode/launch.json index ad61127..fe8f4b8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,9 +11,6 @@ "toolArgs": [ "--dart-define=env=local", ], - "args": [ - "--web-experimental-hot-reload" - ] }, { "name": "Zup App (Web) (Debug)", diff --git a/assets/logos/bnb_chain.svg b/assets/logos/bnb_chain.svg new file mode 100644 index 0000000..7f8d0da --- /dev/null +++ b/assets/logos/bnb_chain.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/abis/pancake_swap_infinity_cl_pool_manager.abi.json b/lib/abis/pancake_swap_infinity_cl_pool_manager.abi.json new file mode 100644 index 0000000..1117a3b --- /dev/null +++ b/lib/abis/pancake_swap_infinity_cl_pool_manager.abi.json @@ -0,0 +1,36 @@ +[ + { + "inputs": [ + { + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + } + ], + "name": "getSlot0", + "outputs": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "internalType": "uint24", + "name": "protocolFee", + "type": "uint24" + }, + { + "internalType": "uint24", + "name": "lpFee", + "type": "uint24" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/lib/app/app_layout.dart b/lib/app/app_layout.dart index 595e756..c75802a 100644 --- a/lib/app/app_layout.dart +++ b/lib/app/app_layout.dart @@ -73,9 +73,13 @@ class _AppPageState extends State with DeviceInfoMixin { title: AppHeader(height: appBarHeight), toolbarHeight: appBarHeight, ), - const SliverFillRemaining( - hasScrollBody: false, - child: RouterOutlet(key: Key("screen")), + SliverToBoxAdapter( + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: MediaQuery.of(context).size.height - appBarHeight), + child: const RouterOutlet( + key: Key("screen"), + ), + ), ), SliverToBoxAdapter( child: Padding( diff --git a/lib/app/create/create_page_select_tokens_stage.dart b/lib/app/create/create_page_select_tokens_stage.dart index 5989e8e..9bcc059 100644 --- a/lib/app/create/create_page_select_tokens_stage.dart +++ b/lib/app/create/create_page_select_tokens_stage.dart @@ -2,7 +2,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:zup_app/app/app_cubit/app_cubit.dart'; -import 'package:zup_app/app/create/widgets/create_page_settings_dropdown.dart'; +import 'package:zup_app/app/create/widgets/create_page_settings_dropdown/create_page_settings_dropdown.dart'; +import 'package:zup_app/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button.dart'; import 'package:zup_app/core/cache.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/injections.dart'; @@ -100,7 +101,7 @@ class _CreatePageState extends State with DeviceInf child: Align( alignment: Alignment.topCenter, child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 490, minHeight: 500), + constraints: const BoxConstraints(maxWidth: 490), child: Align( alignment: Alignment.topLeft, child: Column( @@ -112,48 +113,56 @@ class _CreatePageState extends State with DeviceInf style: const TextStyle(fontSize: 14, color: ZupColors.gray), ), const SizedBox(height: 20), - Row( - children: [ - Transform.translate( - offset: const Offset(0, 8), - child: Text( - S.of(context).token0, - style: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 14, - color: ZupColors.gray, + SizedBox( + width: double.infinity, + child: Wrap( + runSpacing: 10, + verticalDirection: VerticalDirection.up, + alignment: WrapAlignment.spaceBetween, + children: [ + Transform.translate( + offset: const Offset(0, 8), + child: Text( + S.of(context).token0, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14, + color: ZupColors.gray, + ), ), ), - ), - const Spacer(), - StatefulBuilder(builder: (context, localSetState) { - return Row( - children: [ - Badge( - alignment: const Alignment(1.05, -1.05), - smallSize: cache.getPoolSearchSettings().isDefault ? 0 : 6, - backgroundColor: ZupColors.orange, - child: ZupPillButton( - key: const Key("pool-search-settings-button"), - onPressed: (buttonContext) => CreatePageSettingsDropdown.show( - buttonContext, - onClose: () { - if (mounted) { - WidgetsBinding.instance.addPostFrameCallback((_) => localSetState(() {})); - } - }, + StatefulBuilder(builder: (context, localSetState) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + const ExchangesFilterDropdownButton(), + const SizedBox(width: 10), + Badge( + alignment: const Alignment(1.05, -1.05), + smallSize: cache.getPoolSearchSettings().isDefault ? 0 : 6, + backgroundColor: ZupColors.orange, + child: ZupMiniButton( + key: const Key("pool-search-settings-button"), + onPressed: (buttonContext) => CreatePageSettingsDropdown.show( + buttonContext, + onClose: () { + if (mounted) { + WidgetsBinding.instance.addPostFrameCallback((_) => localSetState(() {})); + } + }, + ), + title: S.of(context).createPageSelectTokensStageSearchSettings, + icon: Assets.icons.gear.svg( + height: 18, + colorFilter: const ColorFilter.mode(ZupColors.white, BlendMode.srcIn), + ), ), - foregroundColor: ZupColors.gray, - backgroundColor: ZupColors.gray6, - title: "Search settings", - icon: Assets.icons.gear.svg( - height: 18, colorFilter: const ColorFilter.mode(ZupColors.white, BlendMode.srcIn)), ), - ), - ], - ); - }) - ], + ], + ); + }) + ], + ), ), const SizedBox(height: 12), TokenSelectorButton( @@ -191,7 +200,7 @@ class _CreatePageState extends State with DeviceInf icon: Assets.icons.sparkleMagnifyingglass.svg(), onPressed: token0SelectorController.selectedToken != null && token1SelectorController.selectedToken != null - ? () { + ? (buttonContext) { return navigator.navigateToDeposit( appCubit.selectedNetwork.isAllNetworks ? token0SelectorController.selectedToken!.internalId! diff --git a/lib/app/create/deposit/deposit_cubit.dart b/lib/app/create/deposit/deposit_cubit.dart index f47a4df..83b10ec 100644 --- a/lib/app/create/deposit/deposit_cubit.dart +++ b/lib/app/create/deposit/deposit_cubit.dart @@ -6,6 +6,7 @@ import 'package:web3kit/web3kit.dart'; import 'package:zup_app/app/app_cubit/app_cubit.dart'; import 'package:zup_app/core/cache.dart'; import 'package:zup_app/core/dtos/deposit_settings_dto.dart'; +import 'package:zup_app/core/dtos/pool_search_filters_dto.dart'; import 'package:zup_app/core/dtos/pool_search_settings_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/dtos/yields_dto.dart'; @@ -42,6 +43,7 @@ class DepositCubit extends Cubit with KeysMixin, V3PoolConversorsM final StreamController _pooltickStreamController = StreamController.broadcast(); final StreamController _selectedYieldStreamController = StreamController.broadcast(); + final Duration _poolTickExpiration = const Duration(seconds: 30); BigInt? _latestPoolTick; YieldDto? _selectedYield; @@ -57,7 +59,7 @@ class DepositCubit extends Cubit with KeysMixin, V3PoolConversorsM PoolSearchSettingsDto get poolSearchSettings => _cache.getPoolSearchSettings(); void setup() async { - Timer.periodic(const Duration(minutes: 1), (timer) { + Timer.periodic(_poolTickExpiration, (timer) { if (_pooltickStreamController.isClosed) return timer.cancel(); if (selectedYield != null) getSelectedPoolTick(); @@ -79,12 +81,14 @@ class DepositCubit extends Cubit with KeysMixin, V3PoolConversorsM emit(const DepositState.loading()); final yields = _appCubit.selectedNetwork.isAllNetworks ? await _yieldRepository.getAllNetworksYield( + blockedProtocolIds: _cache.blockedProtocolsIds, token0InternalId: token0AddressOrId, token1InternalId: token1AddressOrId, searchSettings: ignoreMinLiquidity ? poolSearchSettings.copyWith(minLiquidityUSD: 0) : poolSearchSettings, testnetMode: _appCubit.isTestnetMode, ) : await _yieldRepository.getSingleNetworkYield( + blockedProtocolIds: _cache.blockedProtocolsIds, token0Address: token0AddressOrId, token1Address: token1AddressOrId, network: _appCubit.selectedNetwork, @@ -93,7 +97,7 @@ class DepositCubit extends Cubit with KeysMixin, V3PoolConversorsM if (yields.isEmpty) { return emit( - DepositState.noYields(minLiquiditySearched: yields.minLiquidityUSD), + DepositState.noYields(filtersApplied: yields.filters), ); } @@ -123,7 +127,7 @@ class DepositCubit extends Cubit with KeysMixin, V3PoolConversorsM final tick = await _zupSingletonCache.run( () => _poolService.getPoolTick(selectedYieldBeforeCall!), - expiration: const Duration(minutes: 1), + expiration: _poolTickExpiration - const Duration(seconds: 1), ignoreCache: forceRefresh, key: poolTickCacheKey( network: selectedYield!.network, diff --git a/lib/app/create/deposit/deposit_page.dart b/lib/app/create/deposit/deposit_page.dart index f596b2b..c1f448b 100644 --- a/lib/app/create/deposit/deposit_page.dart +++ b/lib/app/create/deposit/deposit_page.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:decimal/decimal.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -14,6 +16,7 @@ import 'package:zup_app/app/create/deposit/widgets/range_selector.dart'; import 'package:zup_app/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card.dart'; import 'package:zup_app/core/cache.dart'; import 'package:zup_app/core/dtos/deposit_settings_dto.dart'; +import 'package:zup_app/core/dtos/pool_search_filters_dto.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/dtos/yields_dto.dart'; @@ -103,6 +106,7 @@ class _DepositPageState extends State double maxPrice = 0; RangeController minRangeController = RangeController(); RangeController maxRangeController = RangeController(); + StreamSubscription? _poolTickStreamSubscription; late Slippage selectedSlippage = _cubit.depositSettings.slippage; late Duration selectedDeadline = _cubit.depositSettings.deadline; @@ -122,8 +126,8 @@ class _DepositPageState extends State final price = tickToPrice( tick: _cubit.latestPoolTick!, - poolToken0Decimals: _cubit.selectedYield!.token0.decimals, - poolToken1Decimals: _cubit.selectedYield!.token1.decimals, + poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, + poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals, ); return areTokensReversed ? price.priceAsQuoteToken : price.priceAsBaseToken; @@ -214,14 +218,14 @@ class _DepositPageState extends State final maxTickPrice = tickToPrice( tick: V3V4PoolConstants.maxTick, - poolToken0Decimals: _cubit.selectedYield!.token0.decimals, - poolToken1Decimals: _cubit.selectedYield!.token1.decimals, + poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, + poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals, ); final minTickPrice = tickToPrice( tick: V3V4PoolConstants.minTick, - poolToken0Decimals: _cubit.selectedYield!.token0.decimals, - poolToken1Decimals: _cubit.selectedYield!.token1.decimals, + poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, + poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals, ); double getMinPrice() { @@ -242,7 +246,7 @@ class _DepositPageState extends State getMinPrice(), getMaxPrice(), ).toString()) - .toString(); + ?.toStringAsFixed(quoteToken.decimals[_cubit.selectedYield!.network.chainId]!); final newBaseTokenAmount = Decimal.tryParse(calculateToken0AmountFromToken1( double.tryParse(quoteTokenAmountController.text) ?? 0, @@ -250,17 +254,17 @@ class _DepositPageState extends State getMinPrice(), getMaxPrice(), ).toString()) - .toString(); + ?.toStringAsFixed(baseToken.decimals[_cubit.selectedYield!.network.chainId]!); if (isBaseTokenAmountUserInput) { - if (newQuoteTokenAmount.isEmptyOrZero) return quoteTokenAmountController.clear(); - quoteTokenAmountController.text = newQuoteTokenAmount; + if (newQuoteTokenAmount?.isEmptyOrZero ?? true) return quoteTokenAmountController.clear(); + quoteTokenAmountController.text = newQuoteTokenAmount!; return; } - if (newBaseTokenAmount.isEmptyOrZero) return baseTokenAmountController.clear(); - baseTokenAmountController.text = newBaseTokenAmount; + if (newBaseTokenAmount?.isEmptyOrZero ?? true) return baseTokenAmountController.clear(); + baseTokenAmountController.text = newBaseTokenAmount!; } Future<({String title, Widget? icon, Function()? onPressed})> depositButtonState() async { @@ -319,14 +323,8 @@ class _DepositPageState extends State maxSlippage: selectedSlippage, currentYield: _cubit.selectedYield!, isReversed: areTokensReversed, - token0DepositAmount: double.tryParse( - areTokensReversed ? quoteTokenAmountController.text : baseTokenAmountController.text, - ) ?? - 0, - token1DepositAmount: double.tryParse( - areTokensReversed ? baseTokenAmountController.text : quoteTokenAmountController.text, - ) ?? - 0, + token0DepositAmountController: areTokensReversed ? quoteTokenAmountController : baseTokenAmountController, + token1DepositAmountController: areTokensReversed ? baseTokenAmountController : quoteTokenAmountController, maxPrice: (isInfinity: isMaxRangeInfinity, price: maxPrice), minPrice: (isInfinity: isMinRangeInfinity, price: minPrice), ).show( @@ -354,6 +352,14 @@ class _DepositPageState extends State _cubit.getBestPools(token0AddressOrId: token0Address, token1AddressOrId: token1Address); }); + _poolTickStreamSubscription = _cubit.poolTickStream.listen((poolTick) { + if (poolTick != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() => calculateDepositTokensAmount()); + }); + } + }); + super.initState(); } @@ -361,6 +367,7 @@ class _DepositPageState extends State void dispose() { minRangeController.dispose(); maxRangeController.dispose(); + _poolTickStreamSubscription?.cancel(); super.dispose(); } @@ -372,9 +379,7 @@ class _DepositPageState extends State builder: (context, state) { return state.maybeWhen( orElse: () => _buildLoadingState(), - noYields: (minLiquiditySearchedUSD) => _buildNoYieldsState( - minLiquiditySearchedUSD: minLiquiditySearchedUSD, - ), + noYields: (filtersApplied) => _buildNoYieldsState(filtersApplied: filtersApplied), error: () => _buildErrorState(), success: (yields) => StreamBuilder( stream: _cubit.selectedYieldStream, @@ -407,9 +412,9 @@ class _DepositPageState extends State valuePercent: selectedSlippage.value.formatPercent, ) : null, - onPressed: (buttonContext) => ZupDropdown.show( - offset: const Offset(0, 100), - showBelowContext: buttonContext, + onPressed: (buttonContext) => ZupPopover.show( + adjustment: const Offset(0, 10), + showBasedOnContext: buttonContext, child: DepositSettingsDropdownChild( context, selectedDeadline: selectedDeadline, @@ -455,7 +460,7 @@ class _DepositPageState extends State Widget _sectionTitle(String title) => Text(title, style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600)); - Widget _buildNoYieldsState({required num minLiquiditySearchedUSD}) => Center( + Widget _buildNoYieldsState({required PoolSearchFiltersDto filtersApplied}) => Center( child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, @@ -470,11 +475,11 @@ class _DepositPageState extends State description: S.of(context).depositPageEmptyStateDescription, helpButtonTitle: S.of(context).depositPageEmptyStateHelpButtonTitle, helpButtonIcon: Assets.icons.arrowLeft.svg(), - onHelpButtonTap: () => _navigator.back(context), + onHelpButtonTap: () => _navigator.navigateToNewPosition(), ), ), const SizedBox(height: 60), - if (minLiquiditySearchedUSD > 0) + if (filtersApplied.minTvlUsd > 0) Text.rich( TextSpan( children: [ @@ -517,7 +522,7 @@ class _DepositPageState extends State ) ], ), - ) + ), ], ), ); @@ -652,7 +657,7 @@ class _DepositPageState extends State Flexible( fit: FlexFit.loose, child: Text( - yields.minLiquidityUSD > 0 + yields.filters.minTvlUsd > 0 ? "${S.of(context).depositPageShowingOnlyPoolsWithMoreThan(minLiquidity: NumberFormat.compactSimpleCurrency().format(_cubit.poolSearchSettings.minLiquidityUSD))} " : "${S.of(context).depositPageShowingAllPools} ", style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14, color: ZupColors.gray), @@ -667,13 +672,13 @@ class _DepositPageState extends State onPressed: () => _cubit.getBestPools( token0AddressOrId: token0Address, token1AddressOrId: token1Address, - ignoreMinLiquidity: yields.minLiquidityUSD > 0, + ignoreMinLiquidity: yields.filters.minTvlUsd > 0, ), style: ButtonStyle( padding: WidgetStateProperty.all(const EdgeInsets.all(6)), ), child: Text( - yields.minLiquidityUSD > 0 + yields.filters.minTvlUsd > 0 ? S.of(context).depositPageSearchAllPools : S.of(context).depositPageSearchOnlyForPoolsWithMorethan( minLiquidity: NumberFormat.compactSimpleCurrency() @@ -757,8 +762,8 @@ class _DepositPageState extends State "1 ${baseToken.symbol} ≈ ${() { final currentPrice = tickToPrice( tick: poolTickSnapshot.data ?? BigInt.zero, - poolToken0Decimals: _cubit.selectedYield!.token0.decimals, - poolToken1Decimals: _cubit.selectedYield!.token1.decimals, + poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, + poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals, ); return areTokensReversed ? currentPrice.priceAsQuoteToken : currentPrice.priceAsBaseToken; @@ -775,14 +780,14 @@ class _DepositPageState extends State children: [ ZupMiniButton( key: const Key("full-range-button"), - onPressed: () => setFullRange(), + onPressed: (_) => setFullRange(), isSelected: isMaxRangeInfinity && isMinRangeInfinity, title: S.of(context).depositPageRangeSectionFullRange, icon: Assets.icons.circleDotted.svg(), ), ZupMiniButton( key: const Key("5-percent-range-button"), - onPressed: () => setPercentageRange(5), + onPressed: (_) => setPercentageRange(5), isSelected: percentRange == 5, title: "5%", icon: Assets.icons.plusminus.svg(), @@ -790,7 +795,7 @@ class _DepositPageState extends State ), ZupMiniButton( key: const Key("20-percent-range-button"), - onPressed: () => setPercentageRange(20), + onPressed: (_) => setPercentageRange(20), isSelected: percentRange == 20, title: "20%", icon: Assets.icons.plusminus.svg(), @@ -798,7 +803,7 @@ class _DepositPageState extends State ), ZupMiniButton( key: const Key("50-percent-range-button"), - onPressed: () => setPercentageRange(50), + onPressed: (_) => setPercentageRange(50), isSelected: percentRange == 50, title: "50%", icon: Assets.icons.plusminus.svg(), @@ -827,8 +832,8 @@ class _DepositPageState extends State }); }, initialPrice: minPrice, - poolToken0: _cubit.selectedYield!.token0, - poolToken1: _cubit.selectedYield!.token1, + poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, + poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals, isReversed: areTokensReversed, displayBaseTokenSymbol: baseToken.symbol, displayQuoteTokenSymbol: quoteToken.symbol, @@ -874,8 +879,8 @@ class _DepositPageState extends State type: RangeSelectorType.maxPrice, isInfinity: isMaxRangeInfinity, initialPrice: maxPrice, - poolToken0: _cubit.selectedYield!.token0, - poolToken1: _cubit.selectedYield!.token1, + poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, + poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals, isReversed: areTokensReversed, tickSpacing: _cubit.selectedYield!.tickSpacing, rangeController: maxRangeController, @@ -909,116 +914,119 @@ class _DepositPageState extends State duration: const Duration(milliseconds: 300), opacity: isRangeInvalid ? 0.2 : 1, child: StreamBuilder( - stream: _cubit.poolTickStream, - initialData: _cubit.latestPoolTick, - builder: (context, poolTickSnapshot) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _sectionTitle(S.of(context).depositPageDepositSectionTitle), - const SizedBox(height: 12), - TokenAmountInputCard( - key: const Key("base-token-input-card"), - token: baseToken, - isNative: baseToken.addresses[_cubit.selectedYield!.network.chainId]!.lowercasedEquals( - EthereumConstants.zeroAddress, - ), - onRefreshBalance: () => setState(() {}), - disabledText: () { - if (!isBaseTokenNeeded) { - return S.of(context).depositPageDepositSectionTokenNotNeeded(tokenSymbol: baseToken.symbol); - } - - if (!isBaseTokenAmountUserInput && - !poolTickSnapshot.hasData && - quoteTokenAmountController.text.isNotEmpty) { - return S.of(context).loading; - } - }.call(), - onInput: (amount) { - setState(() { - isBaseTokenAmountUserInput = true; - - calculateDepositTokensAmount(); - }); - }, - controller: baseTokenAmountController, - network: _cubit.selectedYield!.network, + stream: _cubit.poolTickStream, + initialData: _cubit.latestPoolTick, + builder: (context, poolTickSnapshot) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _sectionTitle(S.of(context).depositPageDepositSectionTitle), + const SizedBox(height: 12), + TokenAmountInputCard( + key: const Key("base-token-input-card"), + token: baseToken, + isNative: baseToken.addresses[_cubit.selectedYield!.network.chainId]!.lowercasedEquals( + EthereumConstants.zeroAddress, ), - const SizedBox(height: 6), - TokenAmountInputCard( - key: const Key("quote-token-input-card"), - token: quoteToken, - isNative: quoteToken.addresses[_cubit.selectedYield!.network.chainId]!.lowercasedEquals( - EthereumConstants.zeroAddress, - ), - onRefreshBalance: () => setState(() {}), - disabledText: () { - if (!isQuoteTokenNeeded) { - return S.of(context).depositPageDepositSectionTokenNotNeeded(tokenSymbol: quoteToken.symbol); - } - - if (isBaseTokenAmountUserInput && - !poolTickSnapshot.hasData && - baseTokenAmountController.text.isNotEmpty) { - return S.of(context).loading; - } - }.call(), - onInput: (amount) { - setState(() { - isBaseTokenAmountUserInput = false; - - calculateDepositTokensAmount(); - }); - }, - controller: quoteTokenAmountController, - network: _cubit.selectedYield!.network, + onRefreshBalance: () => setState(() {}), + disabledText: () { + if (!isBaseTokenNeeded) { + return S.of(context).depositPageDepositSectionTokenNotNeeded(tokenSymbol: baseToken.symbol); + } + + if (!isBaseTokenAmountUserInput && + !poolTickSnapshot.hasData && + quoteTokenAmountController.text.isNotEmpty) { + return S.of(context).loading; + } + }.call(), + onInput: (amount) { + setState(() { + isBaseTokenAmountUserInput = true; + + calculateDepositTokensAmount(); + }); + }, + controller: baseTokenAmountController, + network: _cubit.selectedYield!.network, + ), + const SizedBox(height: 6), + TokenAmountInputCard( + key: const Key("quote-token-input-card"), + token: quoteToken, + isNative: quoteToken.addresses[_cubit.selectedYield!.network.chainId]!.lowercasedEquals( + EthereumConstants.zeroAddress, ), - const SizedBox(height: 20), - Row( - children: [ - Expanded( - child: StreamBuilder( - key: const Key("deposit-button"), - stream: wallet.signerStream, - initialData: wallet.signer, - builder: (context, signerSnapshot) { - if (!signerSnapshot.hasData) { + onRefreshBalance: () => setState(() {}), + disabledText: () { + if (!isQuoteTokenNeeded) { + return S.of(context).depositPageDepositSectionTokenNotNeeded(tokenSymbol: quoteToken.symbol); + } + + if (isBaseTokenAmountUserInput && + !poolTickSnapshot.hasData && + baseTokenAmountController.text.isNotEmpty) { + return S.of(context).loading; + } + }.call(), + onInput: (amount) { + setState(() { + isBaseTokenAmountUserInput = false; + + calculateDepositTokensAmount(); + }); + }, + controller: quoteTokenAmountController, + network: _cubit.selectedYield!.network, + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded( + child: StreamBuilder( + key: const Key("deposit-button"), + stream: wallet.signerStream, + initialData: wallet.signer, + builder: (context, signerSnapshot) { + if (!signerSnapshot.hasData) { + return ZupPrimaryButton( + width: double.maxFinite, + title: S.of(context).connectWallet, + icon: Assets.icons.walletBifold.svg(), + fixedIcon: true, + alignCenter: true, + hoverElevation: 0, + backgroundColor: ZupColors.brand7, + foregroundColor: ZupColors.brand, + onPressed: (buttonContext) => ConnectModal().show(context), + ); + } + + return FutureBuilder( + future: depositButtonState(), + builder: (context, stateSnapshot) { return ZupPrimaryButton( - width: double.maxFinite, - title: S.of(context).connectWallet, - icon: Assets.icons.walletBifold.svg(), - fixedIcon: true, alignCenter: true, - hoverElevation: 0, - backgroundColor: ZupColors.brand7, - foregroundColor: ZupColors.brand, - onPressed: () => ConnectModal().show(context), + title: stateSnapshot.data?.title ?? "Loading...", + icon: stateSnapshot.data?.icon, + isLoading: stateSnapshot.connectionState == ConnectionState.waiting, + fixedIcon: true, + onPressed: stateSnapshot.data?.onPressed == null + ? null + : (buttonContext) => stateSnapshot.data?.onPressed!(), + width: double.maxFinite, ); - } - - return FutureBuilder( - future: depositButtonState(), - builder: (context, stateSnapshot) { - return ZupPrimaryButton( - alignCenter: true, - title: stateSnapshot.data?.title ?? "Loading...", - icon: stateSnapshot.data?.icon, - isLoading: stateSnapshot.connectionState == ConnectionState.waiting, - fixedIcon: true, - onPressed: stateSnapshot.data?.onPressed, - width: double.maxFinite, - ); - }, - ); - }, - ), + }, + ); + }, ), - ], - ), - ], - ); - }), + ), + ], + ), + ], + ); + }, + ), ), ); } diff --git a/lib/app/create/deposit/deposit_state.dart b/lib/app/create/deposit/deposit_state.dart index 31420d0..38dfa3e 100644 --- a/lib/app/create/deposit/deposit_state.dart +++ b/lib/app/create/deposit/deposit_state.dart @@ -6,5 +6,5 @@ class DepositState with _$DepositState { const factory DepositState.loading() = _Loading; const factory DepositState.success(YieldsDto yields) = _Success; const factory DepositState.error() = _Error; - const factory DepositState.noYields({required num minLiquiditySearched}) = _NoYields; + const factory DepositState.noYields({required PoolSearchFiltersDto filtersApplied}) = _NoYields; } diff --git a/lib/app/create/deposit/widgets/deposit_success_modal.dart b/lib/app/create/deposit/widgets/deposit_success_modal.dart index 7cb5396..655a5e2 100644 --- a/lib/app/create/deposit/widgets/deposit_success_modal.dart +++ b/lib/app/create/deposit/widgets/deposit_success_modal.dart @@ -122,7 +122,7 @@ class _DepositSuccessModalState extends State { ZupPrimaryButton( key: const Key("view-position-button"), title: S.of(context).depositSuccessModalViewPositionOnDEX(dexName: widget.depositedYield.protocol.name), - onPressed: () => launchUrl(Uri.parse(widget.depositedYield.protocol.url)), + onPressed: (buttonContext) => launchUrl(Uri.parse(widget.depositedYield.protocol.url)), fontWeight: FontWeight.w500, icon: Assets.icons.arrowUpRight.svg(), backgroundColor: Colors.transparent, @@ -135,7 +135,7 @@ class _DepositSuccessModalState extends State { key: const Key("close-button"), title: S.of(context).close, width: double.infinity, - onPressed: () => Navigator.of(context).pop(), + onPressed: (buttonContext) => Navigator.of(context).pop(), ), ], ); diff --git a/lib/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal.dart b/lib/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal.dart index fca2d4b..dacf233 100644 --- a/lib/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal.dart +++ b/lib/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal.dart @@ -24,6 +24,7 @@ import 'package:zup_app/gen/assets.gen.dart'; import 'package:zup_app/l10n/gen/app_localizations.dart'; import 'package:zup_app/widgets/token_avatar.dart'; import 'package:zup_app/widgets/zup_cached_image.dart'; +import 'package:zup_core/extensions/text_edititing_controller_extension.dart'; import 'package:zup_core/zup_core.dart'; import 'package:zup_ui_kit/zup_ui_kit.dart'; @@ -34,8 +35,8 @@ class PreviewDepositModal extends StatefulWidget with DeviceInfoMixin { required this.isReversed, required this.minPrice, required this.maxPrice, - required this.token0DepositAmount, - required this.token1DepositAmount, + required this.token0DepositAmountController, + required this.token1DepositAmountController, required this.deadline, required this.maxSlippage, required this.yieldTimeFrame, @@ -46,8 +47,8 @@ class PreviewDepositModal extends StatefulWidget with DeviceInfoMixin { final bool isReversed; final ({double price, bool isInfinity}) minPrice; final ({double price, bool isInfinity}) maxPrice; - final double token0DepositAmount; - final double token1DepositAmount; + final TextEditingController token0DepositAmountController; + final TextEditingController token1DepositAmountController; final Duration deadline; final Slippage maxSlippage; @@ -75,8 +76,8 @@ class PreviewDepositModal extends StatefulWidget with DeviceInfoMixin { child: PreviewDepositModal( deadline: deadline, maxSlippage: maxSlippage, - token0DepositAmount: token0DepositAmount, - token1DepositAmount: token1DepositAmount, + token0DepositAmountController: token0DepositAmountController, + token1DepositAmountController: token1DepositAmountController, minPrice: minPrice, maxPrice: maxPrice, currentYield: currentYield, @@ -100,6 +101,10 @@ class _PreviewDepositModalState extends State with V3PoolCo instanceName: InjectInstanceNames.appScrollController, ); + late VoidCallback tokenDepositAmountChangeAction = () { + WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {})); + }; + TokenDto get baseToken { if (isReversedLocal) { return widget.currentYield.token1; @@ -116,21 +121,29 @@ class _PreviewDepositModalState extends State with V3PoolCo return widget.currentYield.token1; } - double get baseTokenAmount => isReversedLocal ? widget.token1DepositAmount : widget.token0DepositAmount; - double get quoteTokenAmount => isReversedLocal ? widget.token0DepositAmount : widget.token1DepositAmount; PreviewDepositModalCubit get cubit => context.read(); - BigInt get token0DepositAmount => - widget.token0DepositAmount.parseTokenAmount(decimals: widget.currentYield.token0.decimals); - BigInt get token1DepositAmount => - widget.token1DepositAmount.parseTokenAmount(decimals: widget.currentYield.token1.decimals); + + double get baseTokenAmount => isReversedLocal + ? widget.token1DepositAmountController.parseTextToDouble + : widget.token0DepositAmountController.parseTextToDouble; + + double get quoteTokenAmount => isReversedLocal + ? widget.token0DepositAmountController.parseTextToDouble + : widget.token1DepositAmountController.parseTextToDouble; + + BigInt get token0DepositAmount => widget.token0DepositAmountController.parseTextToDouble + .parseTokenAmount(decimals: widget.currentYield.token0NetworkDecimals); + + BigInt get token1DepositAmount => widget.token1DepositAmountController.parseTextToDouble + .parseTokenAmount(decimals: widget.currentYield.token1NetworkDecimals); double get currentPrice { final currentTick = cubit.latestPoolTick; final price = tickToPrice( tick: currentTick, - poolToken0Decimals: widget.currentYield.token0.decimals, - poolToken1Decimals: widget.currentYield.token1.decimals, + poolToken0Decimals: widget.currentYield.token0NetworkDecimals, + poolToken1Decimals: widget.currentYield.token1NetworkDecimals, ); return isReversedLocal ? price.priceAsQuoteToken : price.priceAsBaseToken; @@ -142,16 +155,16 @@ class _PreviewDepositModalState extends State with V3PoolCo return priceToTick( price: (widget.isReversed == !isReversedLocal) ? widget.maxPrice.price : widget.minPrice.price, - poolToken0Decimals: widget.currentYield.token0.decimals, - poolToken1Decimals: widget.currentYield.token1.decimals, + poolToken0Decimals: widget.currentYield.token0NetworkDecimals, + poolToken1Decimals: widget.currentYield.token1NetworkDecimals, isReversed: widget.isReversed, ); } ({double priceAsBaseToken, double priceAsQuoteToken}) price() => tickToPrice( tick: tick(), - poolToken0Decimals: widget.currentYield.token0.decimals, - poolToken1Decimals: widget.currentYield.token1.decimals, + poolToken0Decimals: widget.currentYield.token0NetworkDecimals, + poolToken1Decimals: widget.currentYield.token1NetworkDecimals, ); return isReversedLocal ? price().priceAsQuoteToken : price().priceAsBaseToken; @@ -163,16 +176,16 @@ class _PreviewDepositModalState extends State with V3PoolCo return priceToTick( price: (widget.isReversed == !isReversedLocal) ? widget.minPrice.price : widget.maxPrice.price, - poolToken0Decimals: widget.currentYield.token0.decimals, - poolToken1Decimals: widget.currentYield.token1.decimals, + poolToken0Decimals: widget.currentYield.token0NetworkDecimals, + poolToken1Decimals: widget.currentYield.token1NetworkDecimals, isReversed: widget.isReversed, ); } ({double priceAsBaseToken, double priceAsQuoteToken}) price() => tickToPrice( tick: tick(), - poolToken0Decimals: widget.currentYield.token0.decimals, - poolToken1Decimals: widget.currentYield.token1.decimals, + poolToken0Decimals: widget.currentYield.token0NetworkDecimals, + poolToken1Decimals: widget.currentYield.token1NetworkDecimals, ); return isReversedLocal ? price().priceAsQuoteToken : price().priceAsBaseToken; @@ -316,9 +329,21 @@ class _PreviewDepositModalState extends State with V3PoolCo @override void initState() { WidgetsBinding.instance.addPostFrameCallback((_) => cubit.setup()); + + widget.token0DepositAmountController.addListener(tokenDepositAmountChangeAction); + widget.token1DepositAmountController.addListener(tokenDepositAmountChangeAction); + super.initState(); } + @override + void dispose() { + widget.token0DepositAmountController.removeListener(tokenDepositAmountChangeAction); + widget.token1DepositAmountController.removeListener(tokenDepositAmountChangeAction); + + super.dispose(); + } + @override Widget build(BuildContext context) { return BlocConsumer( @@ -492,7 +517,7 @@ class _PreviewDepositModalState extends State with V3PoolCo Text( "${baseTokenAmount.maybeFormatCompactCurrency( isUSD: false, - useLessThan: true, + useLessThan: false, useMoreThan: true, )} ${baseToken.symbol}", style: const TextStyle(fontWeight: FontWeight.w500)), @@ -519,7 +544,7 @@ class _PreviewDepositModalState extends State with V3PoolCo Text( "${quoteTokenAmount.maybeFormatCompactCurrency( isUSD: false, - useLessThan: true, + useLessThan: false, useMoreThan: true, )} ${quoteToken.symbol}", style: const TextStyle(fontWeight: FontWeight.w500)), @@ -580,7 +605,9 @@ class _PreviewDepositModalState extends State with V3PoolCo fixedIcon: true, alignCenter: true, title: depositButtonState.title, - onPressed: depositButtonState.onPressed, + onPressed: depositButtonState.onPressed == null + ? null + : (buttonContext) => depositButtonState.onPressed!(), icon: depositButtonState.icon, isLoading: depositButtonState.isLoading ?? false, width: double.maxFinite, diff --git a/lib/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart b/lib/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart index e537ec9..6836217 100644 --- a/lib/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart +++ b/lib/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart @@ -129,6 +129,8 @@ class PreviewDepositModalCubit extends Cubit with V3Po Future checkOrApprovePermit2ForV4Pool(BigInt approveValue, TokenDto token) async { final tokenAddressInNetwork = token.addresses[_yield.network.chainId]!; + if (tokenAddressInNetwork == EthereumConstants.zeroAddress) return; + final permit2Contract = _permit2.fromSigner( contractAddress: _yield.permit2!, signer: _wallet.signer!, @@ -175,8 +177,8 @@ class PreviewDepositModalCubit extends Cubit with V3Po return priceToTick( price: isReversed ? maxPrice : minPrice, - poolToken0Decimals: _yield.token0.decimals, - poolToken1Decimals: _yield.token1.decimals, + poolToken0Decimals: _yield.token0NetworkDecimals, + poolToken1Decimals: _yield.token1NetworkDecimals, isReversed: isReversed, ); } @@ -194,8 +196,8 @@ class PreviewDepositModalCubit extends Cubit with V3Po return priceToTick( price: isReversed ? minPrice : maxPrice, - poolToken0Decimals: _yield.token0.decimals, - poolToken1Decimals: _yield.token1.decimals, + poolToken0Decimals: _yield.token0NetworkDecimals, + poolToken1Decimals: _yield.token1NetworkDecimals, isReversed: isReversed, ); } @@ -242,7 +244,6 @@ class PreviewDepositModalCubit extends Cubit with V3Po maxAmount0ToDeposit: slippage.calculateMaxTokenAmountFromSlippage(amount0Desired), maxAmount1ToDeposit: slippage.calculateMaxTokenAmountFromSlippage(amount1Desired), recipient: recipient, - currentPoolTick: _latestPoolTick, ); }.call(); @@ -253,8 +254,8 @@ class PreviewDepositModalCubit extends Cubit with V3Po emit(PreviewDepositModalState.depositSuccess(txId: tx.hash)); _zupAnalytics.logDeposit( depositedYield: _yield, - amount0Formatted: amount0Desired.parseTokenAmount(decimals: _yield.token0.decimals), - amount1Formatted: amount1Desired.parseTokenAmount(decimals: _yield.token1.decimals), + amount0Formatted: amount0Desired.parseTokenAmount(decimals: _yield.token0NetworkDecimals), + amount1Formatted: amount1Desired.parseTokenAmount(decimals: _yield.token1NetworkDecimals), walletAddress: recipient, ); } catch (e) { @@ -322,15 +323,15 @@ class PreviewDepositModalCubit extends Cubit with V3Po } } - void _updateTick() { + Future _updateTick() async { try { - _poolRepository.getPoolTick(_yield).then((tick) { - _latestPoolTick = tick; - _poolTickStreamController.add(tick); - }); + _latestPoolTick = await _poolRepository.getPoolTick(_yield); + _poolTickStreamController.add(_latestPoolTick); } catch (_) { // DO NOTHING } + + return _latestPoolTick; } void _waitTransactionFinishBeforeClosing() { diff --git a/lib/app/create/deposit/widgets/range_selector.dart b/lib/app/create/deposit/widgets/range_selector.dart index 9f25473..a797454 100644 --- a/lib/app/create/deposit/widgets/range_selector.dart +++ b/lib/app/create/deposit/widgets/range_selector.dart @@ -1,6 +1,5 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; -import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/extensions/num_extension.dart'; import 'package:zup_app/core/mixins/v3_pool_conversors_mixin.dart'; import 'package:zup_app/core/token_amount_input_formatter.dart'; @@ -55,8 +54,8 @@ class RangeController extends ChangeNotifier { class RangeSelector extends StatefulWidget { const RangeSelector({ super.key, - required this.poolToken0, - required this.poolToken1, + required this.poolToken0Decimals, + required this.poolToken1Decimals, required this.displayBaseTokenSymbol, required this.displayQuoteTokenSymbol, required this.isReversed, @@ -70,8 +69,8 @@ class RangeSelector extends StatefulWidget { this.state = const RangeSelectorState(type: RangeSelectorStateType.regular), }); - final TokenDto poolToken0; - final TokenDto poolToken1; + final int poolToken0Decimals; + final int poolToken1Decimals; final String displayBaseTokenSymbol; final String displayQuoteTokenSymbol; final bool isReversed; @@ -116,8 +115,8 @@ class _RangeSelectorState extends State with V3PoolConversorsMixi double getAdjustedPrice(double price) { final adjustedPrice = priceToClosestValidPrice( price: price, - poolToken0Decimals: widget.poolToken0.decimals, - poolToken1Decimals: widget.poolToken1.decimals, + poolToken0Decimals: widget.poolToken0Decimals, + poolToken1Decimals: widget.poolToken1Decimals, tickSpacing: widget.tickSpacing, isReversed: widget.isReversed, ); @@ -131,13 +130,16 @@ class _RangeSelectorState extends State with V3PoolConversorsMixi if (currentPrice == 0 && !increasing) return; if ((currentPrice == 0 || widget.isInfinity) && increasing) { - final minimumPrice = tickToPrice( - tick: BigInt.from(widget.tickSpacing), - poolToken0Decimals: widget.poolToken0.decimals, - poolToken1Decimals: widget.poolToken1.decimals, + final minimumPrice = priceToClosestValidPrice( + price: 0.000000000000000001, + poolToken0Decimals: widget.poolToken0Decimals, + poolToken1Decimals: widget.poolToken1Decimals, + tickSpacing: widget.tickSpacing, + isReversed: widget.isReversed, ); - userTypedValue = minimumPrice.priceAsBaseToken.toString(); + userTypedValue = minimumPrice.price.toString(); + return adjustTypedAmountAndCallback(); } @@ -145,8 +147,8 @@ class _RangeSelectorState extends State with V3PoolConversorsMixi final BigInt currentTick = tickToClosestValidTick( tick: priceToTick( price: currentPrice, - poolToken0Decimals: widget.poolToken0.decimals, - poolToken1Decimals: widget.poolToken1.decimals, + poolToken0Decimals: widget.poolToken0Decimals, + poolToken1Decimals: widget.poolToken1Decimals, isReversed: widget.isReversed, ), tickSpacing: widget.tickSpacing, @@ -163,8 +165,8 @@ class _RangeSelectorState extends State with V3PoolConversorsMixi double nextPrice() { final nextPrice = tickToPrice( tick: nextTick(), - poolToken0Decimals: widget.poolToken0.decimals, - poolToken1Decimals: widget.poolToken1.decimals, + poolToken0Decimals: widget.poolToken0Decimals, + poolToken1Decimals: widget.poolToken1Decimals, ); if (widget.isReversed) return nextPrice.priceAsQuoteToken; @@ -179,8 +181,8 @@ class _RangeSelectorState extends State with V3PoolConversorsMixi if (widget.initialPrice != null) { final adjustedInitialPrice = priceToClosestValidPrice( price: widget.initialPrice ?? 0, - poolToken0Decimals: widget.poolToken0.decimals, - poolToken1Decimals: widget.poolToken1.decimals, + poolToken0Decimals: widget.poolToken0Decimals, + poolToken1Decimals: widget.poolToken1Decimals, tickSpacing: widget.tickSpacing, isReversed: widget.isReversed, ); diff --git a/lib/app/create/widgets/create_page_settings_dropdown.dart b/lib/app/create/widgets/create_page_settings_dropdown/create_page_settings_dropdown.dart similarity index 93% rename from lib/app/create/widgets/create_page_settings_dropdown.dart rename to lib/app/create/widgets/create_page_settings_dropdown/create_page_settings_dropdown.dart index 8692fc7..b79e67a 100644 --- a/lib/app/create/widgets/create_page_settings_dropdown.dart +++ b/lib/app/create/widgets/create_page_settings_dropdown/create_page_settings_dropdown.dart @@ -18,9 +18,9 @@ class CreatePageSettingsDropdown extends StatefulWidget { BuildContext showBelowContext, { required void Function() onClose, }) => - ZupDropdown.show( - showBelowContext: showBelowContext, - offset: const Offset(0, 90), + ZupPopover.show( + showBasedOnContext: showBelowContext, + adjustment: const Offset(0, 10), child: CreatePageSettingsDropdown(onClose: onClose), ); @@ -47,6 +47,14 @@ class _CreatePageSettingsDropdownState extends State } } + Widget sectionTitle(String title) => Text( + title, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 15, + ), + ); + @override void initState() { minTVLController.text = cache.getPoolSearchSettings().minLiquidityUSD.formatCurrency(isUSD: false); @@ -69,9 +77,7 @@ class _CreatePageSettingsDropdownState extends State decoration: BoxDecoration( color: ZupColors.white, borderRadius: BorderRadius.circular(12), - border: Border.all( - color: ZupColors.gray5, - ), + border: Border.all(color: ZupColors.gray5), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -80,10 +86,7 @@ class _CreatePageSettingsDropdownState extends State Row( mainAxisSize: MainAxisSize.min, children: [ - Text( - S.of(context).createPageSettingsDropdownMinimumLiquidity, - style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 15), - ), + sectionTitle(S.of(context).createPageSettingsDropdownMinimumLiquidity), const SizedBox(width: 8), ZupTooltip( key: const Key("min-liquidity-tooltip"), @@ -165,10 +168,7 @@ class _CreatePageSettingsDropdownState extends State const SizedBox(height: 10), Row( children: [ - Text( - S.of(context).createPageSettingsDropdownAllowedPoolTypes, - style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 15), - ), + sectionTitle(S.of(context).createPageSettingsDropdownAllowedPoolTypes), const SizedBox(width: 8), ZupTooltip( key: const Key("pool-types-allowed-tooltip"), diff --git a/lib/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button.dart b/lib/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button.dart new file mode 100644 index 0000000..0092138 --- /dev/null +++ b/lib/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:zup_app/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button_cubit.dart'; +import 'package:zup_app/core/cache.dart'; +import 'package:zup_app/core/injections.dart'; +import 'package:zup_app/core/repositories/protocol_repository.dart'; +import 'package:zup_app/gen/assets.gen.dart'; +import 'package:zup_app/l10n/gen/app_localizations.dart'; +import 'package:zup_app/widgets/zup_cached_image.dart'; +import 'package:zup_core/zup_singleton_cache.dart'; +import 'package:zup_ui_kit/zup_ui_kit.dart'; + +class ExchangesFilterDropdownButton extends StatefulWidget { + const ExchangesFilterDropdownButton({super.key}); + + @override + State createState() => _ExchangesFilterDropdownButtonState(); +} + +class _ExchangesFilterDropdownButtonState extends State { + final zupSingletonCache = inject(); + final protocolRepository = inject(); + final zupCachedImage = inject(); + final cache = inject(); + + ExchangesFilterDropdownButtonCubit? cubit; + + num get allowedProtocolsCount => cubit!.protocols + .where( + (protocol) => !cache.blockedProtocolsIds.contains(protocol.rawId), + ) + .length; + + Color get buttonForegroundColor { + if (allowedProtocolsCount == 0 && cubit!.protocols.isNotEmpty) return ZupColors.red; + + if (allowedProtocolsCount == cubit!.protocols.length) return ZupColors.gray; + return ZupColors.brand; + } + + String get protocolCounter { + if (allowedProtocolsCount == 0) return "0"; + + if (allowedProtocolsCount == cubit!.protocols.length) return "${cubit!.protocols.length}"; + + return "$allowedProtocolsCount/${cubit!.protocols.length}"; + } + + @override + void initState() { + cubit = ExchangesFilterDropdownButtonCubit(protocolRepository, zupSingletonCache); + + WidgetsBinding.instance.addPostFrameCallback((_) => cubit!.getSupportedProtocols()); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: cubit, + builder: (context, state) { + return ZupPrimaryButton( + key: const Key("exchanges-filter-dropdown-button"), + backgroundColor: Colors.transparent, + foregroundColor: buttonForegroundColor, + border: const BorderSide(color: ZupColors.gray4), + hoverElevation: 0, + icon: Assets.icons.switch2.svg(), + title: state.maybeWhen( + success: (protocols) => S.of(context).exchangesFilterDropdownButtonTitleNumered( + exchangesCount: protocolCounter, + ), + orElse: () => S.of(context).exchangesFilterDropdownButtonTitle, + ), + height: 40, + isLoading: state.maybeWhen( + loading: () => true, + orElse: () => false, + ), + onPressed: state == const ExchangesFilterDropdownButtonState.loading() + ? null + : (buttonContext) => state.whenOrNull( + error: () async { + WidgetsBinding.instance.addPostFrameCallback((_) { + ScaffoldMessenger.of(context).showSnackBar( + ZupSnackBar( + context, + message: S.of(context).exchangesFilterDropdownButtonErrorSnackBarMessage, + type: ZupSnackBarType.error, + maxWidth: 400, + ), + ); + }); + return null; + }, + success: (protocols) => ZupCheckboxListPopover.show( + buttonContext, + positionAdjustment: const Offset(-130, 10), + allSelectionButtonText: ( + clearAll: S.of(context).exchangesFilterDropdownButtonDropdownClearAll, + selectAll: S.of(context).exchangesFilterDropdownButtonDropdownSelectAll + ), + searchHintText: S.of(context).exchangesFilterDropdownButtonDropdownSearchHint, + searchNotFoundStateText: ( + description: S.of(context).exchangesFilterDropdownButtonDropdownNotFoundStateDescription, + title: S.of(context).exchangesFilterDropdownButtonDropdownNotFoundStateTitle + ), + onValueChanged: (items) { + setState( + () { + cache.saveBlockedProtocolIds( + blockedProtocolIds: items + .where((item) => !item.isChecked) + .map( + (item) => item.id!, + ) + .toList(), + ); + }, + ); + }, + items: protocols + .map( + (protocol) => ZupCheckboxItem( + id: protocol.rawId, + title: protocol.name, + icon: zupCachedImage.build(protocol.logo, radius: 50), + isChecked: !cache.blockedProtocolsIds.contains(protocol.rawId), + isDisabled: false, + ), + ) + .toList(), + ), + ), + ); + }, + ); + } +} diff --git a/lib/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button_cubit.dart b/lib/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button_cubit.dart new file mode 100644 index 0000000..86234e7 --- /dev/null +++ b/lib/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button_cubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:zup_app/core/dtos/protocol_dto.dart'; +import 'package:zup_app/core/mixins/keys_mixin.dart'; +import 'package:zup_app/core/repositories/protocol_repository.dart'; +import 'package:zup_core/zup_core.dart'; + +part 'exchanges_filter_dropdown_button_cubit.freezed.dart'; +part 'exchanges_filter_dropdown_button_state.dart'; + +class ExchangesFilterDropdownButtonCubit extends Cubit with KeysMixin { + ExchangesFilterDropdownButtonCubit(this._protocolRepository, this._zupSingletonCache) + : super(const ExchangesFilterDropdownButtonState.initial()); + + final ProtocolRepository _protocolRepository; + final ZupSingletonCache _zupSingletonCache; + + List _supportedProtocols = []; + List get protocols => _supportedProtocols; + + Future getSupportedProtocols() async { + try { + emit(const ExchangesFilterDropdownButtonState.loading()); + + _supportedProtocols = await _zupSingletonCache.run( + () async => await _protocolRepository.getAllSupportedProtocols(), + key: protocolsListKey, + ); + + _supportedProtocols.sort((a, b) => a.name.compareTo(b.name)); + + emit(ExchangesFilterDropdownButtonState.success(protocols)); + } catch (e) { + emit(const ExchangesFilterDropdownButtonState.error()); + } + } +} diff --git a/lib/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button_state.dart b/lib/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button_state.dart new file mode 100644 index 0000000..84b15fc --- /dev/null +++ b/lib/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button_state.dart @@ -0,0 +1,9 @@ +part of 'exchanges_filter_dropdown_button_cubit.dart'; + +@freezed +class ExchangesFilterDropdownButtonState with _$ExchangesFilterDropdownButtonState { + const factory ExchangesFilterDropdownButtonState.initial() = _Initial; + const factory ExchangesFilterDropdownButtonState.loading() = _Loading; + const factory ExchangesFilterDropdownButtonState.error() = _Error; + const factory ExchangesFilterDropdownButtonState.success(List protocols) = _Success; +} diff --git a/lib/core/cache.dart b/lib/core/cache.dart index 9b7dce5..cce223b 100644 --- a/lib/core/cache.dart +++ b/lib/core/cache.dart @@ -9,6 +9,7 @@ enum CacheKey { depositSettings, poolSearchSettings, areCookiesConsented, + blockedProtocolsIds, isTestnetMode; String get key => name; @@ -47,6 +48,14 @@ class Cache { return _cache.getBool(CacheKey.isTestnetMode.key) ?? false; } + Future saveBlockedProtocolIds({required List blockedProtocolIds}) async { + await _cache.setStringList(CacheKey.blockedProtocolsIds.key, blockedProtocolIds); + } + + List get blockedProtocolsIds { + return _cache.getStringList(CacheKey.blockedProtocolsIds.key) ?? []; + } + Future savePoolSearchSettings({required PoolSearchSettingsDto settings}) async { await _cache.setString(CacheKey.poolSearchSettings.key, jsonEncode(settings.toJson())); } diff --git a/lib/core/dtos/pool_search_filters_dto.dart b/lib/core/dtos/pool_search_filters_dto.dart new file mode 100644 index 0000000..4ce4f3d --- /dev/null +++ b/lib/core/dtos/pool_search_filters_dto.dart @@ -0,0 +1,26 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'pool_search_filters_dto.freezed.dart'; +part 'pool_search_filters_dto.g.dart'; + +@freezed +class PoolSearchFiltersDto with _$PoolSearchFiltersDto { + @JsonSerializable(explicitToJson: true) + const factory PoolSearchFiltersDto({ + @Default(0) num minTvlUsd, + @Default(false) bool testnetMode, + @Default([]) List allowedPoolTypes, + @Default([]) List blockedProtocols, + }) = _PoolSearchFiltersDto; + + const PoolSearchFiltersDto._(); + + factory PoolSearchFiltersDto.fromJson(Map json) => _$PoolSearchFiltersDtoFromJson(json); + + factory PoolSearchFiltersDto.fixture() => const PoolSearchFiltersDto( + allowedPoolTypes: ["V3", "V4"], + blockedProtocols: ["nuri-exchange"], + minTvlUsd: 121782617, + testnetMode: false, + ); +} diff --git a/lib/core/dtos/protocol_dto.dart b/lib/core/dtos/protocol_dto.dart index 78c7f44..3c23722 100644 --- a/lib/core/dtos/protocol_dto.dart +++ b/lib/core/dtos/protocol_dto.dart @@ -1,14 +1,19 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:zup_app/core/enums/protocol_id.dart'; part 'protocol_dto.freezed.dart'; part 'protocol_dto.g.dart'; +String _readRawProtocolId(Map map, String key) => map['id']; + @freezed class ProtocolDto with _$ProtocolDto { const ProtocolDto._(); @JsonSerializable(explicitToJson: true) const factory ProtocolDto({ + @Default("") @JsonKey(readValue: _readRawProtocolId) String rawId, + @Default(ProtocolId.unknown) @JsonKey(unknownEnumValue: ProtocolId.unknown) ProtocolId id, @Default("") String name, @Default("") String url, @Default("") String logo, @@ -17,6 +22,8 @@ class ProtocolDto with _$ProtocolDto { factory ProtocolDto.fromJson(Map json) => _$ProtocolDtoFromJson(json); factory ProtocolDto.fixture() => const ProtocolDto( + rawId: "1", + id: ProtocolId.unknown, name: "Uniswap", url: "https://app.uniswap.org/pool", logo: "https://raw.githubusercontent.com/trustwallet/assets/master/dapps/app.uniswap.org.png", diff --git a/lib/core/dtos/token_dto.dart b/lib/core/dtos/token_dto.dart index e69ddef..2c20e2a 100644 --- a/lib/core/dtos/token_dto.dart +++ b/lib/core/dtos/token_dto.dart @@ -13,7 +13,7 @@ class TokenDto with _$TokenDto { @Default("") String name, @Default("") String logoUrl, @Default({}) Map addresses, - @Default(0) int decimals, + @Default({}) Map decimals, }) = _TokenDto; factory TokenDto.fromJson(Map json) => _$TokenDtoFromJson(json); @@ -23,6 +23,13 @@ class TokenDto with _$TokenDto { factory TokenDto.fixture() => TokenDto( symbol: 'WETH', name: 'Wrapped Ether', + decimals: Map.fromEntries( + AppNetworks.values.where((network) => !network.isAllNetworks).map( + (network) { + return MapEntry(network.chainId, 18); + }, + ), + ), addresses: Map.fromEntries( AppNetworks.values.where((network) => !network.isAllNetworks).map( (network) { diff --git a/lib/core/dtos/yield_dto.dart b/lib/core/dtos/yield_dto.dart index 191d414..fb3ef57 100644 --- a/lib/core/dtos/yield_dto.dart +++ b/lib/core/dtos/yield_dto.dart @@ -48,7 +48,7 @@ class YieldDto with _$YieldDto { required num yield30d, required num yield90d, required int chainId, - required PoolType poolType, + @Default(PoolType.unknown) @JsonKey(unknownEnumValue: PoolType.unknown) PoolType poolType, @Default("0") String latestTick, @Default(0) num totalValueLockedUSD, @Default(EthereumConstants.zeroAddress) @JsonKey(name: "hooksAddress") String v4Hooks, @@ -62,6 +62,9 @@ class YieldDto with _$YieldDto { bool get isToken0Native => token0.addresses[network.chainId] == EthereumConstants.zeroAddress; bool get isToken1Native => token1.addresses[network.chainId] == EthereumConstants.zeroAddress; + int get token0NetworkDecimals => token0.decimals[network.chainId]!; + int get token1NetworkDecimals => token1.decimals[network.chainId]!; + factory YieldDto.fromJson(Map json) => _$YieldDtoFromJson(json); factory YieldDto.fixture() => YieldDto( @@ -75,7 +78,9 @@ class YieldDto with _$YieldDto { poolType: PoolType.v3, token0: TokenDto.fixture().copyWith( symbol: "USDC", - decimals: 6, + decimals: { + AppNetworks.sepolia.chainId: 6, + }, addresses: { AppNetworks.sepolia.chainId: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", }, @@ -84,7 +89,9 @@ class YieldDto with _$YieldDto { ), token1: TokenDto.fixture().copyWith( symbol: "WETH", - decimals: 18, + decimals: { + AppNetworks.sepolia.chainId: 18, + }, addresses: { AppNetworks.sepolia.chainId: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", }, diff --git a/lib/core/dtos/yields_dto.dart b/lib/core/dtos/yields_dto.dart index 2bda1bc..ac83d41 100644 --- a/lib/core/dtos/yields_dto.dart +++ b/lib/core/dtos/yields_dto.dart @@ -1,8 +1,10 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:zup_app/core/dtos/pool_search_filters_dto.dart'; import 'package:zup_app/core/dtos/protocol_dto.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/enums/pool_type.dart'; +import 'package:zup_app/core/enums/protocol_id.dart'; part 'yields_dto.freezed.dart'; part 'yields_dto.g.dart'; @@ -14,7 +16,7 @@ class YieldsDto with _$YieldsDto { @JsonSerializable(explicitToJson: true) const factory YieldsDto({ @Default([]) @JsonKey(name: "pools") List pools, - @Default(0) @JsonKey(name: "minTvlUsd") num minLiquidityUSD, + @Default(PoolSearchFiltersDto()) PoolSearchFiltersDto filters, }) = _YieldsDto; bool get isEmpty => pools.isEmpty; @@ -27,26 +29,26 @@ class YieldsDto with _$YieldsDto { factory YieldsDto.empty() => const YieldsDto( pools: [], - minLiquidityUSD: 0, ); - factory YieldsDto.fixture() => const YieldsDto( + factory YieldsDto.fixture() => YieldsDto( + filters: PoolSearchFiltersDto.fixture(), pools: [ YieldDto( latestTick: "637812562", positionManagerAddress: "0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4", poolType: PoolType.v3, - token0: TokenDto( + token0: const TokenDto( addresses: {11155111: "0x02a3e7E0480B668bD46b42852C58363F93e3bA5C"}, - decimals: 6, + decimals: {11155111: 6}, logoUrl: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/scroll/assets/0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4/logo.png", name: "USDC", symbol: "USDC", ), - token1: TokenDto( + token1: const TokenDto( addresses: {11155111: "0x5300000000000000000000000000000000000004"}, - decimals: 18, + decimals: {11155111: 18}, logoUrl: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/scroll/assets/0x5300000000000000000000000000000000000004/logo.png", name: "Wrapped Ether", @@ -61,6 +63,8 @@ class YieldsDto with _$YieldsDto { totalValueLockedUSD: 65434567890.21, feeTier: 500, protocol: ProtocolDto( + id: ProtocolId.pancakeSwapInfinityCL, + rawId: ProtocolId.pancakeSwapInfinityCL.toRawJsonValue, name: "PancakeSwap", logo: "https://raw.githubusercontent.com/trustwallet/assets/master/dapps/exchange.pancakeswap.finance.png", diff --git a/lib/core/enums/networks.dart b/lib/core/enums/networks.dart index a29915a..428c45b 100644 --- a/lib/core/enums/networks.dart +++ b/lib/core/enums/networks.dart @@ -2,13 +2,13 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:web3kit/web3kit.dart'; -import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/gen/assets.gen.dart'; enum AppNetworks { allNetworks, mainnet, // base, + // bnb, unichain, scroll, sepolia; @@ -47,6 +47,7 @@ enum AppNetworks { allNetworks => false, // base => false, unichain => false, + // bnb => false }; String get label => switch (this) { @@ -56,6 +57,7 @@ enum AppNetworks { allNetworks => "All Networks", // base => "Base", unichain => "Unichain", + // bnb => "BNB Chain", }; Widget get icon => switch (this) { @@ -65,6 +67,7 @@ enum AppNetworks { // base => Assets.logos.base.svg(), unichain => Assets.logos.unichain.svg(), allNetworks => Assets.icons.all.svg(), + // bnb => Assets.logos.bnbChain.svg() }; ChainInfo get chainInfo => switch (this) { @@ -100,10 +103,17 @@ enum AppNetworks { unichain => ChainInfo( hexChainId: "0x82", chainName: label, - blockExplorerUrls: const ["https://uniscan.xyz/"], + blockExplorerUrls: const ["https://uniscan.xyz"], nativeCurrency: NativeCurrencies.eth.currencyInfo, rpcUrls: [rpcUrl], ), + // bnb => ChainInfo( + // hexChainId => "0x38", + // chainName => label, + // blockExplorerUrls => const ["https://bscscan.com"], + // nativeCurrency => NativeCurrencies.bnb.currencyInfo, + // rpcUrls => [rpcUrl], + // ), }; String get wrappedNativeTokenAddress => switch (this) { @@ -112,48 +122,8 @@ enum AppNetworks { mainnet => "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", scroll => "0x5300000000000000000000000000000000000004", // base => "0x4200000000000000000000000000000000000006", - unichain => "0x4200000000000000000000000000000000000006" - }; - - TokenDto get wrappedNative => switch (this) { - allNetworks => throw UnimplementedError("allNetworks is not a valid network"), - sepolia => TokenDto( - addresses: {chainId: wrappedNativeTokenAddress}, - name: "Wrapped Ether", - decimals: NativeCurrencies.eth.currencyInfo.decimals, - symbol: "WETH", - logoUrl: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png", - ), - mainnet => TokenDto( - addresses: {chainId: wrappedNativeTokenAddress}, - decimals: NativeCurrencies.eth.currencyInfo.decimals, - name: "Wrapped Ether", - symbol: "WETH", - logoUrl: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png", - ), - scroll => TokenDto( - addresses: {chainId: wrappedNativeTokenAddress}, - decimals: NativeCurrencies.eth.currencyInfo.decimals, - name: "Wrapped Ether", - symbol: "WETH", - logoUrl: - "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/scroll/assets/0x5300000000000000000000000000000000000004/logo.png", - ), - // base => TokenDto( - // addresses: {chainId: wrappedNativeTokenAddress}, - // decimals: NativeCurrencies.eth.currencyInfo.decimals, - // name: "Wrapped Ether", - // symbol: "WETH", - // logoUrl: - // "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/base/assets/0x4200000000000000000000000000000000000006/logo.png", - // ), - unichain => TokenDto( - addresses: {chainId: wrappedNativeTokenAddress}, - decimals: NativeCurrencies.eth.currencyInfo.decimals, - name: "Wrapped Ether", - symbol: "WETH", - logoUrl: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/unichain/logo.png", - ), + unichain => "0x4200000000000000000000000000000000000006", + // bnb => "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", }; String get rpcUrl => switch (this) { @@ -163,6 +133,7 @@ enum AppNetworks { scroll => "https://scroll-rpc.publicnode.com", // base => "https://base-rpc.publicnode.com", unichain => "https://unichain-rpc.publicnode.com", + // bnb => "https://bsc-rpc.publicnode.com" }; Future openTx(String txHash) async { diff --git a/lib/core/enums/pool_type.dart b/lib/core/enums/pool_type.dart index b8945e3..9690241 100644 --- a/lib/core/enums/pool_type.dart +++ b/lib/core/enums/pool_type.dart @@ -4,7 +4,8 @@ enum PoolType { @JsonValue("V3") v3, @JsonValue("V4") - v4; + v4, + unknown; bool get isV3 => this == PoolType.v3; bool get isV4 => this == PoolType.v4; @@ -12,5 +13,6 @@ enum PoolType { String get label => switch (this) { PoolType.v3 => "V3", PoolType.v4 => "V4", + PoolType.unknown => "Unknown", }; } diff --git a/lib/core/enums/protocol_id.dart b/lib/core/enums/protocol_id.dart new file mode 100644 index 0000000..529166a --- /dev/null +++ b/lib/core/enums/protocol_id.dart @@ -0,0 +1,14 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'protocol_id.g.dart'; + +@JsonEnum(alwaysCreate: true) +enum ProtocolId { + @JsonValue("pancake-v4-cl") + pancakeSwapInfinityCL, + unknown; + + bool get isPancakeSwapInfinityCL => this == ProtocolId.pancakeSwapInfinityCL; + + String get toRawJsonValue => _$ProtocolIdEnumMap[this]!; +} diff --git a/lib/core/extensions/string_extension.dart b/lib/core/extensions/string_extension.dart index dcc2fe3..46afd09 100644 --- a/lib/core/extensions/string_extension.dart +++ b/lib/core/extensions/string_extension.dart @@ -1,5 +1,5 @@ extension StringExtension on String { - bool get isEmptyOrZero => isEmpty || this == "0"; + bool get isEmptyOrZero => isEmpty || num.tryParse(this) == 0; bool get isNotEmptyOrZero => !isEmptyOrZero; } diff --git a/lib/core/injections.dart b/lib/core/injections.dart index ec51a59..c4a2e38 100644 --- a/lib/core/injections.dart +++ b/lib/core/injections.dart @@ -7,6 +7,7 @@ import 'package:lottie/lottie.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:web3kit/web3kit.dart'; import 'package:zup_app/abis/erc_20.abi.g.dart'; +import 'package:zup_app/abis/pancake_swap_infinity_cl_pool_manager.abi.g.dart'; import 'package:zup_app/abis/uniswap_permit2.abi.g.dart'; import 'package:zup_app/abis/uniswap_v3_pool.abi.g.dart'; import 'package:zup_app/abis/uniswap_v3_position_manager.abi.g.dart'; @@ -18,6 +19,7 @@ import 'package:zup_app/core/debouncer.dart'; import 'package:zup_app/core/enums/app_environment.dart'; import 'package:zup_app/core/pool_service.dart'; import 'package:zup_app/core/repositories/positions_repository.dart'; +import 'package:zup_app/core/repositories/protocol_repository.dart'; import 'package:zup_app/core/repositories/tokens_repository.dart'; import 'package:zup_app/core/repositories/yield_repository.dart'; import 'package:zup_app/core/zup_analytics.dart'; @@ -120,14 +122,13 @@ Future setupInjections() async { inject.registerLazySingleton(() => EthereumAbiCoder()); + inject.registerLazySingleton( + () => PancakeSwapInfinityClPoolManager(), + ); + inject.registerLazySingleton( - () => PoolService( - inject(), - inject(), - inject(), - inject(), - inject(), - ), + () => PoolService(inject(), inject(), inject(), + inject(), inject(), inject()), ); inject.registerLazySingleton( @@ -152,6 +153,10 @@ Future setupInjections() async { inject.registerLazySingleton(() => UniswapV3Pool()); + inject.registerLazySingleton( + () => ProtocolRepository(zupApiDio: inject(instanceName: InjectInstanceNames.zupAPIDio)), + ); + // WARNING: this should be factory, as it's a controller and can/should be disposed inject.registerFactory( () => ConfettiController(duration: const Duration(seconds: 10)), diff --git a/lib/core/mixins/keys_mixin.dart b/lib/core/mixins/keys_mixin.dart index e4fe989..2c78c40 100644 --- a/lib/core/mixins/keys_mixin.dart +++ b/lib/core/mixins/keys_mixin.dart @@ -16,4 +16,6 @@ mixin KeysMixin { String tokenPriceCacheKey({required String tokenAddress, required AppNetworks network}) { return 'tokenPrice-$tokenAddress-${network.name}'; } + + String get protocolsListKey => 'zup-supported-protocols'; } diff --git a/lib/core/mixins/v3_pool_liquidity_calculations_mixin.dart b/lib/core/mixins/v3_pool_liquidity_calculations_mixin.dart index 2462a1d..80760ad 100644 --- a/lib/core/mixins/v3_pool_liquidity_calculations_mixin.dart +++ b/lib/core/mixins/v3_pool_liquidity_calculations_mixin.dart @@ -21,9 +21,9 @@ mixin V3PoolLiquidityCalculationsMixin { double priceUpper, ) { final liquidity = tokenYAmount / (sqrt(currentPrice) - sqrt(priceLower)); - final token1Amount = + final token0Amount = liquidity * ((sqrt(priceUpper) - sqrt(currentPrice)) / (sqrt(priceUpper) * sqrt(currentPrice))); - return token1Amount; + return token0Amount; } } diff --git a/lib/core/mixins/v4_pool_liquidity_calculations_mixin.dart b/lib/core/mixins/v4_pool_liquidity_calculations_mixin.dart index 4f1def4..298d5bc 100644 --- a/lib/core/mixins/v4_pool_liquidity_calculations_mixin.dart +++ b/lib/core/mixins/v4_pool_liquidity_calculations_mixin.dart @@ -1,15 +1,26 @@ mixin V4PoolLiquidityCalculationsMixin { - final _q96 = BigInt.parse("0x1000000000000000000000000"); + final _q96 = BigInt.from(2).pow(96); BigInt getLiquidityForAmount0(BigInt sqrtPriceAX96, BigInt sqrtPriceBX96, BigInt amount0) { - if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); + if (sqrtPriceAX96 > sqrtPriceBX96) { + final sqrtPriceAX96Before = sqrtPriceAX96; + sqrtPriceAX96 = sqrtPriceBX96; + sqrtPriceBX96 = sqrtPriceAX96Before; + } + + final numerator = (amount0 * sqrtPriceAX96) * sqrtPriceBX96; + final denominator = _q96 * (sqrtPriceBX96 - sqrtPriceAX96); - BigInt intermediate = ((sqrtPriceAX96 * sqrtPriceBX96) ~/ _q96); - return (amount0 * intermediate) ~/ (sqrtPriceBX96 - sqrtPriceAX96); + return numerator ~/ denominator; } BigInt getLiquidityForAmount1(BigInt sqrtPriceAX96, BigInt sqrtPriceBX96, BigInt amount1) { - if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); + if (sqrtPriceAX96 > sqrtPriceBX96) { + final sqrtPriceAX96Before = sqrtPriceAX96; + sqrtPriceAX96 = sqrtPriceBX96; + sqrtPriceBX96 = sqrtPriceAX96Before; + } + return (amount1 * _q96) ~/ (sqrtPriceBX96 - sqrtPriceAX96); } @@ -20,7 +31,11 @@ mixin V4PoolLiquidityCalculationsMixin { BigInt amount0, BigInt amount1, ) { - if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); + if (sqrtPriceAX96 > sqrtPriceBX96) { + final sqrtPriceAX96Before = sqrtPriceAX96; + sqrtPriceAX96 = sqrtPriceBX96; + sqrtPriceBX96 = sqrtPriceAX96Before; + } if (sqrtPriceX96 <= sqrtPriceAX96) { return getLiquidityForAmount0(sqrtPriceAX96, sqrtPriceBX96, amount0); @@ -75,10 +90,9 @@ mixin V4PoolLiquidityCalculationsMixin { price = maxUint256 ~/ price; } - BigInt maxUint32 = (BigInt.one << 32) - BigInt.one; - price = (price + maxUint32) >> 32; + final remainder = price & ((BigInt.one << 32) - BigInt.one); + final sqrtPriceX96 = price >> 32; - BigInt mask160 = (BigInt.one << 160) - BigInt.one; - return price & mask160; + return remainder > BigInt.zero ? sqrtPriceX96 + BigInt.one : sqrtPriceX96; } } diff --git a/lib/core/pool_service.dart b/lib/core/pool_service.dart index 8a85d1a..02a0d76 100644 --- a/lib/core/pool_service.dart +++ b/lib/core/pool_service.dart @@ -1,6 +1,7 @@ import 'package:clock/clock.dart'; import 'package:web3kit/core/dtos/transaction_response.dart'; import 'package:web3kit/web3kit.dart'; +import 'package:zup_app/abis/pancake_swap_infinity_cl_pool_manager.abi.g.dart'; import 'package:zup_app/abis/uniswap_v3_pool.abi.g.dart'; import 'package:zup_app/abis/uniswap_v3_position_manager.abi.g.dart'; import 'package:zup_app/abis/uniswap_v4_position_manager.abi.g.dart'; @@ -15,6 +16,7 @@ class PoolService with V4PoolLiquidityCalculationsMixin { final UniswapV3PositionManager _uniswapV3PositionManager; final UniswapV4PositionManager _uniswapV4PositionManager; final EthereumAbiCoder _ethereumAbiCoder; + final PancakeSwapInfinityClPoolManager _pancakeSwapInfinityClPoolManager; PoolService( this._uniswapV4StateView, @@ -22,9 +24,19 @@ class PoolService with V4PoolLiquidityCalculationsMixin { this._uniswapV3PositionManager, this._uniswapV4PositionManager, this._ethereumAbiCoder, + this._pancakeSwapInfinityClPoolManager, ); Future getPoolTick(YieldDto forYield) async { + if (forYield.protocol.id.isPancakeSwapInfinityCL) { + final pancakeSwapInfinityCLPoolManagerContract = _pancakeSwapInfinityClPoolManager.fromRpcProvider( + contractAddress: forYield.v4PoolManager!, + rpcUrl: forYield.network.rpcUrl, + ); + + return (await pancakeSwapInfinityCLPoolManagerContract.getSlot0(id: forYield.poolAddress)).tick; + } + if (forYield.poolType.isV4) { final stateView = _uniswapV4StateView.fromRpcProvider( contractAddress: forYield.v4StateView!, @@ -42,6 +54,37 @@ class PoolService with V4PoolLiquidityCalculationsMixin { return (await uniswapV3Pool.slot0()).tick; } + Future getSqrtPriceX96(YieldDto forYield) async { + if (forYield.protocol.id.isPancakeSwapInfinityCL) { + final pancakeSwapInfinityCLPoolManagerContract = _pancakeSwapInfinityClPoolManager.fromRpcProvider( + contractAddress: forYield.v4PoolManager!, + rpcUrl: forYield.network.rpcUrl, + ); + + return (await pancakeSwapInfinityCLPoolManagerContract.getSlot0(id: forYield.poolAddress)).sqrtPriceX96; + } + + if (forYield.poolType.isV4) { + final stateView = _uniswapV4StateView.fromRpcProvider( + contractAddress: forYield.v4StateView!, + rpcUrl: forYield.network.rpcUrl, + ); + + return (await stateView.getSlot0(poolId: forYield.poolAddress)).sqrtPriceX96; + } + + if (forYield.poolType.isV3) { + final uniswapV3Pool = _uniswapV3Pool.fromRpcProvider( + contractAddress: forYield.poolAddress, + rpcUrl: forYield.network.rpcUrl, + ); + + return (await uniswapV3Pool.slot0()).sqrtPriceX96; + } + + throw Exception('Unknown pool type; Cannot get sqrtPriceX96'); + } + Future sendV3PoolDepositTransaction( YieldDto depositOnYield, Signer signer, { @@ -126,9 +169,18 @@ class PoolService with V4PoolLiquidityCalculationsMixin { required BigInt maxAmount0ToDeposit, required BigInt maxAmount1ToDeposit, required String recipient, - required BigInt currentPoolTick, }) async { final isNativeDeposit = depositOnYield.isToken0Native || depositOnYield.isToken1Native; + final sqrtPriceX96 = await getSqrtPriceX96(depositOnYield); + final sqrtPriceAX96 = getSqrtPriceAtTick(tickLower); + final sqrtPriceBX96 = getSqrtPriceAtTick(tickUpper); + final liquidity = getLiquidityForAmounts( + sqrtPriceX96, + sqrtPriceAX96, + sqrtPriceBX96, + amount0toDeposit, + amount1ToDeposit, + ); final actions = _ethereumAbiCoder.encodePacked([ "uint8", @@ -159,15 +211,9 @@ class PoolService with V4PoolLiquidityCalculationsMixin { ], tickLower, tickUpper, - getLiquidityForAmounts( - getSqrtPriceAtTick(currentPoolTick), - getSqrtPriceAtTick(tickLower), - getSqrtPriceAtTick(tickUpper), - amount0toDeposit, - amount1ToDeposit, - ), - maxAmount0ToDeposit, - maxAmount1ToDeposit, + liquidity, + depositOnYield.isToken0Native ? amount0toDeposit : maxAmount0ToDeposit, + depositOnYield.isToken1Native ? amount1ToDeposit : maxAmount1ToDeposit, recipient, EthereumConstants.emptyBytes, ]); diff --git a/lib/core/repositories/protocol_repository.dart b/lib/core/repositories/protocol_repository.dart new file mode 100644 index 0000000..5233f3a --- /dev/null +++ b/lib/core/repositories/protocol_repository.dart @@ -0,0 +1,20 @@ +import 'package:dio/dio.dart'; +import 'package:zup_app/core/dtos/protocol_dto.dart'; + +class ProtocolRepository { + ProtocolRepository({required this.zupApiDio}); + + final Dio zupApiDio; + + Future> getAllSupportedProtocols() async { + final protocolsResponse = await zupApiDio.get("/protocols").onError((_, __) async { + return await zupApiDio.get("/protocols"); + }); + + return (protocolsResponse.data as List) + .map( + (protocol) => ProtocolDto.fromJson(protocol), + ) + .toList(); + } +} diff --git a/lib/core/repositories/yield_repository.dart b/lib/core/repositories/yield_repository.dart index cde644d..0f2ac4b 100644 --- a/lib/core/repositories/yield_repository.dart +++ b/lib/core/repositories/yield_repository.dart @@ -13,6 +13,7 @@ class YieldRepository { required String token1Address, required AppNetworks network, required PoolSearchSettingsDto searchSettings, + required List blockedProtocolIds, }) async { final response = await _zupAPIDio.post("/pools/search/${network.chainId}", queryParameters: { "token0Address": token0Address, @@ -20,6 +21,7 @@ class YieldRepository { }, data: { "filters": { "minTvlUsd": searchSettings.minLiquidityUSD, + "blockedProtocols": blockedProtocolIds, "allowedPoolTypes": [ if (searchSettings.allowV3Search) "V3", if (searchSettings.allowV4Search) "V4", @@ -34,6 +36,7 @@ class YieldRepository { required String token0InternalId, required String token1InternalId, required PoolSearchSettingsDto searchSettings, + required List blockedProtocolIds, bool testnetMode = false, }) async { final response = await _zupAPIDio.post("/pools/search/all", queryParameters: { @@ -43,6 +46,7 @@ class YieldRepository { "filters": { "minTvlUsd": searchSettings.minLiquidityUSD, "testnetMode": testnetMode, + "blockedProtocols": blockedProtocolIds, "allowedPoolTypes": [ if (searchSettings.allowV3Search) "V3", if (searchSettings.allowV4Search) "V4", diff --git a/lib/l10n/en.arb b/lib/l10n/en.arb index bdea6df..c5e8609 100644 --- a/lib/l10n/en.arb +++ b/lib/l10n/en.arb @@ -34,8 +34,8 @@ } } }, - "depositPageMinLiquiditySearchAlert": "Searched only for pools with more than {minLiquidity} TVL.", "depositPageTrySearchAllPools": "Try search all pools?", + "depositPageMinLiquiditySearchAlert": "You’ve set the search to only show pools with more than {minLiquidity}.", "@depositPageMinLiquiditySearchAlert": { "placeholders": { "minLiquidity": { @@ -166,6 +166,24 @@ "rangeSelectorMinRange": "Min Range", "rangeSelectorMaxRange": "Max Range", "loading": "Loading...", + "exchangesFilterDropdownButtonDropdownClearAll": "Clear All", + "exchangesFilterDropdownButtonDropdownSelectAll": "Select All", + "exchangesFilterDropdownButtonDropdownSearchHint": "Search by name", + "exchangesFilterDropdownButtonDropdownNotFoundStateTitle": "Not found", + "exchangesFilterDropdownButtonDropdownNotFoundStateDescription": "No supported exchanges found with this name", + "exchangesFilterDropdownButtonErrorSnackBarMessage": "Uh-oh! Something went wrong loading the exchanges. Please try refreshing the page.", + "exchangesFilterDropdownButtonTitle": "Exchanges", + "exchangesFilterDropdownButtonTitleNumered": "Exchanges ({exchangesCount})", + "@exchangesFilterDropdownButtonTitleNumered": { + "placeholders": { + "exchangesCount": { + "type": "String" + } + } + }, + "createPageSelectTokensStageTokenA": "Token A", + "createPageSelectTokensStageTokenB": "Token B", + "createPageSelectTokensStageSearchSettings": "Search Settings", "depositPageLoadingStep1Title": "Matching Tokens...", "depositPageLoadingStep1Description": "Pairing Token A and Token B to kick off the search for top yields!", "depositPageLoadingStep2Title": "Scanning the Pools...", @@ -175,8 +193,8 @@ "depositPageErrorStateTitle": "Oops! Something went wrong!", "depositPageErrorStateDescription": "We ran into a issue while trying to find the best pool. Give it another shot, and if it keeps happening, don’t hesitate to reach out to us!", "depositPageEmptyStateTitle": "No Pools Found", - "depositPageEmptyStateDescription": "Seems like that there are no pools on our supported protocols matching your selected tokens. Would you like to try another combination?", - "depositPageEmptyStateHelpButtonTitle": "Try another combination", + "depositPageEmptyStateDescription": "Seems like that there are no pools matching your defined settings at the moment. Would you like to either change your settings or try another combination?", + "depositPageEmptyStateHelpButtonTitle": "Go Back to New Position", "depositPageBackButtonTitle": "Select Pair", "depositPageTitle": "Add liquidity", "depositPageTimeFrameTooltipMessage": "Select a time-frame that matches your goal with this pool: a quick win (Short term), a balanced approach (Medium term), or a long haul (Long term).", diff --git a/lib/l10n/gen/app_localizations.dart b/lib/l10n/gen/app_localizations.dart index e0f6eac..69e8044 100644 --- a/lib/l10n/gen/app_localizations.dart +++ b/lib/l10n/gen/app_localizations.dart @@ -173,18 +173,18 @@ abstract class S { String depositPageSearchOnlyForPoolsWithMorethan( {required String minLiquidity}); - /// No description provided for @depositPageMinLiquiditySearchAlert. - /// - /// In en, this message translates to: - /// **'Searched only for pools with more than {minLiquidity} TVL.'** - String depositPageMinLiquiditySearchAlert({required String minLiquidity}); - /// No description provided for @depositPageTrySearchAllPools. /// /// In en, this message translates to: /// **'Try search all pools?'** String get depositPageTrySearchAllPools; + /// No description provided for @depositPageMinLiquiditySearchAlert. + /// + /// In en, this message translates to: + /// **'You’ve set the search to only show pools with more than {minLiquidity}.'** + String depositPageMinLiquiditySearchAlert({required String minLiquidity}); + /// No description provided for @slippageExplanation. /// /// In en, this message translates to: @@ -496,6 +496,73 @@ abstract class S { /// **'Loading...'** String get loading; + /// No description provided for @exchangesFilterDropdownButtonDropdownClearAll. + /// + /// In en, this message translates to: + /// **'Clear All'** + String get exchangesFilterDropdownButtonDropdownClearAll; + + /// No description provided for @exchangesFilterDropdownButtonDropdownSelectAll. + /// + /// In en, this message translates to: + /// **'Select All'** + String get exchangesFilterDropdownButtonDropdownSelectAll; + + /// No description provided for @exchangesFilterDropdownButtonDropdownSearchHint. + /// + /// In en, this message translates to: + /// **'Search by name'** + String get exchangesFilterDropdownButtonDropdownSearchHint; + + /// No description provided for @exchangesFilterDropdownButtonDropdownNotFoundStateTitle. + /// + /// In en, this message translates to: + /// **'Not found'** + String get exchangesFilterDropdownButtonDropdownNotFoundStateTitle; + + /// No description provided for @exchangesFilterDropdownButtonDropdownNotFoundStateDescription. + /// + /// In en, this message translates to: + /// **'No supported exchanges found with this name'** + String get exchangesFilterDropdownButtonDropdownNotFoundStateDescription; + + /// No description provided for @exchangesFilterDropdownButtonErrorSnackBarMessage. + /// + /// In en, this message translates to: + /// **'Uh-oh! Something went wrong loading the exchanges. Please try refreshing the page.'** + String get exchangesFilterDropdownButtonErrorSnackBarMessage; + + /// No description provided for @exchangesFilterDropdownButtonTitle. + /// + /// In en, this message translates to: + /// **'Exchanges'** + String get exchangesFilterDropdownButtonTitle; + + /// No description provided for @exchangesFilterDropdownButtonTitleNumered. + /// + /// In en, this message translates to: + /// **'Exchanges ({exchangesCount})'** + String exchangesFilterDropdownButtonTitleNumered( + {required String exchangesCount}); + + /// No description provided for @createPageSelectTokensStageTokenA. + /// + /// In en, this message translates to: + /// **'Token A'** + String get createPageSelectTokensStageTokenA; + + /// No description provided for @createPageSelectTokensStageTokenB. + /// + /// In en, this message translates to: + /// **'Token B'** + String get createPageSelectTokensStageTokenB; + + /// No description provided for @createPageSelectTokensStageSearchSettings. + /// + /// In en, this message translates to: + /// **'Search Settings'** + String get createPageSelectTokensStageSearchSettings; + /// No description provided for @depositPageLoadingStep1Title. /// /// In en, this message translates to: @@ -553,13 +620,13 @@ abstract class S { /// No description provided for @depositPageEmptyStateDescription. /// /// In en, this message translates to: - /// **'Seems like that there are no pools on our supported protocols matching your selected tokens. Would you like to try another combination?'** + /// **'Seems like that there are no pools matching your defined settings at the moment. Would you like to either change your settings or try another combination?'** String get depositPageEmptyStateDescription; /// No description provided for @depositPageEmptyStateHelpButtonTitle. /// /// In en, this message translates to: - /// **'Try another combination'** + /// **'Go Back to New Position'** String get depositPageEmptyStateHelpButtonTitle; /// No description provided for @depositPageBackButtonTitle. diff --git a/lib/l10n/gen/app_localizations_en.dart b/lib/l10n/gen/app_localizations_en.dart index b4ec1ad..5ef9c0a 100644 --- a/lib/l10n/gen/app_localizations_en.dart +++ b/lib/l10n/gen/app_localizations_en.dart @@ -57,12 +57,12 @@ class SEn extends S { } @override - String depositPageMinLiquiditySearchAlert({required String minLiquidity}) { - return 'Searched only for pools with more than $minLiquidity TVL.'; - } + String get depositPageTrySearchAllPools => 'Try search all pools?'; @override - String get depositPageTrySearchAllPools => 'Try search all pools?'; + String depositPageMinLiquiditySearchAlert({required String minLiquidity}) { + return 'You’ve set the search to only show pools with more than $minLiquidity.'; + } @override String get slippageExplanation => @@ -254,6 +254,46 @@ class SEn extends S { @override String get loading => 'Loading...'; + @override + String get exchangesFilterDropdownButtonDropdownClearAll => 'Clear All'; + + @override + String get exchangesFilterDropdownButtonDropdownSelectAll => 'Select All'; + + @override + String get exchangesFilterDropdownButtonDropdownSearchHint => + 'Search by name'; + + @override + String get exchangesFilterDropdownButtonDropdownNotFoundStateTitle => + 'Not found'; + + @override + String get exchangesFilterDropdownButtonDropdownNotFoundStateDescription => + 'No supported exchanges found with this name'; + + @override + String get exchangesFilterDropdownButtonErrorSnackBarMessage => + 'Uh-oh! Something went wrong loading the exchanges. Please try refreshing the page.'; + + @override + String get exchangesFilterDropdownButtonTitle => 'Exchanges'; + + @override + String exchangesFilterDropdownButtonTitleNumered( + {required String exchangesCount}) { + return 'Exchanges ($exchangesCount)'; + } + + @override + String get createPageSelectTokensStageTokenA => 'Token A'; + + @override + String get createPageSelectTokensStageTokenB => 'Token B'; + + @override + String get createPageSelectTokensStageSearchSettings => 'Search Settings'; + @override String get depositPageLoadingStep1Title => 'Matching Tokens...'; @@ -288,10 +328,10 @@ class SEn extends S { @override String get depositPageEmptyStateDescription => - 'Seems like that there are no pools on our supported protocols matching your selected tokens. Would you like to try another combination?'; + 'Seems like that there are no pools matching your defined settings at the moment. Would you like to either change your settings or try another combination?'; @override - String get depositPageEmptyStateHelpButtonTitle => 'Try another combination'; + String get depositPageEmptyStateHelpButtonTitle => 'Go Back to New Position'; @override String get depositPageBackButtonTitle => 'Select Pair'; diff --git a/lib/widgets/app_cookies_consent_widget.dart b/lib/widgets/app_cookies_consent_widget.dart index 8a35946..36cf04c 100644 --- a/lib/widgets/app_cookies_consent_widget.dart +++ b/lib/widgets/app_cookies_consent_widget.dart @@ -69,7 +69,7 @@ class AppCookieConsentWidget extends StatelessWidget { hoverElevation: 0, backgroundColor: ZupColors.brand6, foregroundColor: ZupColors.brand, - onPressed: () { + onPressed: (buttonContext) { onAccept(); cache.saveCookiesConsentStatus(status: true); }, diff --git a/lib/widgets/app_settings_dropdown.dart b/lib/widgets/app_settings_dropdown.dart index 2a72b7d..11c4959 100644 --- a/lib/widgets/app_settings_dropdown.dart +++ b/lib/widgets/app_settings_dropdown.dart @@ -8,9 +8,9 @@ import 'package:zup_ui_kit/zup_ui_kit.dart'; class AppSettingsDropdown extends StatefulWidget { const AppSettingsDropdown({super.key}); - static void show(BuildContext showBelowContext) => ZupDropdown.show( - showBelowContext: showBelowContext, - offset: const Offset(0, 16), + static void show(BuildContext showBelowContext) => ZupPopover.show( + showBasedOnContext: showBelowContext, + adjustment: const Offset(0, 16), child: const AppSettingsDropdown(), ); diff --git a/lib/widgets/token_avatar.dart b/lib/widgets/token_avatar.dart index 4a114f7..2d7a945 100644 --- a/lib/widgets/token_avatar.dart +++ b/lib/widgets/token_avatar.dart @@ -30,23 +30,17 @@ class TokenAvatar extends StatelessWidget { Widget build(BuildContext context) { return asset.logoUrl.isEmpty ? genericAvatar() - : zupCachedImage.build( - asset.logoUrl, + : zupCachedImage.build(asset.logoUrl, height: size, width: size, radius: 50, errorWidget: (_, __, ___) => genericAvatar(), - loadingBuilder: (context, child, loadingProgress) { - if (loadingProgress == null) return child; - - return Skeleton.ignore( - child: ZupCircularLoadingIndicator( - size: size, - backgroundColor: ZupColors.brand5, - indicatorColor: ZupColors.brand, - ), - ); - }, - ); + placeholder: Skeleton.ignore( + child: ZupCircularLoadingIndicator( + size: size, + backgroundColor: ZupColors.brand5, + indicatorColor: ZupColors.brand, + ), + )); } } diff --git a/lib/widgets/zup_cached_image.dart b/lib/widgets/zup_cached_image.dart index 573462b..aedfc61 100644 --- a/lib/widgets/zup_cached_image.dart +++ b/lib/widgets/zup_cached_image.dart @@ -15,7 +15,7 @@ class ZupCachedImage { double? height, double? width, double? radius, - ImageLoadingBuilder? loadingBuilder, + Widget? placeholder, ImageErrorWidgetBuilder? errorWidget, }) { return ClipRRect( @@ -26,14 +26,17 @@ class ZupCachedImage { borderRadius: BorderRadius.circular(radius ?? 0), border: Border.all(width: 0.5, color: ZupColors.gray5), ), - // cache not implemented yet because of web issue rendering images from other domains + // cache not implemented yet because of web issue rendering images from other domains (https://github.com/Baseflow/flutter_cached_network_image/issues/972) child: Image.network( _parseImageUrl(url), height: height, width: width, fit: BoxFit.cover, errorBuilder: errorWidget, - loadingBuilder: loadingBuilder, + frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { + if (frame == null) return placeholder ?? ZupCircularLoadingIndicator(size: height ?? 20); + return child; + }, webHtmlElementStrategy: WebHtmlElementStrategy.fallback, ), ), diff --git a/pubspec.lock b/pubspec.lock index f39ed55..b542fc9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -542,10 +542,10 @@ packages: dependency: transitive description: name: hashlib - sha256: f00cbea036b8a8ea19be5df1e233fb2e250858639d3488776c32f0bd60b41cc4 + sha256: a7b34c92005e9ccf462b71bcab40b5d5cff53669c7e87e6c7277f18bb075bf05 url: "https://pub.dev" source: hosted - version: "1.21.2" + version: "1.21.3" hashlib_codecs: dependency: transitive description: @@ -1293,7 +1293,7 @@ packages: description: path: "." ref: main - resolved-ref: "96c38241bbd55f27820c0fbe0e62ed53f2febed7" + resolved-ref: "50b4737ccfa02d8b657173e430ca197dc18b5993" url: "https://github.com/Zup-Protocol/web3kit.git" source: git version: "0.0.1" @@ -1342,7 +1342,7 @@ packages: description: path: "." ref: main - resolved-ref: "2fb18c6a9212718d0bb72d13f255436800d34c4a" + resolved-ref: "93b849fffe3a50e116c28d45530ffb28a007c5e1" url: "https://github.com/Zup-Protocol/zup-core.git" source: git version: "0.0.1" @@ -1351,7 +1351,7 @@ packages: description: path: "." ref: main - resolved-ref: cd1e5f42c34c93ec91f8da476ec19cc2d2435c36 + resolved-ref: "1d2a3b3890032536df60a14db88bf7cd8fe7e52e" url: "https://github.com/Zup-Protocol/zup-ui-kit.git" source: git version: "0.0.1" diff --git a/test/app/app_layout_test.dart b/test/app/app_layout_test.dart index af0f33e..7af2fba 100644 --- a/test/app/app_layout_test.dart +++ b/test/app/app_layout_test.dart @@ -59,13 +59,15 @@ void main() { initialPath: ZupNavigatorPaths.newPosition.path, routeBuilder: (context, settings, child) => PageRouteBuilder( settings: settings, - pageBuilder: (context, __, ___) => const AppPage(), + pageBuilder: (context, __, ___) => const SizedBox( + height: 500, + child: AppPage(), + ), ), ), ), device: isMobile ? GoldenDevice.mobile : GoldenDevice.pc, ); - zGoldenTest("When the device size is a mobile, it should have a bottom navbar instead of a top app bar", goldenFileName: "app_layout_navbar", (tester) async { await tester.pumpDeviceBuilder(await goldenBuilder(isMobile: true), wrapper: GoldenConfig.localizationsWrapper()); diff --git a/test/app/create/create_page_select_token_stage_test.dart b/test/app/create/create_page_select_token_stage_test.dart index 238a8c0..1e167ae 100644 --- a/test/app/create/create_page_select_token_stage_test.dart +++ b/test/app/create/create_page_select_token_stage_test.dart @@ -13,11 +13,13 @@ import 'package:zup_app/core/dtos/pool_search_settings_dto.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_app/core/injections.dart'; +import 'package:zup_app/core/repositories/protocol_repository.dart'; import 'package:zup_app/core/repositories/tokens_repository.dart'; import 'package:zup_app/core/zup_navigator.dart'; import 'package:zup_app/widgets/token_card.dart'; import 'package:zup_app/widgets/token_selector_modal/token_selector_modal_cubit.dart'; import 'package:zup_app/widgets/zup_cached_image.dart'; +import 'package:zup_core/zup_core.dart'; import '../../golden_config.dart'; import '../../mocks.dart'; @@ -28,12 +30,16 @@ void main() { late Wallet wallet; late Cache cache; late ZupNavigator zupNavigator; + late ProtocolRepository protocolRepository; + late ZupSingletonCache zupSingletonCache; setUp(() { appCubit = AppCubitMock(); tokensRepository = TokensRepositoryMock(); wallet = WalletMock(); zupNavigator = ZupNavigatorMock(); + protocolRepository = ProtocolRepositoryMock(); + zupSingletonCache = ZupSingletonCache.shared; registerFallbackValue(AppNetworks.sepolia); @@ -43,6 +49,8 @@ void main() { inject.registerFactory(() => Debouncer(milliseconds: 0)); inject.registerFactory(() => zupNavigator); inject.registerFactory(() => cache); + inject.registerFactory(() => zupSingletonCache); + inject.registerFactory(() => protocolRepository); inject.registerLazySingleton( () => TokenSelectorModalCubit(tokensRepository, appCubit, wallet), @@ -84,7 +92,7 @@ void main() { "When selecting the B token with the same address as A token, it should change the A token to null, and the B token to the selected token", goldenFileName: "create_page_select_tokens_stage_change_b_token_to_same_token_as_a", (tester) async { const selectedNetwork = AppNetworks.sepolia; - final token0 = selectedNetwork.wrappedNative; + final token0 = TokenDto.fixture(); when(() => tokensRepository.getPopularTokens(any())).thenAnswer( (_) async => [token0], @@ -104,7 +112,7 @@ void main() { "When selecting the A token with the same address as B token, it should change the B token to null and the A token to the selected token", goldenFileName: "create_page_select_tokens_stage_change_a_token_to_same_token_as_b", (tester) async { const selectedNetwork = AppNetworks.sepolia; - final token0 = selectedNetwork.wrappedNative; + final token0 = TokenDto.fixture(); when(() => appCubit.currentChainId).thenReturn(selectedNetwork.chainId); when(() => tokensRepository.getPopularTokens(any())).thenAnswer( diff --git a/test/app/create/create_page_test.dart b/test/app/create/create_page_test.dart index 49a6888..8658efa 100644 --- a/test/app/create/create_page_test.dart +++ b/test/app/create/create_page_test.dart @@ -7,8 +7,10 @@ import 'package:zup_app/core/cache.dart'; import 'package:zup_app/core/dtos/pool_search_settings_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_app/core/injections.dart'; +import 'package:zup_app/core/repositories/protocol_repository.dart'; import 'package:zup_app/core/zup_navigator.dart'; import 'package:zup_app/widgets/zup_cached_image.dart'; +import 'package:zup_core/zup_core.dart'; import '../../golden_config.dart'; import '../../mocks.dart'; @@ -25,6 +27,8 @@ void main() { inject.registerFactory(() => mockZupCachedImage()); inject.registerFactory(() => appCubit); inject.registerFactory(() => ZupNavigatorMock()); + inject.registerFactory(() => ZupSingletonCache.shared); + inject.registerFactory(() => ProtocolRepositoryMock()); when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto.fixture()); when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); diff --git a/test/app/create/deposit/deposit_cubit_test.dart b/test/app/create/deposit/deposit_cubit_test.dart index 551aed1..541c0bf 100644 --- a/test/app/create/deposit/deposit_cubit_test.dart +++ b/test/app/create/deposit/deposit_cubit_test.dart @@ -8,6 +8,7 @@ import 'package:zup_app/app/app_cubit/app_cubit.dart'; import 'package:zup_app/app/create/deposit/deposit_cubit.dart'; import 'package:zup_app/core/cache.dart'; import 'package:zup_app/core/dtos/deposit_settings_dto.dart'; +import 'package:zup_app/core/dtos/pool_search_filters_dto.dart'; import 'package:zup_app/core/dtos/pool_search_settings_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/dtos/yields_dto.dart'; @@ -60,11 +61,12 @@ void main() { ); when(() => appCubit.isTestnetMode).thenReturn(false); - + when(() => cache.blockedProtocolsIds).thenReturn([]); when(() => yieldRepository.getAllNetworksYield( token0InternalId: any(named: "token0InternalId"), token1InternalId: any(named: "token1InternalId"), searchSettings: any(named: "searchSettings"), + blockedProtocolIds: any(named: "blockedProtocolIds"), testnetMode: any(named: "testnetMode"))).thenAnswer((_) async => YieldsDto.fixture()); when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); @@ -99,7 +101,7 @@ void main() { await zupSingletonCache.clear(); }); - group("When calling `setup`, the cubit should register a periodic task to get the pool tick every minute. ", () { + group("When calling `setup`, the cubit should register a periodic task to get the pool tick every half minute. ", () { test("And if the selected yield is not null, it should execute the task to get the pool tick", () async { BigInt? actualLastEmittedPoolTick; int eventsCounter = 0; @@ -120,7 +122,7 @@ void main() { async.elapse(const Duration(minutes: minutesPassed)); expect(actualLastEmittedPoolTick, poolTick); - expect(eventsCounter, minutesPassed); + expect(eventsCounter, minutesPassed * 2); }); }); @@ -181,6 +183,7 @@ void main() { token0Address: any(named: "token0Address"), token1Address: any(named: "token1Address"), searchSettings: any(named: "searchSettings"), + blockedProtocolIds: any(named: "blockedProtocolIds"), network: any(named: "network"))).thenAnswer( (_) async => YieldsDto.fixture(), ); @@ -195,6 +198,7 @@ void main() { token1Address: token1Address, searchSettings: any(named: "searchSettings"), network: any(named: "network"), + blockedProtocolIds: any(named: "blockedProtocolIds"), )).called(1); }); @@ -204,18 +208,21 @@ void main() { const minLiquidityUSD = 123; when(() => yieldRepository.getSingleNetworkYield( + blockedProtocolIds: any(named: "blockedProtocolIds"), token0Address: any(named: "token0Address"), token1Address: any(named: "token1Address"), searchSettings: any(named: "searchSettings"), network: any(named: "network"))).thenAnswer( - (_) async => const YieldsDto(pools: [], minLiquidityUSD: minLiquidityUSD), + (_) async => const YieldsDto(pools: [], filters: PoolSearchFiltersDto(minTvlUsd: minLiquidityUSD)), ); expectLater( sut.stream, emitsInOrder([ const DepositState.loading(), - const DepositState.noYields(minLiquiditySearched: minLiquidityUSD), + const DepositState.noYields( + filtersApplied: PoolSearchFiltersDto(minTvlUsd: minLiquidityUSD), + ), ])); await sut.getBestPools(token0AddressOrId: "", token1AddressOrId: ""); @@ -225,6 +232,7 @@ void main() { final pools = YieldsDto.fixture(); when(() => yieldRepository.getSingleNetworkYield( + blockedProtocolIds: any(named: "blockedProtocolIds"), token0Address: any(named: "token0Address"), token1Address: any(named: "token1Address"), searchSettings: any(named: "searchSettings"), @@ -237,6 +245,7 @@ void main() { test("When calling `getBestPools` and receiving an error, it should emit the error state", () async { when(() => yieldRepository.getSingleNetworkYield( + blockedProtocolIds: any(named: "blockedProtocolIds"), token0Address: any(named: "token0Address"), token1Address: any(named: "token1Address"), searchSettings: any(named: "searchSettings"), @@ -454,7 +463,8 @@ void main() { ); test( - """When calling 'getSelectedPoolTick', it should use the zup singleton cache with a expiration of 1 minute""", + """When calling 'getSelectedPoolTick', it should use the zup singleton cache + with a expiration of half a minute (-1 second to not cause race conditions)""", () async { final selectedYield = YieldDto.fixture(); @@ -475,7 +485,7 @@ void main() { verify(() => zupSingletonCache.run( any(), key: "poolTick-${selectedYield.poolAddress}-${selectedYield.network.name}", - expiration: const Duration(minutes: 1), + expiration: const Duration(seconds: 30 - 1), ignoreCache: false, )).called(1); }, @@ -689,6 +699,7 @@ void main() { when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 129816)); when(() => yieldRepository.getSingleNetworkYield( + blockedProtocolIds: any(named: "blockedProtocolIds"), token0Address: any(named: "token0Address"), token1Address: any(named: "token1Address"), network: any(named: "network"), @@ -698,6 +709,7 @@ void main() { await sut.getBestPools(token0AddressOrId: "0x", token1AddressOrId: "0x", ignoreMinLiquidity: true); verify(() => yieldRepository.getSingleNetworkYield( + blockedProtocolIds: any(named: "blockedProtocolIds"), token0Address: any(named: "token0Address"), token1Address: any(named: "token1Address"), network: any(named: "network"), @@ -710,6 +722,7 @@ void main() { when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 129816)); when(() => yieldRepository.getAllNetworksYield( + blockedProtocolIds: any(named: "blockedProtocolIds"), token0InternalId: any(named: "token0InternalId"), token1InternalId: any(named: "token1InternalId"), searchSettings: any(named: "searchSettings"), @@ -718,6 +731,7 @@ void main() { await sut.getBestPools(token0AddressOrId: "0x", token1AddressOrId: "0x", ignoreMinLiquidity: true); verify(() => yieldRepository.getAllNetworksYield( + blockedProtocolIds: any(named: "blockedProtocolIds"), token0InternalId: any(named: "token0InternalId"), token1InternalId: any(named: "token1InternalId"), searchSettings: PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: 0), @@ -730,6 +744,7 @@ void main() { when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: minLiquiditySaved)); when(() => yieldRepository.getSingleNetworkYield( + blockedProtocolIds: any(named: "blockedProtocolIds"), token0Address: any(named: "token0Address"), token1Address: any(named: "token1Address"), network: any(named: "network"), @@ -739,6 +754,7 @@ void main() { await sut.getBestPools(token0AddressOrId: "0x", token1AddressOrId: "0x", ignoreMinLiquidity: false); verify(() => yieldRepository.getSingleNetworkYield( + blockedProtocolIds: any(named: "blockedProtocolIds"), token0Address: any(named: "token0Address"), token1Address: any(named: "token1Address"), network: any(named: "network"), @@ -773,6 +789,7 @@ void main() { await sut.getBestPools(token0AddressOrId: token0Address, token1AddressOrId: token1Address); verify(() => yieldRepository.getAllNetworksYield( + blockedProtocolIds: any(named: "blockedProtocolIds"), token0InternalId: token0Address, token1InternalId: token1Address, searchSettings: any(named: "searchSettings"), @@ -787,4 +804,39 @@ void main() { expect(sut.selectedYieldTimeframe, selectedYieldTimeFrame); }); + + test("""When calling 'getBestPools' and all networks is the selected network, + it should call the repository to get it passing the blocked protocol ids got + from the cache""", () async { + final cachedBlockedProtocolIds = ["0x1", "0x2", "ababa"]; + when(() => cache.blockedProtocolsIds).thenReturn(cachedBlockedProtocolIds); + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); + + await sut.getBestPools(token0AddressOrId: "0x", token1AddressOrId: "0x"); + + verify(() => yieldRepository.getAllNetworksYield( + blockedProtocolIds: cachedBlockedProtocolIds, + token0InternalId: any(named: "token0InternalId"), + token1InternalId: any(named: "token1InternalId"), + searchSettings: any(named: "searchSettings"), + )).called(1); + }); + + test("""When calling 'getBestPools' and all networks is not the selected network, + it should call the repository to get it passing the blocked protocol ids got + from the cache""", () async { + final cachedBlockedProtocolIds = ["017628761", "asaas", "ababa"]; + when(() => cache.blockedProtocolsIds).thenReturn(cachedBlockedProtocolIds); + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.sepolia); + + await sut.getBestPools(token0AddressOrId: "0x", token1AddressOrId: "0x"); + + verify(() => yieldRepository.getSingleNetworkYield( + blockedProtocolIds: cachedBlockedProtocolIds, + network: any(named: "network"), + token0Address: any(named: "token0Address"), + token1Address: any(named: "token1Address"), + searchSettings: any(named: "searchSettings"), + )).called(1); + }); } diff --git a/test/app/create/deposit/deposit_page_test.dart b/test/app/create/deposit/deposit_page_test.dart index f042d3f..43b85ea 100644 --- a/test/app/create/deposit/deposit_page_test.dart +++ b/test/app/create/deposit/deposit_page_test.dart @@ -18,6 +18,7 @@ import 'package:zup_app/app/create/deposit/deposit_page.dart'; import 'package:zup_app/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal.dart'; import 'package:zup_app/core/cache.dart'; import 'package:zup_app/core/dtos/deposit_settings_dto.dart'; +import 'package:zup_app/core/dtos/pool_search_filters_dto.dart'; import 'package:zup_app/core/dtos/pool_search_settings_dto.dart'; import 'package:zup_app/core/dtos/token_price_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; @@ -154,6 +155,7 @@ void main() { zGoldenTest("When initializing the page it should call setup in the cubit", (tester) async { await tester.runAsync(() async { await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); }); verify(() => cubit.setup()).called(1); @@ -169,6 +171,7 @@ void main() { await tester.runAsync(() async { await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); }); verify( @@ -185,6 +188,7 @@ void main() { await tester.runAsync(() async { await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); }); await tester.pumpAndSettle(); @@ -192,9 +196,14 @@ void main() { zGoldenTest("When the cubit state is noYields with no min liquidity searched, it should just show the noYields state", goldenFileName: "deposit_page_no_yields", (tester) async { - when(() => cubit.state).thenReturn(const DepositState.noYields(minLiquiditySearched: 0)); + when(() => cubit.state).thenReturn( + const DepositState.noYields( + filtersApplied: PoolSearchFiltersDto(minTvlUsd: 0), + ), + ); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.pumpAndSettle(); }); @@ -204,9 +213,14 @@ void main() { with a helper text saying it, and a button to search all pools""", goldenFileName: "deposit_page_no_yields_filtered_by_min_liquidity", (tester) async { - when(() => cubit.state).thenReturn(const DepositState.noYields(minLiquiditySearched: 97654)); + when(() => cubit.state).thenReturn( + const DepositState.noYields( + filtersApplied: PoolSearchFiltersDto(minTvlUsd: 97654), + ), + ); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.pumpAndSettle(); }, @@ -222,9 +236,14 @@ void main() { ignoreMinLiquidity: any(named: "ignoreMinLiquidity")), ).thenAnswer((_) async {}); - when(() => cubit.state).thenReturn(const DepositState.noYields(minLiquiditySearched: 97654)); + when(() => cubit.state).thenReturn( + const DepositState.noYields( + filtersApplied: PoolSearchFiltersDto(minTvlUsd: 97654), + ), + ); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("search-all-pools-button"))); await tester.pumpAndSettle(); @@ -241,16 +260,17 @@ void main() { zGoldenTest("""When clicking the helper button in the no yields state, it should navigate back to choose tokens stage""", (tester) async { - when(() => navigator.back(any())).thenAnswer((_) async {}); + when(() => navigator.navigateToNewPosition()).thenAnswer((_) async {}); - when(() => cubit.state).thenReturn(const DepositState.noYields(minLiquiditySearched: 0)); + when(() => cubit.state).thenReturn(const DepositState.noYields(filtersApplied: PoolSearchFiltersDto())); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("help-button"))); await tester.pumpAndSettle(); - verify(() => navigator.back(any())).called(1); + verify(() => navigator.navigateToNewPosition()).called(1); }); zGoldenTest("When the cubit state is error, it should show the error state", goldenFileName: "deposit_page_error", @@ -258,6 +278,7 @@ void main() { when(() => cubit.state).thenReturn(const DepositState.error()); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.pumpAndSettle(); }); @@ -272,6 +293,7 @@ void main() { when(() => cubit.state).thenReturn(const DepositState.error()); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("help-button"))); await tester.pumpAndSettle(); @@ -287,6 +309,7 @@ void main() { when(() => cubit.state).thenReturn(DepositState.success(yields)); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.pumpAndSettle(); }); @@ -299,6 +322,7 @@ void main() { when(() => cubit.state).thenReturn(DepositState.success(yields)); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.pumpAndSettle(); }); @@ -311,6 +335,7 @@ void main() { when(() => cubit.state).thenReturn(DepositState.success(yields)); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.pumpAndSettle(); }); @@ -319,11 +344,14 @@ void main() { search has zero, but the user has a local filter set, it should show a text and a button to search only pools with the local filter amount set""", goldenFileName: "deposit_page_success_filtered_by_min_liquidity_local_filter_set", (tester) async { - final yields = YieldsDto.fixture().copyWith(minLiquidityUSD: 0); // api filter returns 0 + final yields = YieldsDto.fixture().copyWith( + filters: const PoolSearchFiltersDto(minTvlUsd: 0), + ); // api filter returns 0 when(() => cubit.poolSearchSettings).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 2189)); // local filter set when(() => cubit.state).thenReturn(DepositState.success(yields)); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.pumpAndSettle(); }); @@ -331,7 +359,10 @@ void main() { zGoldenTest("""When clicking in the button to search all pools in the success state that is with a filter for min liquidity, it should call the cubit to get pools with the ignore min liquidity flag""", (tester) async { - final yields = YieldsDto.fixture().copyWith(minLiquidityUSD: 12675); + final yields = YieldsDto.fixture().copyWith( + filters: const PoolSearchFiltersDto(minTvlUsd: 12675), + ); + when(() => cubit.poolSearchSettings).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 12675)); when(() => cubit.state).thenReturn(DepositState.success(yields)); when(() => cubit.getBestPools( @@ -340,6 +371,7 @@ void main() { ignoreMinLiquidity: any(named: "ignoreMinLiquidity"))).thenAnswer((_) async {}); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("hide-show-all-pools-button"))); await tester.pumpAndSettle(); @@ -356,7 +388,10 @@ void main() { zGoldenTest("""When clicking in the button to search only pools with more than x amount in the success state that is without a filter for min liquidity, it should call the cubit to get pools with the min liquidity set to not be ignored""", (tester) async { - final yields = YieldsDto.fixture().copyWith(minLiquidityUSD: 0); // api filter returns 0 + final yields = YieldsDto.fixture().copyWith( + filters: const PoolSearchFiltersDto(minTvlUsd: 0), + ); // api filter returns 0 + when(() => cubit.poolSearchSettings).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 12675)); // local filter set when(() => cubit.state).thenReturn(DepositState.success(yields)); when(() => cubit.getBestPools( @@ -365,6 +400,7 @@ void main() { ignoreMinLiquidity: any(named: "ignoreMinLiquidity"))).thenAnswer((_) async {}); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("hide-show-all-pools-button"))); await tester.pumpAndSettle(); @@ -428,6 +464,7 @@ void main() { when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("back-button"))); await tester.pumpAndSettle(); @@ -440,6 +477,7 @@ void main() { when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.hover(find.byKey(const Key("timeframe-tooltip"))); await tester.pumpAndSettle(); @@ -451,6 +489,7 @@ void main() { when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.hover(find.byKey(const Key("timeframe-tooltip"))); await tester.pumpAndSettle(); @@ -476,6 +515,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); + await tester.pumpAndSettle(); }); }); @@ -484,6 +524,7 @@ void main() { when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("yield-card-24h"))); await tester.pumpAndSettle(); @@ -502,6 +543,7 @@ void main() { when(() => cubit.selectedYield).thenReturn(selectedYield); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("yield-card-30d"))); await tester.pumpAndSettle(); @@ -521,6 +563,7 @@ void main() { when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); await tester.pumpAndSettle(); @@ -538,6 +581,7 @@ void main() { when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); await tester.pumpAndSettle(); @@ -558,6 +602,7 @@ void main() { when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); await tester.pumpAndSettle(); @@ -580,6 +625,7 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); + await tester.pumpAndSettle(); }); }); @@ -595,6 +641,7 @@ void main() { when(() => cubit.poolTickStream).thenAnswer((_) => Stream.value(BigInt.from(174072))); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("reverse-tokens-reversed"))); await tester.pumpAndSettle(); @@ -616,6 +663,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("min-price-selector")), "1000"); FocusManager.instance.primaryFocus?.unfocus(); @@ -643,6 +691,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("min-price-selector")), "1000"); FocusManager.instance.primaryFocus?.unfocus(); @@ -671,6 +720,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("min-price-selector")), "90000000000"); FocusManager.instance.primaryFocus?.unfocus(); @@ -694,6 +744,8 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("min-price-selector")), "1200"); FocusManager.instance.primaryFocus?.unfocus(); @@ -721,6 +773,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("min-price-selector")), "0.000000001"); FocusManager.instance.primaryFocus?.unfocus(); @@ -745,6 +798,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("min-price-selector")), "1"); FocusManager.instance.primaryFocus?.unfocus(); @@ -776,6 +830,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("min-price-selector")), "1"); FocusManager.instance.primaryFocus?.unfocus(); @@ -799,6 +854,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("5-percent-range-button"))); await tester.pumpAndSettle(); @@ -823,6 +879,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("5-percent-range-button"))); await tester.pumpAndSettle(); @@ -849,6 +906,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("5-percent-range-button"))); await tester.pumpAndSettle(); @@ -871,6 +929,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("5-percent-range-button"))); await tester.pumpAndSettle(); @@ -894,6 +953,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("20-percent-range-button"))); await tester.pumpAndSettle(); @@ -916,6 +976,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("20-percent-range-button"))); await tester.pumpAndSettle(); @@ -939,6 +1000,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("50-percent-range-button"))); await tester.pumpAndSettle(); @@ -961,6 +1023,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("50-percent-range-button"))); await tester.pumpAndSettle(); @@ -983,6 +1046,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("max-price-selector")), "1"); FocusManager.instance.primaryFocus?.unfocus(); @@ -1007,6 +1071,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("50-percent-range-button"))); await tester.pumpAndSettle(); @@ -1034,6 +1099,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("50-percent-range-button"))); await tester.pumpAndSettle(); @@ -1060,6 +1126,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("min-price-selector")), "1"); FocusManager.instance.primaryFocus?.unfocus(); @@ -1086,14 +1153,18 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("min-price-selector")), "2"); FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("max-price-selector")), "1"); FocusManager.instance.primaryFocus?.unfocus(); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("deposit-button"))); await tester.pumpAndSettle(); @@ -1115,6 +1186,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1139,6 +1211,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1165,6 +1238,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1194,6 +1268,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1221,6 +1296,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1248,6 +1324,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1275,6 +1352,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1304,6 +1382,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1333,6 +1412,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1369,6 +1449,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1404,6 +1485,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1441,6 +1523,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1479,6 +1562,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1515,6 +1599,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1550,6 +1635,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1592,6 +1678,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1632,6 +1719,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1663,6 +1751,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1692,6 +1781,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1727,6 +1817,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -1761,6 +1852,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); }); @@ -1818,7 +1910,8 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); }); }, @@ -1847,7 +1940,8 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("base-token-input-card")), "1"); @@ -1885,7 +1979,8 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); @@ -1922,7 +2017,8 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); @@ -1962,6 +2058,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -2002,7 +2099,8 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); @@ -2037,7 +2135,8 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("min-price-selector")), "1"); @@ -2074,7 +2173,8 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key("min-price-selector")), "0.0000001"); @@ -2145,6 +2245,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(null); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -2169,6 +2270,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(null); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -2194,6 +2296,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(null); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -2219,6 +2322,7 @@ void main() { when(() => cubit.latestPoolTick).thenReturn(null); await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-section")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -2484,4 +2588,50 @@ void main() { verifyNever(() => appCubit.updateAppNetwork(any())); }, ); + + zGoldenTest( + """When emitting a new pool tick, and the deposit amounts are already typed, + it should update the amount that have been calculated by the typed amount""", + goldenFileName: "deposit_page_pool_tick_update_deposit_amount", + (tester) async { + await tester.runAsync(() async { + final selectedYield = YieldsDto.fixture().best24hYield; + final currentPriceAsTick = BigInt.from(174072); + final nextPriceAsTick = BigInt.from(261892); + final poolTickStreamController = StreamController.broadcast(); + + final signer = SignerMock(); + + when(() => wallet.signer).thenReturn(signer); + when(() => wallet.signerStream).thenAnswer((_) => Stream.value(signer)); + when(() => cubit.getWalletTokenAmount(selectedYield.token0.addresses[selectedYield.network.chainId]!, + network: any(named: "network"))).thenAnswer( + (_) => Future.value(347537253), + ); + when(() => cubit.getWalletTokenAmount(selectedYield.token1.addresses[selectedYield.network.chainId]!, + network: any(named: "network"))).thenAnswer( + (_) => Future.value(32576352673), + ); + when(() => cubit.selectYield(any(), any())).thenAnswer((_) => Future.value()); + when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); + when(() => cubit.selectedYield).thenReturn(selectedYield); + when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.poolTickStream).thenAnswer((_) => poolTickStreamController.stream); + when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); + + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.tap(find.byKey(const Key("yield-card-24h"))); + await tester.pumpAndSettle(); + await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); + await tester.pumpAndSettle(); + + await tester.enterText(find.byKey(const Key("quote-token-input-card")), "1"); + await tester.pumpAndSettle(); + + poolTickStreamController.add(nextPriceAsTick); + when(() => cubit.latestPoolTick).thenReturn(nextPriceAsTick); + await tester.pumpAndSettle(); + }); + }, + ); } diff --git a/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range.png b/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range.png index be0223a..6241290 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range.png and b/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range_reverse_tokens.png index 289d19d..82dbc33 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_base_token_input_enabled_after_loading.png b/test/app/create/deposit/goldens/deposit_page_base_token_input_enabled_after_loading.png index bebcbfa..b9d38eb 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_base_token_input_enabled_after_loading.png and b/test/app/create/deposit/goldens/deposit_page_base_token_input_enabled_after_loading.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_base_token_input_loading.png b/test/app/create/deposit/goldens/deposit_page_base_token_input_loading.png index 4ec6333..6e693b5 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_base_token_input_loading.png and b/test/app/create/deposit/goldens/deposit_page_base_token_input_loading.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_calculate_price.png b/test/app/create/deposit/goldens/deposit_page_calculate_price.png index 6ad5752..6241290 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_calculate_price.png and b/test/app/create/deposit/goldens/deposit_page_calculate_price.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_calculate_price_reversed.png b/test/app/create/deposit/goldens/deposit_page_calculate_price_reversed.png index 289d19d..82dbc33 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_calculate_price_reversed.png and b/test/app/create/deposit/goldens/deposit_page_calculate_price_reversed.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_orange.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_orange.png index 3df86de..b75a826 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_orange.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_orange.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_red.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_red.png index 56eabef..3ccf344 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_red.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_red.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_slippage_title.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_slippage_title.png index 653c1fb..309e2d0 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_slippage_title.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_slippage_title.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_zup_purple_gray.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_zup_purple_gray.png index 49ca770..b4afc0f 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_zup_purple_gray.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_zup_purple_gray.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown.png index 575cddb..dc9e34d 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown_reopening.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown_reopening.png index 3b331ac..2828102 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown_reopening.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown_reopening.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_enough_balance_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_enough_balance_deposit_button.png index 28717d0..2f2d337 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_enough_balance_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_enough_balance_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount.png index 80f1dcb..39695b3 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_change_range.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_change_range.png index a2c75ad..f7c5856 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_change_range.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_change_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse.png index bf3ea4a..0e0361b 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse_back.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse_back.png index c639997..3f8898d 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse_back.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse_back.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reverse_tokens_and_change_range.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reverse_tokens_and_change_range.png index a2c1227..5328a13 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reverse_tokens_and_change_range.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reverse_tokens_and_change_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reversed.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reversed.png index c61387a..3d2511a 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reversed.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reversed.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_reverse_tokens_then_set_max_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_reverse_tokens_then_set_max_price_out_of_range.png index 345ae45..b5184a2 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_reverse_tokens_then_set_max_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_reverse_tokens_then_set_max_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_set_max_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_set_max_price_out_of_range.png index 38a96bf..37e357e 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_set_max_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_set_max_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount.png index eb652be..ff31ff4 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_change_range.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_change_range.png index 2d6032b..b9a9a7b 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_change_range.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_change_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse.png index e6264d8..93c1b24 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse_back.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse_back.png index 120641e..5af7b96 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse_back.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse_back.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reverse_tokens_and_change_range.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reverse_tokens_and_change_range.png index d88d40b..3383d7a 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reverse_tokens_and_change_range.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reverse_tokens_and_change_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reversed.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reversed.png index 82f0518..a5251f7 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reversed.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reversed.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_reverse_tokens_then_set_min_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_reverse_tokens_then_set_min_price_out_of_range.png index de57a46..7e93329 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_reverse_tokens_then_set_min_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_reverse_tokens_then_set_min_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_set_min_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_set_min_price_out_of_range.png index db55603..0197fa0 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_set_min_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_set_min_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_range_then_input_base_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_range_then_input_base_token_amount.png index 6d6f6a8..cc44599 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_range_then_input_base_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_range_then_input_base_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_range_then_input_quote_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_range_then_input_quote_token_amount.png index 486c054..2deeea8 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_range_then_input_quote_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_range_then_input_quote_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_base_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_base_token_amount.png index d88d40b..3383d7a 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_base_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_base_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_quote_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_quote_token_amount.png index a2c1227..5328a13 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_quote_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_quote_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_invalid_range_deposit_section.png b/test/app/create/deposit/goldens/deposit_page_invalid_range_deposit_section.png index bf53d2e..ece93f4 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_invalid_range_deposit_section.png and b/test/app/create/deposit/goldens/deposit_page_invalid_range_deposit_section.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_max_price_less_than_min_price.png b/test/app/create/deposit/goldens/deposit_page_max_price_less_than_min_price.png index 724ffd1..2f3612a 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_max_price_less_than_min_price.png and b/test/app/create/deposit/goldens/deposit_page_max_price_less_than_min_price.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_max_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_max_price_out_of_range.png index 9d9f4f7..33080a1 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_max_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_max_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_max_price_set_to_full_range.png b/test/app/create/deposit/goldens/deposit_page_max_price_set_to_full_range.png index be0223a..6241290 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_max_price_set_to_full_range.png and b/test/app/create/deposit/goldens/deposit_page_max_price_set_to_full_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_max_price_set_to_infinity.png b/test/app/create/deposit/goldens/deposit_page_max_price_set_to_infinity.png index d6fc6a2..93bfdee 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_max_price_set_to_infinity.png and b/test/app/create/deposit/goldens/deposit_page_max_price_set_to_infinity.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_max_range_out_of_range_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_max_range_out_of_range_deposit_button.png index 6d7a20b..00fcd6e 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_max_range_out_of_range_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_max_range_out_of_range_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_and_max_price_set_to_full_range.png b/test/app/create/deposit/goldens/deposit_page_min_and_max_price_set_to_full_range.png index be0223a..6241290 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_and_max_price_set_to_full_range.png and b/test/app/create/deposit/goldens/deposit_page_min_and_max_price_set_to_full_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range.png index 5879452..6fb3f2b 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed.png b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed.png index 5fd0a0d..ce2b92a 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed.png and b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed_in_range.png b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed_in_range.png index 5a93f42..eac4708 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed_in_range.png and b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed_in_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_price_set_to_full_range.png b/test/app/create/deposit/goldens/deposit_page_min_price_set_to_full_range.png index be0223a..6241290 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_price_set_to_full_range.png and b/test/app/create/deposit/goldens/deposit_page_min_price_set_to_full_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_range_out_of_range_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_min_range_out_of_range_deposit_button.png index 7c0ce6a..0d835e1 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_range_out_of_range_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_min_range_out_of_range_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_no_amount_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_no_amount_deposit_button.png index cd6b0cf..074c735 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_no_amount_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_no_amount_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_no_yields.png b/test/app/create/deposit/goldens/deposit_page_no_yields.png index 1b85c95..b1f0508 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_no_yields.png and b/test/app/create/deposit/goldens/deposit_page_no_yields.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_no_yields_filtered_by_min_liquidity.png b/test/app/create/deposit/goldens/deposit_page_no_yields_filtered_by_min_liquidity.png index 1460754..8ac83d1 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_no_yields_filtered_by_min_liquidity.png and b/test/app/create/deposit/goldens/deposit_page_no_yields_filtered_by_min_liquidity.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_connected.png b/test/app/create/deposit/goldens/deposit_page_not_connected.png index f1a28c5..6c622f5 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_connected.png and b/test/app/create/deposit/goldens/deposit_page_not_connected.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_connected_deposit_button_click.png b/test/app/create/deposit/goldens/deposit_page_not_connected_deposit_button_click.png index 2301361..a17e4a9 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_connected_deposit_button_click.png and b/test/app/create/deposit/goldens/deposit_page_not_connected_deposit_button_click.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button.png index 5b16a4b..a2ba963 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button_after_connecting.png b/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button_after_connecting.png index 80c12b8..af62a99 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button_after_connecting.png and b/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button_after_connecting.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button.png index 62b0533..d0e52e2 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button_after_connecting.png b/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button_after_connecting.png index 62b0533..1e30bd0 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button_after_connecting.png and b/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button_after_connecting.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_pool_tick_update_deposit_amount.png b/test/app/create/deposit/goldens/deposit_page_pool_tick_update_deposit_amount.png new file mode 100644 index 0000000..681c5f1 Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_pool_tick_update_deposit_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_quote_token_input_enabled_after_loading.png b/test/app/create/deposit/goldens/deposit_page_quote_token_input_enabled_after_loading.png index a2c187d..19e7205 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_quote_token_input_enabled_after_loading.png and b/test/app/create/deposit/goldens/deposit_page_quote_token_input_enabled_after_loading.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_quote_token_input_loading.png b/test/app/create/deposit/goldens/deposit_page_quote_token_input_loading.png index df0b72f..a9a8899 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_quote_token_input_loading.png and b/test/app/create/deposit/goldens/deposit_page_quote_token_input_loading.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_range_section_mobile.png b/test/app/create/deposit/goldens/deposit_page_range_section_mobile.png index 1483027..8a04412 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_range_section_mobile.png and b/test/app/create/deposit/goldens/deposit_page_range_section_mobile.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_reverse_tokens.png index cb1afb5..582b944 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_reverse_tokens_back.png b/test/app/create/deposit/goldens/deposit_page_reverse_tokens_back.png index 1ef9298..6b81ddd 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_reverse_tokens_back.png and b/test/app/create/deposit/goldens/deposit_page_reverse_tokens_back.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_select_yield_scroll.png b/test/app/create/deposit/goldens/deposit_page_select_yield_scroll.png index 23c587b..228603c 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_select_yield_scroll.png and b/test/app/create/deposit/goldens/deposit_page_select_yield_scroll.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_selected_yield_stream.png b/test/app/create/deposit/goldens/deposit_page_selected_yield_stream.png index 10d90e4..6b81ddd 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_selected_yield_stream.png and b/test/app/create/deposit/goldens/deposit_page_selected_yield_stream.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_20_percent_range.png b/test/app/create/deposit/goldens/deposit_page_set_20_percent_range.png index f2d614e..6fb62de 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_20_percent_range.png and b/test/app/create/deposit/goldens/deposit_page_set_20_percent_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_20_percent_range_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_set_20_percent_range_reverse_tokens.png index b3e6105..cf766ae 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_20_percent_range_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_set_20_percent_range_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_50_percent_range.png b/test/app/create/deposit/goldens/deposit_page_set_50_percent_range.png index fdad4a6..304e7a0 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_50_percent_range.png and b/test/app/create/deposit/goldens/deposit_page_set_50_percent_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_50_percent_range_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_set_50_percent_range_reverse_tokens.png index 7b6e3d0..ca9ff06 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_50_percent_range_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_set_50_percent_range_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_5_percent_range.png b/test/app/create/deposit/goldens/deposit_page_set_5_percent_range.png index ac07fcd..658d8b5 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_5_percent_range.png and b/test/app/create/deposit/goldens/deposit_page_set_5_percent_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_5_percent_range_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_set_5_percent_range_reverse_tokens.png index e1be2b9..600413b 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_5_percent_range_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_set_5_percent_range_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_max_price_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_max_price_reverse_tokens.png index 2796c6b..20cbbfb 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_max_price_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_max_price_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_min_price_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_min_price_reverse_tokens.png index e2959c3..6706285 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_min_price_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_min_price_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_success.png b/test/app/create/deposit/goldens/deposit_page_success.png index 8b6285c..1d7bfc1 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_success.png and b/test/app/create/deposit/goldens/deposit_page_success.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_success_filtered_by_min_liquidity.png b/test/app/create/deposit/goldens/deposit_page_success_filtered_by_min_liquidity.png index 7b3bed1..0e2e1ea 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_success_filtered_by_min_liquidity.png and b/test/app/create/deposit/goldens/deposit_page_success_filtered_by_min_liquidity.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_success_mobile.png b/test/app/create/deposit/goldens/deposit_page_success_mobile.png index d4c4086..a6db298 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_success_mobile.png and b/test/app/create/deposit/goldens/deposit_page_success_mobile.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_timeframe_tooltip.png b/test/app/create/deposit/goldens/deposit_page_timeframe_tooltip.png index 567291f..e3dd6a7 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_timeframe_tooltip.png and b/test/app/create/deposit/goldens/deposit_page_timeframe_tooltip.png differ diff --git a/test/app/create/deposit/widgets/goldens/range_selector_is_infinity_increase_price.png b/test/app/create/deposit/widgets/goldens/range_selector_is_infinity_increase_price.png index ad2038b..5aa5a7c 100644 Binary files a/test/app/create/deposit/widgets/goldens/range_selector_is_infinity_increase_price.png and b/test/app/create/deposit/widgets/goldens/range_selector_is_infinity_increase_price.png differ diff --git a/test/app/create/deposit/widgets/goldens/range_selector_is_infinity_increase_price_reversed.png b/test/app/create/deposit/widgets/goldens/range_selector_is_infinity_increase_price_reversed.png index 5c9e4af..5aa5a7c 100644 Binary files a/test/app/create/deposit/widgets/goldens/range_selector_is_infinity_increase_price_reversed.png and b/test/app/create/deposit/widgets/goldens/range_selector_is_infinity_increase_price_reversed.png differ diff --git a/test/app/create/deposit/widgets/preview_deposit_modal/goldens/preview_deposit_modal_approve_token0_state.png b/test/app/create/deposit/widgets/preview_deposit_modal/goldens/preview_deposit_modal_approve_token0_state.png index d9caebd..5c0b7c5 100644 Binary files a/test/app/create/deposit/widgets/preview_deposit_modal/goldens/preview_deposit_modal_approve_token0_state.png and b/test/app/create/deposit/widgets/preview_deposit_modal/goldens/preview_deposit_modal_approve_token0_state.png differ diff --git a/test/app/create/deposit/widgets/preview_deposit_modal/goldens/preview_deposit_modal_token_amounts_change.png b/test/app/create/deposit/widgets/preview_deposit_modal/goldens/preview_deposit_modal_token_amounts_change.png new file mode 100644 index 0000000..2a1cb5f Binary files /dev/null and b/test/app/create/deposit/widgets/preview_deposit_modal/goldens/preview_deposit_modal_token_amounts_change.png differ diff --git a/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit_test.dart b/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit_test.dart index 7334694..b5849dc 100644 --- a/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit_test.dart +++ b/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit_test.dart @@ -1004,8 +1004,8 @@ void main() { tickLower: V3PoolConversorsMixinWrapper().tickToClosestValidTick( tick: V3PoolConversorsMixinWrapper().priceToTick( price: minPrice, - poolToken0Decimals: currentYield.token0.decimals, - poolToken1Decimals: currentYield.token1.decimals, + poolToken0Decimals: currentYield.token0NetworkDecimals, + poolToken1Decimals: currentYield.token1NetworkDecimals, isReversed: false, ), tickSpacing: currentYield.tickSpacing, @@ -1052,8 +1052,8 @@ void main() { tickLower: V3PoolConversorsMixinWrapper().tickToClosestValidTick( tick: V3PoolConversorsMixinWrapper().priceToTick( price: maxPrice, - poolToken0Decimals: currentYield.token0.decimals, - poolToken1Decimals: currentYield.token1.decimals, + poolToken0Decimals: currentYield.token0NetworkDecimals, + poolToken1Decimals: currentYield.token1NetworkDecimals, isReversed: isReversed, ), tickSpacing: currentYield.tickSpacing, @@ -1177,8 +1177,8 @@ void main() { tickUpper: V3PoolConversorsMixinWrapper().tickToClosestValidTick( tick: V3PoolConversorsMixinWrapper().priceToTick( price: maxPrice, - poolToken0Decimals: currentYield.token0.decimals, - poolToken1Decimals: currentYield.token1.decimals, + poolToken0Decimals: currentYield.token0NetworkDecimals, + poolToken1Decimals: currentYield.token1NetworkDecimals, isReversed: false, ), tickSpacing: currentYield.tickSpacing, @@ -1224,8 +1224,8 @@ void main() { tickUpper: V3PoolConversorsMixinWrapper().tickToClosestValidTick( tick: V3PoolConversorsMixinWrapper().priceToTick( price: minPrice, - poolToken0Decimals: currentYield.token0.decimals, - poolToken1Decimals: currentYield.token1.decimals, + poolToken0Decimals: currentYield.token0NetworkDecimals, + poolToken1Decimals: currentYield.token1NetworkDecimals, isReversed: isReversed, ), tickSpacing: currentYield.tickSpacing, @@ -1702,8 +1702,8 @@ void main() { verify(() => zupAnalytics.logDeposit( depositedYield: currentYield, - amount0Formatted: token0amount.parseTokenAmount(decimals: currentYield.token0.decimals), - amount1Formatted: token1amount.parseTokenAmount(decimals: currentYield.token1.decimals), + amount0Formatted: token0amount.parseTokenAmount(decimals: currentYield.token0NetworkDecimals), + amount1Formatted: token1amount.parseTokenAmount(decimals: currentYield.token1NetworkDecimals), walletAddress: userAddress, )).called(1); }, @@ -1936,16 +1936,16 @@ void main() { final tickLower = V3PoolConversorsMixinWrapper().tickToClosestValidTick( tick: V3PoolConversorsMixinWrapper().priceToTick( price: minPrice, - poolToken0Decimals: currentYield0.token0.decimals, - poolToken1Decimals: currentYield0.token1.decimals, + poolToken0Decimals: currentYield0.token0NetworkDecimals, + poolToken1Decimals: currentYield0.token1NetworkDecimals, ), tickSpacing: currentYield0.tickSpacing); final tickUpper = V3PoolConversorsMixinWrapper().tickToClosestValidTick( tick: V3PoolConversorsMixinWrapper().priceToTick( price: maxPrice, - poolToken0Decimals: currentYield0.token0.decimals, - poolToken1Decimals: currentYield0.token1.decimals, + poolToken0Decimals: currentYield0.token0NetworkDecimals, + poolToken1Decimals: currentYield0.token1NetworkDecimals, ), tickSpacing: currentYield0.tickSpacing); @@ -1985,7 +1985,6 @@ void main() { tickLower: tickLower, tickUpper: tickUpper, recipient: recipient, - currentPoolTick: initialPoolTick, ), ).called(1); }); diff --git a/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_test.dart b/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_test.dart index 1581893..3315a32 100644 --- a/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_test.dart +++ b/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_test.dart @@ -122,8 +122,8 @@ void main() { bool isReversed = false, ({bool isInfinity, double price}) minPrice = (isInfinity: false, price: 1200), ({bool isInfinity, double price}) maxPrice = (isInfinity: false, price: 3000), - double token0DepositAmount = 1, - double token1DepositAmount = 3, + TextEditingController? token0DepositAmountController, + TextEditingController? token1DepositAmountController, Duration deadline = const Duration(minutes: 30), Slippage slippage = Slippage.halfPercent, }) => @@ -144,8 +144,8 @@ void main() { isReversed: isReversed, minPrice: minPrice, maxPrice: maxPrice, - token0DepositAmount: token0DepositAmount, - token1DepositAmount: token1DepositAmount, + token0DepositAmountController: token0DepositAmountController ?? TextEditingController(text: "1"), + token1DepositAmountController: token1DepositAmountController ?? TextEditingController(text: "3"), ), )); }); @@ -309,8 +309,8 @@ void main() { final currentPriceAsTick = V3PoolConversorsMixinWrapper().priceToTick( price: currentPrice, - poolToken0Decimals: currentYield.token0.decimals, - poolToken1Decimals: currentYield.token1.decimals, + poolToken0Decimals: currentYield.token0NetworkDecimals, + poolToken1Decimals: currentYield.token1NetworkDecimals, ); when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); @@ -335,8 +335,8 @@ void main() { final currentPriceAsTick = V3PoolConversorsMixinWrapper().priceToTick( price: currentPrice, - poolToken0Decimals: currentYield.token0.decimals, - poolToken1Decimals: currentYield.token1.decimals, + poolToken0Decimals: currentYield.token0NetworkDecimals, + poolToken1Decimals: currentYield.token1NetworkDecimals, ); when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); @@ -364,8 +364,8 @@ void main() { final currentPriceAsTick = V3PoolConversorsMixinWrapper().priceToTick( price: currentPrice, - poolToken0Decimals: currentYield.token0.decimals, - poolToken1Decimals: currentYield.token1.decimals, + poolToken0Decimals: currentYield.token0NetworkDecimals, + poolToken1Decimals: currentYield.token1NetworkDecimals, ); when(() => cubit.latestPoolTick).thenReturn(currentPriceAsTick); @@ -497,7 +497,9 @@ void main() { }); await tester.pumpDeviceBuilder( - await goldenBuilder(token0DepositAmount: depositAmount, token1DepositAmount: 0), + await goldenBuilder( + token0DepositAmountController: TextEditingController(text: depositAmount.toString()), + token1DepositAmountController: TextEditingController(text: "0")), wrapper: GoldenConfig.localizationsWrapper(), ); await tester.pumpAndSettle(); @@ -530,7 +532,9 @@ void main() { }); await tester.pumpDeviceBuilder( - await goldenBuilder(token0DepositAmount: depositAmount, token1DepositAmount: 0), + await goldenBuilder( + token0DepositAmountController: TextEditingController(text: depositAmount.toString()), + token1DepositAmountController: TextEditingController(text: "0")), wrapper: GoldenConfig.localizationsWrapper(), ); await tester.pumpAndSettle(); @@ -541,7 +545,7 @@ void main() { verify( () => cubit.approveToken( currentYield.token0, - depositAmount.parseTokenAmount(decimals: currentYield.token0.decimals), + depositAmount.parseTokenAmount(decimals: currentYield.token0NetworkDecimals), ), ).called(1); }, @@ -558,20 +562,22 @@ void main() { when(() => cubit.state).thenReturn( PreviewDepositModalState.initial( - token0Allowance: depositAmount.parseTokenAmount(decimals: currentYield.token0.decimals), + token0Allowance: depositAmount.parseTokenAmount(decimals: currentYield.token0NetworkDecimals), token1Allowance: token1Allowance, ), ); when(() => cubit.stream).thenAnswer((_) { return Stream.value(PreviewDepositModalState.initial( - token0Allowance: depositAmount.parseTokenAmount(decimals: currentYield.token0.decimals), + token0Allowance: depositAmount.parseTokenAmount(decimals: currentYield.token0NetworkDecimals), token1Allowance: token1Allowance, )); }); await tester.pumpDeviceBuilder( - await goldenBuilder(token0DepositAmount: depositAmount, token1DepositAmount: depositAmount), + await goldenBuilder( + token0DepositAmountController: TextEditingController(text: depositAmount.toString()), + token1DepositAmountController: TextEditingController(text: depositAmount.toString())), wrapper: GoldenConfig.localizationsWrapper(), ); await tester.pumpAndSettle(); @@ -593,20 +599,22 @@ void main() { when(() => cubit.state).thenReturn( PreviewDepositModalState.initial( - token0Allowance: depositAmount.parseTokenAmount(decimals: currentYield.token0.decimals), + token0Allowance: depositAmount.parseTokenAmount(decimals: currentYield.token0NetworkDecimals), token1Allowance: token1Allowance, ), ); when(() => cubit.stream).thenAnswer((_) { return Stream.value(PreviewDepositModalState.initial( - token0Allowance: depositAmount.parseTokenAmount(decimals: currentYield.token0.decimals), + token0Allowance: depositAmount.parseTokenAmount(decimals: currentYield.token0NetworkDecimals), token1Allowance: token1Allowance, )); }); await tester.pumpDeviceBuilder( - await goldenBuilder(token0DepositAmount: depositAmount, token1DepositAmount: depositAmount), + await goldenBuilder( + token0DepositAmountController: TextEditingController(text: depositAmount.toString()), + token1DepositAmountController: TextEditingController(text: depositAmount.toString())), wrapper: GoldenConfig.localizationsWrapper(), ); await tester.pumpAndSettle(); @@ -617,7 +625,7 @@ void main() { verify( () => cubit.approveToken( currentYield.token1, - depositAmount.parseTokenAmount(decimals: currentYield.token1.decimals), + depositAmount.parseTokenAmount(decimals: currentYield.token1NetworkDecimals), ), ).called(1); }, @@ -629,8 +637,8 @@ void main() { in the deposit state""", goldenFileName: "preview_deposit_modal_deposit_state", (tester) async { - final token0Allowance = 400.parseTokenAmount(decimals: currentYield.token0.decimals); - final token1Allowance = 1200.parseTokenAmount(decimals: currentYield.token1.decimals); + final token0Allowance = 400.parseTokenAmount(decimals: currentYield.token0NetworkDecimals); + final token1Allowance = 1200.parseTokenAmount(decimals: currentYield.token1NetworkDecimals); const deposit0Amount = 100.2; const deposit1Amount = 110.2; @@ -650,7 +658,9 @@ void main() { }); await tester.pumpDeviceBuilder( - await goldenBuilder(token0DepositAmount: deposit0Amount, token1DepositAmount: deposit1Amount), + await goldenBuilder( + token0DepositAmountController: TextEditingController(text: deposit0Amount.toString()), + token1DepositAmountController: TextEditingController(text: deposit1Amount.toString())), wrapper: GoldenConfig.localizationsWrapper(), ); await tester.pumpAndSettle(); @@ -663,8 +673,8 @@ void main() { in the deposit state. Once the deposit button is clicked, it should call the deposit function in the cubit passing the correct params (got from the constructor)""", (tester) async { - final token0Allowance = 400.parseTokenAmount(decimals: currentYield.token0.decimals); - final token1Allowance = 1200.parseTokenAmount(decimals: currentYield.token1.decimals); + final token0Allowance = 400.parseTokenAmount(decimals: currentYield.token0NetworkDecimals); + final token1Allowance = 1200.parseTokenAmount(decimals: currentYield.token1NetworkDecimals); const deposit0Amount = 100.2; const deposit1Amount = 110.2; @@ -692,8 +702,8 @@ void main() { await tester.pumpDeviceBuilder( await goldenBuilder( - token0DepositAmount: deposit0Amount, - token1DepositAmount: deposit1Amount, + token0DepositAmountController: TextEditingController(text: deposit0Amount.toString()), + token1DepositAmountController: TextEditingController(text: deposit1Amount.toString()), deadline: deadline, isReversed: isReversed, minPrice: (isInfinity: isMinPriceInfinity, price: minPrice), @@ -716,8 +726,8 @@ void main() { maxPrice: maxPrice, minPrice: minPrice, slippage: slippage, - token0Amount: deposit0Amount.parseTokenAmount(decimals: currentYield.token0.decimals), - token1Amount: deposit1Amount.parseTokenAmount(decimals: currentYield.token1.decimals), + token0Amount: deposit0Amount.parseTokenAmount(decimals: currentYield.token0NetworkDecimals), + token1Amount: deposit1Amount.parseTokenAmount(decimals: currentYield.token1NetworkDecimals), ), ).called(1); }, @@ -730,8 +740,8 @@ void main() { when(() => cubit.latestPoolTick).thenReturn( V3PoolConversorsMixinWrapper().priceToTick( price: 0.01, // It should be shown in the card (or very close to it) - poolToken0Decimals: currentYield.token0.decimals, - poolToken1Decimals: currentYield.token1.decimals, + poolToken0Decimals: currentYield.token0NetworkDecimals, + poolToken1Decimals: currentYield.token1NetworkDecimals, isReversed: false, ), ); @@ -748,8 +758,8 @@ void main() { when(() => cubit.latestPoolTick).thenReturn( V3PoolConversorsMixinWrapper().priceToTick( price: 1200, // It should be shown in the card (or very close to it) - poolToken0Decimals: currentYield.token0.decimals, - poolToken1Decimals: currentYield.token1.decimals, + poolToken0Decimals: currentYield.token0NetworkDecimals, + poolToken1Decimals: currentYield.token1NetworkDecimals, isReversed: true, ), ); @@ -768,8 +778,8 @@ void main() { const newPrice = 0.02632; // It should be shown in the card (or very close to it) final newPriceAsTick = V3PoolConversorsMixinWrapper().priceToTick( price: newPrice, - poolToken0Decimals: currentYield.token0.decimals, - poolToken1Decimals: currentYield.token1.decimals, + poolToken0Decimals: currentYield.token0NetworkDecimals, + poolToken1Decimals: currentYield.token1NetworkDecimals, isReversed: false, ); @@ -929,8 +939,8 @@ void main() { isReversed: true, minPrice: (isInfinity: true, price: 0), maxPrice: (isInfinity: true, price: 0), - token0DepositAmount: 1200, - token1DepositAmount: 4300, + token0DepositAmountController: TextEditingController(text: "1200"), + token1DepositAmountController: TextEditingController(text: "4300"), deadline: const Duration(minutes: 30), maxSlippage: Slippage.halfPercent, ).show(context, currentPoolTick: BigInt.from(121475)); @@ -962,8 +972,8 @@ void main() { isReversed: true, minPrice: (isInfinity: true, price: 0), maxPrice: (isInfinity: true, price: 0), - token0DepositAmount: 1200, - token1DepositAmount: 4300, + token0DepositAmountController: TextEditingController(text: "1200"), + token1DepositAmountController: TextEditingController(text: "4300"), deadline: const Duration(minutes: 30), maxSlippage: Slippage.halfPercent, ).show(context, currentPoolTick: BigInt.from(121475)); @@ -1019,4 +1029,37 @@ void main() { await tester.pumpAndSettle(); }, ); + + zGoldenTest("When the token amounts controllers change its amount, it should reflect in the UI", + goldenFileName: "preview_deposit_modal_token_amounts_change", (tester) async { + final token0DepositAmountController = TextEditingController(text: "0"); + final token1DepositAmountController = TextEditingController(text: "0"); + + when(() => cubit.state).thenReturn( + PreviewDepositModalState.initial( + token0Allowance: EthereumConstants.uint256Max, + token1Allowance: EthereumConstants.uint256Max, + ), + ); + + when(() => cubit.stream).thenAnswer((_) { + return Stream.value(PreviewDepositModalState.initial( + token0Allowance: EthereumConstants.uint256Max, + token1Allowance: EthereumConstants.uint256Max, + )); + }); + + await tester.pumpDeviceBuilder( + await goldenBuilder( + token0DepositAmountController: token0DepositAmountController, + token1DepositAmountController: token1DepositAmountController, + ), + wrapper: GoldenConfig.localizationsWrapper(scaffoldMessengerKey: scaffoldMessengerKey)); + + await tester.pumpAndSettle(); + + token0DepositAmountController.text = "1200"; + token1DepositAmountController.text = "4300"; + await tester.pumpAndSettle(); + }); } diff --git a/test/app/create/deposit/widgets/range_selector_test.dart b/test/app/create/deposit/widgets/range_selector_test.dart index 00d56cb..2464733 100644 --- a/test/app/create/deposit/widgets/range_selector_test.dart +++ b/test/app/create/deposit/widgets/range_selector_test.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; import 'package:zup_app/app/create/deposit/widgets/range_selector.dart'; -import 'package:zup_app/core/dtos/token_dto.dart'; import '../../../../golden_config.dart'; @@ -11,8 +10,8 @@ void main() { Key? key, bool isReversed = false, Function(double price)? onPriceChanged, - TokenDto? poolToken0, - TokenDto? poolToken1, + int? poolToken0Decimals, + int? poolToken1Decimals, int tickSpacing = 10, RangeSelectorType type = RangeSelectorType.minPrice, double? initialPrice, @@ -32,8 +31,8 @@ void main() { displayQuoteTokenSymbol: "Token B", isReversed: isReversed, onPriceChanged: onPriceChanged ?? (_) {}, - poolToken0: poolToken0 ?? TokenDto.fixture().copyWith(symbol: "Token A"), - poolToken1: poolToken1 ?? TokenDto.fixture().copyWith(symbol: "Token B"), + poolToken0Decimals: poolToken0Decimals ?? 18, + poolToken1Decimals: poolToken0Decimals ?? 18, tickSpacing: tickSpacing, type: type, initialPrice: initialPrice, @@ -58,11 +57,7 @@ void main() { zGoldenTest("When the `isReversed` param is true, it should reverse the tokens in the widget", goldenFileName: "range_selector_reversed", (tester) async { return tester.pumpDeviceBuilder( - await goldenBuilder( - isReversed: true, - poolToken0: TokenDto.fixture().copyWith(symbol: "Token 0"), - poolToken1: TokenDto.fixture().copyWith(symbol: "Token 1"), - ), + await goldenBuilder(isReversed: true), ); }); @@ -304,13 +299,13 @@ void main() { "When the price is infinity, and click to increase, the price should increase to the minimum price based on the tick spacing", goldenFileName: "range_selector_is_infinity_increase_price", (tester) async { - const expectedIncreasedPrice = 1.0010004501200209e-12; + const expectedIncreasedPrice = 9.996040641477102e-19; double actualIncreasedPrice = 0; await tester.pumpDeviceBuilder(await goldenBuilder( isInfinity: true, - poolToken0: TokenDto.fixture().copyWith(decimals: 6), - poolToken1: TokenDto.fixture().copyWith(decimals: 18), + poolToken0Decimals: 6, + poolToken1Decimals: 18, onPriceChanged: (price) { actualIncreasedPrice = price; }, @@ -328,14 +323,14 @@ void main() { the price should increase to the minimum price based on the tick spacing""", goldenFileName: "range_selector_is_infinity_increase_price_reversed", (tester) async { - const expectedIncreasedPrice = 1.0008055719626048e-12; + const expectedIncreasedPrice = 9.996040641477102e-19; double actualIncreasedPrice = 0; await tester.pumpDeviceBuilder(await goldenBuilder( isInfinity: true, isReversed: true, - poolToken0: TokenDto.fixture().copyWith(decimals: 6), - poolToken1: TokenDto.fixture().copyWith(decimals: 18), + poolToken0Decimals: 6, + poolToken1Decimals: 18, onPriceChanged: (price) { actualIncreasedPrice = price; }, @@ -359,8 +354,8 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder( isInfinity: true, isReversed: true, - poolToken0: TokenDto.fixture().copyWith(decimals: 6), - poolToken1: TokenDto.fixture().copyWith(decimals: 18), + poolToken0Decimals: 6, + poolToken1Decimals: 18, onPriceChanged: (price) { actualIncreasedPrice = price; }, @@ -382,8 +377,8 @@ void main() { await tester.pumpDeviceBuilder(await goldenBuilder( isInfinity: true, - poolToken0: TokenDto.fixture().copyWith(decimals: 6), - poolToken1: TokenDto.fixture().copyWith(decimals: 18), + poolToken0Decimals: 6, + poolToken1Decimals: 18, onPriceChanged: (price) { actualIncreasedPrice = price; }, diff --git a/test/app/create/goldens/create_page_initial_stage.png b/test/app/create/goldens/create_page_initial_stage.png index 2c36037..3a276bb 100644 Binary files a/test/app/create/goldens/create_page_initial_stage.png and b/test/app/create/goldens/create_page_initial_stage.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_change_a_token_to_same_token_as_b.png b/test/app/create/goldens/create_page_select_tokens_stage_change_a_token_to_same_token_as_b.png index 013ee4c..10918e7 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_change_a_token_to_same_token_as_b.png and b/test/app/create/goldens/create_page_select_tokens_stage_change_a_token_to_same_token_as_b.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_change_b_token_to_same_token_as_a.png b/test/app/create/goldens/create_page_select_tokens_stage_change_b_token_to_same_token_as_a.png index 42a26ae..6af423e 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_change_b_token_to_same_token_as_a.png and b/test/app/create/goldens/create_page_select_tokens_stage_change_b_token_to_same_token_as_a.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_default_a_token.png b/test/app/create/goldens/create_page_select_tokens_stage_default_a_token.png index 2c36037..3a276bb 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_default_a_token.png and b/test/app/create/goldens/create_page_select_tokens_stage_default_a_token.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_mobile.png b/test/app/create/goldens/create_page_select_tokens_stage_mobile.png index 1c452b6..502018b 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_mobile.png and b/test/app/create/goldens/create_page_select_tokens_stage_mobile.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_add_badge.png b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_add_badge.png index 9f74f5e..bd3a0a6 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_add_badge.png and b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_add_badge.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_default.png b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_default.png index 50a4715..282e363 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_default.png and b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_default.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_not_default.png b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_not_default.png index 9f74f5e..bd3a0a6 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_not_default.png and b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_not_default.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_open.png b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_open.png index ebcd14b..9bd5edf 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_open.png and b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_open.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_remove_badge.png b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_remove_badge.png index 50a4715..282e363 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_remove_badge.png and b/test/app/create/goldens/create_page_select_tokens_stage_pool_search_settings_remove_badge.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_reset_tokens_from_network.png b/test/app/create/goldens/create_page_select_tokens_stage_reset_tokens_from_network.png index 9f74f5e..bd3a0a6 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_reset_tokens_from_network.png and b/test/app/create/goldens/create_page_select_tokens_stage_reset_tokens_from_network.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_token_a_selected_disabled_button.png b/test/app/create/goldens/create_page_select_tokens_stage_token_a_selected_disabled_button.png index 013ee4c..10918e7 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_token_a_selected_disabled_button.png and b/test/app/create/goldens/create_page_select_tokens_stage_token_a_selected_disabled_button.png differ diff --git a/test/app/create/goldens/create_page_select_tokens_stage_token_enabled_button.png b/test/app/create/goldens/create_page_select_tokens_stage_token_enabled_button.png index 1ca3ee3..d03fc5e 100644 Binary files a/test/app/create/goldens/create_page_select_tokens_stage_token_enabled_button.png and b/test/app/create/goldens/create_page_select_tokens_stage_token_enabled_button.png differ diff --git a/test/app/create/widgets/create_page_settings_dropdown_test.dart b/test/app/create/widgets/create_page_settings_dropdown_test.dart index ebfe786..4518a9d 100644 --- a/test/app/create/widgets/create_page_settings_dropdown_test.dart +++ b/test/app/create/widgets/create_page_settings_dropdown_test.dart @@ -2,7 +2,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:zup_app/app/create/widgets/create_page_settings_dropdown.dart'; +import 'package:zup_app/app/create/widgets/create_page_settings_dropdown/create_page_settings_dropdown.dart'; import 'package:zup_app/core/cache.dart'; import 'package:zup_app/core/debouncer.dart'; import 'package:zup_app/core/dtos/pool_search_settings_dto.dart'; diff --git a/test/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button_cubit_test.dart b/test/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button_cubit_test.dart new file mode 100644 index 0000000..0e248c0 --- /dev/null +++ b/test/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button_cubit_test.dart @@ -0,0 +1,108 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:zup_app/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button_cubit.dart'; +import 'package:zup_app/core/dtos/protocol_dto.dart'; +import 'package:zup_app/core/repositories/protocol_repository.dart'; +import 'package:zup_core/zup_singleton_cache.dart'; + +import '../../../../mocks.dart'; + +void main() { + late ExchangesFilterDropdownButtonCubit sut; + late ProtocolRepository protocolRepository; + late ZupSingletonCache zupSingletonCache; + + setUp(() { + protocolRepository = ProtocolRepositoryMock(); + zupSingletonCache = ZupSingletonCache.shared; + + sut = ExchangesFilterDropdownButtonCubit(protocolRepository, zupSingletonCache); + + when(() => protocolRepository.getAllSupportedProtocols()).thenAnswer((_) => Future.value([])); + }); + + tearDown(() => zupSingletonCache.clear()); + + test("When calling 'getSupportedProtocols' it should emit the loading state", () async { + expectLater(sut.stream, emits(const ExchangesFilterDropdownButtonState.loading())); + + await sut.getSupportedProtocols(); + }); + + test( + "When calling `getSupportedProtocols` and it succeds, it should sort the list by name", + () async { + final protocolListAnswer = [ + const ProtocolDto(name: "C"), + const ProtocolDto(name: "A"), + const ProtocolDto(name: "B"), + const ProtocolDto(name: "A"), + ]; + + when(() => protocolRepository.getAllSupportedProtocols()).thenAnswer((_) => Future.value(protocolListAnswer)); + + await sut.getSupportedProtocols(); + + expect( + sut.protocols, + protocolListAnswer..sort((a, b) => a.name.compareTo(b.name)), + ); + }, + ); + + test( + """When calling `getSupportedProtocols` and it succeds, it should emit success state with + the sorted list by name""", + () async { + final protocolListAnswer = [ + const ProtocolDto(name: "C"), + const ProtocolDto(name: "A"), + const ProtocolDto(name: "B"), + const ProtocolDto(name: "A"), + ]; + + when(() => protocolRepository.getAllSupportedProtocols()).thenAnswer((_) => Future.value(protocolListAnswer)); + + await sut.getSupportedProtocols(); + + expect( + sut.state, + ExchangesFilterDropdownButtonState.success(protocolListAnswer..sort((a, b) => a.name.compareTo(b.name))), + ); + }, + ); + + test("When an error happens while calling `getSupportedProtocols`, it should emit error state`", () async { + when(() => protocolRepository.getAllSupportedProtocols()).thenThrow(Exception()); + + await sut.getSupportedProtocols(); + + expect(sut.state, const ExchangesFilterDropdownButtonState.error()); + }); + + test( + """When calling `getSupportedProtocols` it should use the zupSingletonCache + to make the request ands cache it with no experation""", + () async { + zupSingletonCache = ZupSingletonCacheMock(); + sut = ExchangesFilterDropdownButtonCubit(protocolRepository, zupSingletonCache); + + when(() => zupSingletonCache.clear()).thenAnswer((_) => Future.value()); + when(() => zupSingletonCache.run>( + any(), + key: any(named: "key"), + expiration: any(named: "expiration"), + ignoreCache: any(named: "ignoreCache"), + )).thenAnswer((_) => Future.value([])); + + await sut.getSupportedProtocols(); + + verify(() => zupSingletonCache.run>( + any(), + key: "zup-supported-protocols", + expiration: null, + ignoreCache: false, + )).called(1); + }, + ); +} diff --git a/test/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button_test.dart b/test/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button_test.dart new file mode 100644 index 0000000..073d8ae --- /dev/null +++ b/test/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button_test.dart @@ -0,0 +1,227 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:golden_toolkit/golden_toolkit.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:zup_app/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button.dart'; +import 'package:zup_app/core/cache.dart'; +import 'package:zup_app/core/dtos/protocol_dto.dart'; +import 'package:zup_app/core/injections.dart'; +import 'package:zup_app/core/repositories/protocol_repository.dart'; +import 'package:zup_app/widgets/zup_cached_image.dart'; +import 'package:zup_core/zup_singleton_cache.dart'; + +import '../../../../golden_config.dart'; +import '../../../../mocks.dart'; + +void main() { + late ZupSingletonCache zupSingletonCache; + late ProtocolRepository protocolRepository; + late ZupCachedImage zupCachedImage; + late Cache cache; + + setUp(() { + zupSingletonCache = ZupSingletonCache.shared; + protocolRepository = ProtocolRepositoryMock(); + zupCachedImage = mockZupCachedImage(); + cache = CacheMock(); + + when(() => protocolRepository.getAllSupportedProtocols()).thenAnswer((_) => Future.value([])); + when(() => cache.blockedProtocolsIds).thenReturn([]); + + inject.registerFactory(() => zupSingletonCache); + inject.registerFactory(() => protocolRepository); + inject.registerFactory(() => zupCachedImage); + inject.registerFactory(() => cache); + }); + + tearDown(() { + inject.reset(); + zupSingletonCache.clear(); + }); + + Future goldenBuilder() async => await goldenDeviceBuilder( + const Center(child: ExchangesFilterDropdownButton()), + ); + + zGoldenTest("""When the widget is created, it should call the cubit to get the exchanges + and show a preview of the exchanges count""", goldenFileName: "exchanges_filter_dropdown_button", (tester) async { + when(() => protocolRepository.getAllSupportedProtocols()).thenAnswer((_) async => [ + const ProtocolDto(name: "C"), + const ProtocolDto(name: "A"), + const ProtocolDto(name: "B"), + const ProtocolDto(name: "A"), + ]); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + verify(() => protocolRepository.getAllSupportedProtocols()).called(1); + }); + + zGoldenTest( + """When the widget is created, it should call the cubit to get the exchanges. + If some of the exchanges are blocked by the user, the button should show a counter with + the number of total exchanages slash blocked exchanges""", + goldenFileName: "exchanges_filter_dropdown_button_blocked_exchanges_counter", + (tester) async { + final blockedProtocolsIds = ["32983", "sag", "nnnn", "dale"]; + when(() => cache.blockedProtocolsIds).thenReturn(blockedProtocolsIds); + + when(() => protocolRepository.getAllSupportedProtocols()).thenAnswer((_) async => [ + ProtocolDto(name: "C", rawId: blockedProtocolsIds[0]), + ProtocolDto(name: "A", rawId: blockedProtocolsIds[1]), + ProtocolDto(name: "B", rawId: blockedProtocolsIds[2]), + const ProtocolDto(name: "A", rawId: "some other id not blocked"), + ]); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When the cubit state is error, it should show only "Exchanges" in the button, + without the counter""", + goldenFileName: "exchanges_filter_dropdown_button_error_counter", + (tester) async { + when(() => protocolRepository.getAllSupportedProtocols()).thenThrow(Exception()); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + }, + ); + zGoldenTest( + """When the cubit state is error, and the user clicks the button, + it should show a snackbar saying to try to refresh the page""", + goldenFileName: "exchanges_filter_dropdown_button_error_click", + (tester) async { + when(() => protocolRepository.getAllSupportedProtocols()).thenThrow(Exception()); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("exchanges-filter-dropdown-button"))); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When the state is success, and the user clicks the button, it should + show a dropdown to select and unselect exchanges""", + goldenFileName: "exchanges_filter_dropdown_button_click", + (tester) async { + when(() => protocolRepository.getAllSupportedProtocols()).thenAnswer( + (_) async => [ + const ProtocolDto(name: "C"), + const ProtocolDto(name: "A"), + const ProtocolDto(name: "B"), + const ProtocolDto(name: "A"), + ], + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("exchanges-filter-dropdown-button"))); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When the state is success, and the user clicks the button, it should + show a dropdown to select and unselect exchanges. The exchanges that + are blocked already in the cache, should be unchecked""", + goldenFileName: "exchanges_filter_dropdown_button_click_with_blocked_exchanges", + (tester) async { + final blockedProtocolsIds = ["32983", "sag", "nnnn", "dale"]; + when(() => cache.blockedProtocolsIds).thenReturn(blockedProtocolsIds); + + when(() => protocolRepository.getAllSupportedProtocols()).thenAnswer( + (_) async => [ + ProtocolDto(name: "A", rawId: blockedProtocolsIds[0]), + const ProtocolDto(name: "B", rawId: "some other id not blocked"), + ProtocolDto(name: "C", rawId: blockedProtocolsIds[2]), + const ProtocolDto(name: "D", rawId: "some other id not blocked"), + ], + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("exchanges-filter-dropdown-button"))); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When the state is success, and the user clicks the button, it should + show a dropdown to select and unselect exchanges. When the user clicks + on a checked exchange, it should save to the cache as blocked exchange""", + (tester) async { + final blockedProtocolsIds = ["32983", "sag", "nnnn", "dale"]; + final allProtocols = [ + ProtocolDto(name: "A", rawId: blockedProtocolsIds[0]), + const ProtocolDto(name: "B", rawId: "some other id not blocked"), + ProtocolDto(name: "C", rawId: blockedProtocolsIds[2]), + const ProtocolDto(name: "D", rawId: "some other id not blocked"), + ]; + + when(() => cache.saveBlockedProtocolIds(blockedProtocolIds: any(named: "blockedProtocolIds"))).thenAnswer( + (_) async => {}, + ); + when(() => cache.blockedProtocolsIds).thenReturn(blockedProtocolsIds); + when(() => protocolRepository.getAllSupportedProtocols()).thenAnswer((_) async => allProtocols); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("exchanges-filter-dropdown-button"))); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("checkbox-item-1"))); + await tester.pumpAndSettle(); + + verify( + () => cache.saveBlockedProtocolIds(blockedProtocolIds: [ + blockedProtocolsIds[0], + "some other id not blocked", + blockedProtocolsIds[2], + ]), + ).called(1); + }, + ); + + zGoldenTest( + """When the state is success, and the user clicks the button, it should + show a dropdown to select and unselect exchanges. When the user clicks + on a unchecked exchange, it should remove it from the saved cached""", + (tester) async { + final blockedProtocolsIds = ["32983", "sag", "nnnn", "dale"]; + final allProtocols = [ + ProtocolDto(name: "A", rawId: blockedProtocolsIds[0]), + const ProtocolDto(name: "B", rawId: "some other id not blocked"), + ProtocolDto(name: "C", rawId: blockedProtocolsIds[2]), + const ProtocolDto(name: "D", rawId: "some other id not blocked"), + ]; + + when(() => cache.saveBlockedProtocolIds(blockedProtocolIds: any(named: "blockedProtocolIds"))).thenAnswer( + (_) async => {}, + ); + when(() => cache.blockedProtocolsIds).thenReturn(blockedProtocolsIds); + when(() => protocolRepository.getAllSupportedProtocols()).thenAnswer((_) async => allProtocols); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("exchanges-filter-dropdown-button"))); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("checkbox-item-0"))); + await tester.pumpAndSettle(); + + verify( + () => cache.saveBlockedProtocolIds(blockedProtocolIds: ["nnnn"]), + ).called(1); + }, + ); +} diff --git a/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button.png b/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button.png new file mode 100644 index 0000000..5ef1e99 Binary files /dev/null and b/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button.png differ diff --git a/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_blocked_exchanges_counter.png b/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_blocked_exchanges_counter.png new file mode 100644 index 0000000..5c14597 Binary files /dev/null and b/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_blocked_exchanges_counter.png differ diff --git a/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_click.png b/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_click.png new file mode 100644 index 0000000..6047457 Binary files /dev/null and b/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_click.png differ diff --git a/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_click_with_blocked_exchanges.png b/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_click_with_blocked_exchanges.png new file mode 100644 index 0000000..963cf9a Binary files /dev/null and b/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_click_with_blocked_exchanges.png differ diff --git a/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_error_click.png b/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_error_click.png new file mode 100644 index 0000000..cf703ac Binary files /dev/null and b/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_error_click.png differ diff --git a/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_error_counter.png b/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_error_counter.png new file mode 100644 index 0000000..79c6c06 Binary files /dev/null and b/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_error_counter.png differ diff --git a/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_loading.png b/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_loading.png new file mode 100644 index 0000000..763cf1d Binary files /dev/null and b/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_loading.png differ diff --git a/test/core/cache_test.dart b/test/core/cache_test.dart index abb79ef..db7635d 100644 --- a/test/core/cache_test.dart +++ b/test/core/cache_test.dart @@ -162,4 +162,22 @@ void main() { expect(result, true); verify(() => sharedPreferencesWithCache.getBool(CacheKey.areCookiesConsented.key)).called(1); }); + + test("when calling 'blockedProtocolsIds' it should get under the correct key", () { + when(() => sharedPreferencesWithCache.getStringList(any())).thenReturn(["1", "2"]); + + final result = sut.blockedProtocolsIds; + + expect(result, ["1", "2"]); + verify(() => sharedPreferencesWithCache.getStringList(CacheKey.blockedProtocolsIds.key)).called(1); + }); + + test("when calling 'saveBlockedProtocolsIds' it should save under the correct key", () async { + when(() => sharedPreferencesWithCache.setStringList(any(), any())).thenAnswer((_) async => true); + + final ids = ["1", "2"]; + await sut.saveBlockedProtocolIds(blockedProtocolIds: ids); + + verify(() => sharedPreferencesWithCache.setStringList(CacheKey.blockedProtocolsIds.key, ids)).called(1); + }); } diff --git a/test/core/enums/goldens/bnb_network_icon.png b/test/core/enums/goldens/bnb_network_icon.png new file mode 100644 index 0000000..198aeeb Binary files /dev/null and b/test/core/enums/goldens/bnb_network_icon.png differ diff --git a/test/core/enums/networks_test.dart b/test/core/enums/networks_test.dart index 41fa609..1e77904 100644 --- a/test/core/enums/networks_test.dart +++ b/test/core/enums/networks_test.dart @@ -3,7 +3,6 @@ import 'package:golden_toolkit/golden_toolkit.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'package:web3kit/core/dtos/chain_info.dart'; import 'package:web3kit/core/enums/native_currencies.dart'; -import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; import '../../golden_config.dart'; @@ -24,6 +23,7 @@ void main() { expect(AppNetworks.fromValue("allNetworks"), AppNetworks.allNetworks, reason: "All networks should match"); // expect(AppNetworks.fromValue("base"), AppNetworks.base, reason: "Base should match"); expect(AppNetworks.fromValue("unichain"), AppNetworks.unichain, reason: "Unichain should match"); + // expect(AppNetworks.fromValue("bnb"), AppNetworks.bnb, reason: "BNB should match"); }); test("Label extension should match for all networks", () { @@ -33,6 +33,7 @@ void main() { expect(AppNetworks.allNetworks.label, "All Networks", reason: "All Networks Label should match"); // expect(AppNetworks.base.label, "Base", reason: "Base Label should match"); expect(AppNetworks.unichain.label, "Unichain", reason: "Unichain Label should match"); + // expect(AppNetworks.bnb.label, "BNB Chain", reason: "BNB Chain Label should match"); }); test("`testnets` method should return all testnets in the enum, excluding the 'all networks'", () { @@ -48,6 +49,7 @@ void main() { AppNetworks.scroll, // AppNetworks.base, AppNetworks.unichain, + // AppNetworks.bnb ]), ); }); @@ -72,6 +74,10 @@ void main() { expect(AppNetworks.unichain.isTestnet, false); }); + test("`isTestnet` method should return false for bnb", () { + // expect(AppNetworks.bnb.isTestnet, false); + }); + test("Chain info extension should match for all networks", () { expect( AppNetworks.sepolia.chainInfo, @@ -125,12 +131,24 @@ void main() { ChainInfo( hexChainId: "0x82", chainName: "Unichain", - blockExplorerUrls: const ["https://uniscan.xyz/"], + blockExplorerUrls: const ["https://uniscan.xyz"], nativeCurrency: NativeCurrencies.eth.currencyInfo, rpcUrls: const ["https://unichain-rpc.publicnode.com"], ), reason: "Unichain ChainInfo should match", ); + + // expect( + // AppNetworks.bnb.chainInfo, + // ChainInfo( + // hexChainId: "0x38", + // chainName: "BNB Chain", + // blockExplorerUrls: const ["https://bscscan.com"], + // nativeCurrency: NativeCurrencies.bnb.currencyInfo, + // rpcUrls: const ["https://bsc-rpc.publicnode.com"], + // ), + // reason: "BNB Chain ChainInfo should match", + // ); }); test("wrapped native token address should match for all networks", () { @@ -165,80 +183,6 @@ void main() { ); }); - test("wrapped native token should match for all networks", () { - expect( - AppNetworks.sepolia.wrappedNative, - TokenDto( - addresses: { - AppNetworks.sepolia.chainId: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14", - }, - name: "Wrapped Ether", - decimals: 18, - symbol: "WETH", - logoUrl: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png", - ), - reason: "Sepolia default token should match", - ); - - expect( - AppNetworks.mainnet.wrappedNative, - TokenDto( - addresses: { - AppNetworks.mainnet.chainId: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - }, - name: "Wrapped Ether", - decimals: 18, - symbol: "WETH", - logoUrl: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png", - ), - reason: "Ethereum default token should match", - ); - - expect( - AppNetworks.scroll.wrappedNative, - TokenDto( - addresses: { - AppNetworks.scroll.chainId: "0x5300000000000000000000000000000000000004", - }, - name: "Wrapped Ether", - decimals: 18, - symbol: "WETH", - logoUrl: - "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/scroll/assets/0x5300000000000000000000000000000000000004/logo.png", - ), - reason: "Scroll default token should match", - ); - - // expect( - // AppNetworks.base.wrappedNative, - // TokenDto( - // addresses: { - // AppNetworks.base.chainId: "0x4200000000000000000000000000000000000006", - // }, - // name: "Wrapped Ether", - // decimals: 18, - // symbol: "WETH", - // logoUrl: - // "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/base/assets/0x4200000000000000000000000000000000000006/logo.png", - // ), - // reason: "Base default token should match", - // ); - - expect( - AppNetworks.unichain.wrappedNative, - TokenDto( - addresses: { - AppNetworks.unichain.chainId: "0x4200000000000000000000000000000000000006", - }, - name: "Wrapped Ether", - decimals: 18, - symbol: "WETH", - logoUrl: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/unichain/logo.png", - ), - reason: "Unichain default token should match", - ); - }); - test("RpcUrl extension should return the correct rpc url", () { expect( AppNetworks.sepolia.rpcUrl, @@ -269,6 +213,12 @@ void main() { "https://unichain-rpc.publicnode.com", reason: "Unichain rpc url should match", ); + + // expect( + // AppNetworks.bnb.rpcUrl, + // "https://bsc-rpc.publicnode.com", + // reason: "BNB rpc url should match", + // ); }); test("openTx should open the correct url for each network", () async { @@ -350,4 +300,11 @@ void main() { device: GoldenDevice.square, )); }); + + // zGoldenTest("BNB network icon should match", goldenFileName: "bnb_network_icon", (tester) async { + // await tester.pumpDeviceBuilder(await goldenDeviceBuilder( + // AppNetworks.bnb.icon, + // device: GoldenDevice.square, + // )); + // }); } diff --git a/test/core/enums/protocol_id_test.dart b/test/core/enums/protocol_id_test.dart new file mode 100644 index 0000000..b2d22a2 --- /dev/null +++ b/test/core/enums/protocol_id_test.dart @@ -0,0 +1,18 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:zup_app/core/enums/protocol_id.dart'; + +void main() { + test( + "When calling `isPancakeSwapInfinityCL` and the protocol is indeed pancakeSwapInfinityCL, it should return true", + () { + expect(ProtocolId.pancakeSwapInfinityCL.isPancakeSwapInfinityCL, true); + }, + ); + + test( + "When calling `isPancakeSwapInfinityCL` and the protocol is not pancakeSwapInfinityCL, it should return false", + () { + expect(ProtocolId.unknown.isPancakeSwapInfinityCL, false); + }, + ); +} diff --git a/test/core/mixins/keys_mixin_test.dart b/test/core/mixins/keys_mixin_test.dart index 4210281..57ba489 100644 --- a/test/core/mixins/keys_mixin_test.dart +++ b/test/core/mixins/keys_mixin_test.dart @@ -44,4 +44,10 @@ void main() { expect(key, 'tokenPrice-$tokenAddress-${network.name}'); }); + + test("protocolsListKey should return correct key", () { + final key = _KeysMixinWrapper().protocolsListKey; + + expect(key, 'zup-supported-protocols'); + }); } diff --git a/test/core/mixins/v4_pool_liquidity_calculations_mixin_test.dart b/test/core/mixins/v4_pool_liquidity_calculations_mixin_test.dart index e71d746..d47280a 100644 --- a/test/core/mixins/v4_pool_liquidity_calculations_mixin_test.dart +++ b/test/core/mixins/v4_pool_liquidity_calculations_mixin_test.dart @@ -13,7 +13,7 @@ void main() { expect( _V4PoolLiquidityCalculationsMixinTest().getLiquidityForAmount0(sqrtPriceAX96, sqrtPriceBX96, amount0), - BigInt.parse("282021882116526385819866125"), + BigInt.parse("282021882116526385820841971"), ); }, ); @@ -44,7 +44,7 @@ void main() { expect( _V4PoolLiquidityCalculationsMixinTest() .getLiquidityForAmounts(sqrtPriceX96, sqrtPriceAX96, sqrtPriceBX96, amount0, amount1), - BigInt.parse("282021882116526385819866125"), + BigInt.parse("282021882116526385820841971"), ); }, ); @@ -61,7 +61,7 @@ void main() { expect( _V4PoolLiquidityCalculationsMixinTest() .getLiquidityForAmounts(sqrtPriceX96, sqrtPriceAX96, sqrtPriceBX96, amount0, amount1), - BigInt.parse("94007294038842128606622041"), + BigInt.parse("94007294038842128606947412"), ); }, ); diff --git a/test/core/pool_service_test.dart b/test/core/pool_service_test.dart index c19d037..91bf353 100644 --- a/test/core/pool_service_test.dart +++ b/test/core/pool_service_test.dart @@ -4,14 +4,17 @@ import 'package:mocktail/mocktail.dart'; import 'package:web3kit/core/dtos/transaction_receipt.dart'; import 'package:web3kit/core/dtos/transaction_response.dart'; import 'package:web3kit/web3kit.dart'; +import 'package:zup_app/abis/pancake_swap_infinity_cl_pool_manager.abi.g.dart'; import 'package:zup_app/abis/uniswap_v3_pool.abi.g.dart'; import 'package:zup_app/abis/uniswap_v3_position_manager.abi.g.dart'; import 'package:zup_app/abis/uniswap_v4_position_manager.abi.g.dart'; import 'package:zup_app/abis/uniswap_v4_state_view.abi.g.dart'; +import 'package:zup_app/core/dtos/protocol_dto.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_app/core/enums/pool_type.dart'; +import 'package:zup_app/core/enums/protocol_id.dart'; import 'package:zup_app/core/mixins/v4_pool_liquidity_calculations_mixin.dart'; import 'package:zup_app/core/pool_service.dart'; import 'package:zup_app/core/v4_pool_constants.dart'; @@ -26,6 +29,7 @@ void main() { late UniswapV3Pool uniswapV3Pool; late UniswapV3PositionManager positionManagerV3; late UniswapV4PositionManager positionManagerV4; + late PancakeSwapInfinityClPoolManager pancakeSwapInfinityCLPoolManager; late Signer signer; late YieldDto currentYield; late TransactionResponse transactionResponse; @@ -34,6 +38,7 @@ void main() { late UniswapV3PoolImpl uniswapV3PoolImpl; late UniswapV3PositionManagerImpl positionManagerV3Impl; late UniswapV4PositionManagerImpl positionManagerV4Impl; + late PancakeSwapInfinityClPoolManagerImpl pancakeSwapInfinityCLPoolManagerImpl; late EthereumAbiCoder ethereumAbiCoder; setUp(() { @@ -58,9 +63,12 @@ void main() { uniswapV3Pool = UniswapV3PoolMock(); positionManagerV3 = UniswapV3PositionManagerMock(); positionManagerV4 = UniswapV4PositionManagerMock(); + pancakeSwapInfinityCLPoolManager = PancakeSwapInfinityCLPoolManagerMock(); ethereumAbiCoder = EthereumAbiCoderMock(); signer = SignerMock(); + pancakeSwapInfinityCLPoolManagerImpl = PancakeSwapInfinityCLPoolManagerImplMock(); + stateViewImpl = UniswapV4StateViewImplMock(); uniswapV3PoolImpl = UniswapV3PoolImplMock(); positionManagerV3Impl = UniswapV3PositionManagerImplMock(); @@ -68,10 +76,14 @@ void main() { currentYield = YieldDto.fixture(); - sut = PoolService(stateView, uniswapV3Pool, positionManagerV3, positionManagerV4, ethereumAbiCoder); - - when(() => stateView.fromRpcProvider(contractAddress: any(named: "contractAddress"), rpcUrl: any(named: "rpcUrl"))) - .thenReturn(stateViewImpl); + sut = PoolService( + stateView, + uniswapV3Pool, + positionManagerV3, + positionManagerV4, + ethereumAbiCoder, + pancakeSwapInfinityCLPoolManager, + ); when(() => uniswapV3Pool.fromRpcProvider( contractAddress: any(named: "contractAddress"), @@ -90,6 +102,9 @@ void main() { positionManagerV4.fromSigner(contractAddress: any(named: "contractAddress"), signer: any(named: "signer"))) .thenReturn(positionManagerV4Impl); + when(() => stateView.fromRpcProvider(contractAddress: any(named: "contractAddress"), rpcUrl: any(named: "rpcUrl"))) + .thenReturn(stateViewImpl); + when(() => positionManagerV4.fromRpcProvider( contractAddress: any(named: "contractAddress"), rpcUrl: any(named: "rpcUrl"))).thenReturn(positionManagerV4Impl); @@ -98,6 +113,12 @@ void main() { when(() => transactionResponse.waitConfirmation()).thenAnswer((_) async => TransactionReceipt(hash: "0x123")); when(() => transactionResponse.hash).thenReturn("0x123"); + when(() => stateViewImpl.getSlot0(poolId: any(named: "poolId"))).thenAnswer((_) async => ( + lpFee: BigInt.from(0), + protocolFee: BigInt.from(0), + sqrtPriceX96: BigInt.from(0), + tick: BigInt.from(0), + )); }); test( @@ -110,6 +131,12 @@ void main() { sqrtPriceX96: BigInt.from(0), tick: expectedTick, )); + when(() => pancakeSwapInfinityCLPoolManagerImpl.getSlot0(id: any(named: "id"))).thenAnswer((_) async => ( + lpFee: BigInt.from(0), + protocolFee: BigInt.from(0), + sqrtPriceX96: BigInt.from(0), + tick: expectedTick, + )); final currentYield0 = currentYield.copyWith(poolType: PoolType.v4, v4StateView: "0x123"); final result = await sut.getPoolTick(currentYield0); @@ -254,7 +281,7 @@ void main() { recipient: recipient, tickLower: tickLower, tickUpper: tickUpper, - token0: network.wrappedNative.addresses[network.chainId]!, + token0: network.wrappedNativeTokenAddress, token1: token1Address, )), ).called(1); @@ -517,9 +544,11 @@ void main() { final recipient = await signer.address; final tickLower = BigInt.from(321); final tickUpper = BigInt.from(1222); - final currentPoolTick = BigInt.from(123); + final currentYield0 = currentYield.copyWith( chainId: network.chainId, + poolType: PoolType.v4, + v4StateView: "0x1", token0: TokenDto.fixture().copyWith(addresses: {network.chainId: EthereumConstants.zeroAddress}), token1: TokenDto.fixture().copyWith(addresses: {network.chainId: "0x1"}), ); @@ -535,7 +564,6 @@ void main() { maxAmount0ToDeposit: amount0Max, maxAmount1ToDeposit: amount1Max, recipient: recipient, - currentPoolTick: currentPoolTick, ); verify(() => ethereumAbiCoder.encodePacked([ @@ -568,9 +596,11 @@ void main() { final recipient = await signer.address; final tickLower = BigInt.from(321); final tickUpper = BigInt.from(1222); - final currentPoolTick = BigInt.from(123); + final currentYield0 = currentYield.copyWith( chainId: network.chainId, + v4StateView: "0x1", + poolType: PoolType.v4, token1: TokenDto.fixture().copyWith(addresses: {network.chainId: EthereumConstants.zeroAddress}), token0: TokenDto.fixture().copyWith(addresses: {network.chainId: "0x1"}), ); @@ -586,7 +616,6 @@ void main() { maxAmount0ToDeposit: amount0Max, maxAmount1ToDeposit: amount1Max, recipient: recipient, - currentPoolTick: currentPoolTick, ); verify(() => ethereumAbiCoder.encodePacked([ @@ -619,9 +648,11 @@ void main() { final recipient = await signer.address; final tickLower = BigInt.from(321); final tickUpper = BigInt.from(1222); - final currentPoolTick = BigInt.from(123); + final currentYield0 = currentYield.copyWith( chainId: network.chainId, + v4StateView: "0x1", + poolType: PoolType.v4, token0: TokenDto.fixture().copyWith(addresses: {network.chainId: "0x2"}), token1: TokenDto.fixture().copyWith(addresses: {network.chainId: "0x1"}), ); @@ -637,7 +668,6 @@ void main() { maxAmount0ToDeposit: amount0Max, maxAmount1ToDeposit: amount1Max, recipient: recipient, - currentPoolTick: currentPoolTick, ); verify(() => ethereumAbiCoder.encodePacked([ @@ -652,13 +682,6 @@ void main() { test( "When calling `sendV4PoolDepositTransaction` the mint action params should be correctly encoded", () async { - when(() => ethereumAbiCoder.encodePacked(any(), any())).thenReturn("0x"); - when(() => ethereumAbiCoder.encode(any(), any())).thenReturn("0x"); - when( - () => positionManagerV4Impl.modifyLiquidities( - unlockData: any(named: "unlockData"), deadline: any(named: "deadline"), ethValue: any(named: "ethValue")), - ).thenAnswer((_) async => transactionResponse); - const token0Address = "0x1"; const token1Address = "0x2"; const network = AppNetworks.mainnet; @@ -670,9 +693,26 @@ void main() { final recipient = await signer.address; final tickLower = BigInt.from(321); final tickUpper = BigInt.from(1222); - final currentPoolTick = BigInt.from(123); + final sqrtPriceX96 = BigInt.from(2167212171927187); + + when(() => ethereumAbiCoder.encodePacked(any(), any())).thenReturn("0x"); + when(() => ethereumAbiCoder.encode(any(), any())).thenReturn("0x"); + when( + () => positionManagerV4Impl.modifyLiquidities( + unlockData: any(named: "unlockData"), deadline: any(named: "deadline"), ethValue: any(named: "ethValue")), + ).thenAnswer((_) async => transactionResponse); + + when(() => stateViewImpl.getSlot0(poolId: any(named: "poolId"))).thenAnswer((_) async => ( + lpFee: BigInt.from(0), + protocolFee: BigInt.from(0), + sqrtPriceX96: sqrtPriceX96, + tick: BigInt.from(0), + )); + final currentYield0 = currentYield.copyWith( chainId: network.chainId, + v4StateView: "0x1", + poolType: PoolType.v4, token1: TokenDto.fixture().copyWith(addresses: {network.chainId: token1Address}), token0: TokenDto.fixture().copyWith(addresses: {network.chainId: token0Address}), ); @@ -688,7 +728,6 @@ void main() { maxAmount0ToDeposit: amount0Max, maxAmount1ToDeposit: amount1Max, recipient: recipient, - currentPoolTick: currentPoolTick, ); verify(() => ethereumAbiCoder.encode([ @@ -711,7 +750,7 @@ void main() { tickLower, tickUpper, _V4PoolLiquidityCalculationsMixinWrapper().getLiquidityForAmounts( - _V4PoolLiquidityCalculationsMixinWrapper().getSqrtPriceAtTick(currentPoolTick), + sqrtPriceX96, _V4PoolLiquidityCalculationsMixinWrapper().getSqrtPriceAtTick(tickLower), _V4PoolLiquidityCalculationsMixinWrapper().getSqrtPriceAtTick(tickUpper), amount0Desired, @@ -746,9 +785,11 @@ void main() { final recipient = await signer.address; final tickLower = BigInt.from(321); final tickUpper = BigInt.from(1222); - final currentPoolTick = BigInt.from(123); + final currentYield0 = currentYield.copyWith( chainId: network.chainId, + v4StateView: "0x1", + poolType: PoolType.v4, token1: TokenDto.fixture().copyWith(addresses: {network.chainId: token1Address}), token0: TokenDto.fixture().copyWith(addresses: {network.chainId: token0Address}), ); @@ -764,7 +805,6 @@ void main() { maxAmount0ToDeposit: amount0Max, maxAmount1ToDeposit: amount1Max, recipient: recipient, - currentPoolTick: currentPoolTick, ); verify(() => ethereumAbiCoder.encode(["address", "address"], [token0Address, token1Address])).called(1); @@ -792,9 +832,11 @@ void main() { final recipient = await signer.address; final tickLower = BigInt.from(321); final tickUpper = BigInt.from(1222); - final currentPoolTick = BigInt.from(123); + final currentYield0 = currentYield.copyWith( chainId: network.chainId, + v4StateView: "0x1", + poolType: PoolType.v4, token1: TokenDto.fixture().copyWith(addresses: {network.chainId: token1Address}), token0: TokenDto.fixture().copyWith(addresses: {network.chainId: token0Address}), ); @@ -810,7 +852,6 @@ void main() { maxAmount0ToDeposit: amount0Max, maxAmount1ToDeposit: amount1Max, recipient: recipient, - currentPoolTick: currentPoolTick, ); verify( @@ -843,9 +884,11 @@ void main() { final recipient = await signer.address; final tickLower = BigInt.from(321); final tickUpper = BigInt.from(1222); - final currentPoolTick = BigInt.from(123); + final currentYield0 = currentYield.copyWith( chainId: network.chainId, + v4StateView: "0x1", + poolType: PoolType.v4, token0: TokenDto.fixture().copyWith(addresses: {network.chainId: token0Address}), token1: TokenDto.fixture().copyWith(addresses: {network.chainId: token1Address}), ); @@ -861,7 +904,6 @@ void main() { maxAmount0ToDeposit: amount0Max, maxAmount1ToDeposit: amount1Max, recipient: recipient, - currentPoolTick: currentPoolTick, ); verify( @@ -894,9 +936,11 @@ void main() { final recipient = await signer.address; final tickLower = BigInt.from(321); final tickUpper = BigInt.from(1222); - final currentPoolTick = BigInt.from(123); + final currentYield0 = currentYield.copyWith( chainId: network.chainId, + v4StateView: "0x1", + poolType: PoolType.v4, token0: TokenDto.fixture().copyWith(addresses: {network.chainId: token0Address}), token1: TokenDto.fixture().copyWith(addresses: {network.chainId: token1Address}), ); @@ -942,7 +986,6 @@ void main() { maxAmount0ToDeposit: amount0Max, maxAmount1ToDeposit: amount1Max, recipient: recipient, - currentPoolTick: currentPoolTick, ); verify( @@ -976,9 +1019,11 @@ void main() { final recipient = await signer.address; final tickLower = BigInt.from(321); final tickUpper = BigInt.from(1222); - final currentPoolTick = BigInt.from(123); + final currentYield0 = currentYield.copyWith( chainId: network.chainId, + v4StateView: "0x1", + poolType: PoolType.v4, token0: TokenDto.fixture().copyWith(addresses: {network.chainId: token0Address}), token1: TokenDto.fixture().copyWith(addresses: {network.chainId: token1Address}), ); @@ -1024,7 +1069,6 @@ void main() { maxAmount0ToDeposit: amount0Max, maxAmount1ToDeposit: amount1Max, recipient: recipient, - currentPoolTick: currentPoolTick, ); verify( @@ -1057,9 +1101,11 @@ void main() { final recipient = await signer.address; final tickLower = BigInt.from(321); final tickUpper = BigInt.from(1222); - final currentPoolTick = BigInt.from(123); + final currentYield0 = currentYield.copyWith( chainId: network.chainId, + v4StateView: "0x1", + poolType: PoolType.v4, token0: TokenDto.fixture().copyWith(addresses: {network.chainId: token0Address}), token1: TokenDto.fixture().copyWith(addresses: {network.chainId: token1Address}), ); @@ -1102,7 +1148,6 @@ void main() { maxAmount0ToDeposit: amount0Max, maxAmount1ToDeposit: amount1Max, recipient: recipient, - currentPoolTick: currentPoolTick, ); verify( @@ -1131,9 +1176,11 @@ void main() { final recipient = await signer.address; final tickLower = BigInt.from(321); final tickUpper = BigInt.from(1222); - final currentPoolTick = BigInt.from(123); + final currentYield0 = currentYield.copyWith( chainId: network.chainId, + v4StateView: "0x1", + poolType: PoolType.v4, token0: TokenDto.fixture().copyWith(addresses: {network.chainId: token0Address}), token1: TokenDto.fixture().copyWith(addresses: {network.chainId: token1Address}), ); @@ -1157,7 +1204,6 @@ void main() { maxAmount0ToDeposit: amount0Max, maxAmount1ToDeposit: amount1Max, recipient: recipient, - currentPoolTick: currentPoolTick, ); verify( @@ -1185,9 +1231,11 @@ void main() { final recipient = await signer.address; final tickLower = BigInt.from(321); final tickUpper = BigInt.from(1222); - final currentPoolTick = BigInt.from(123); + final currentYield0 = currentYield.copyWith( chainId: network.chainId, + v4StateView: "0x1", + poolType: PoolType.v4, token0: TokenDto.fixture().copyWith(addresses: {network.chainId: token0Address}), token1: TokenDto.fixture().copyWith(addresses: {network.chainId: token1Address}), ); @@ -1211,7 +1259,6 @@ void main() { maxAmount0ToDeposit: amount0Max, maxAmount1ToDeposit: amount1Max, recipient: recipient, - currentPoolTick: currentPoolTick, ); verify( @@ -1239,9 +1286,11 @@ void main() { final recipient = await signer.address; final tickLower = BigInt.from(321); final tickUpper = BigInt.from(1222); - final currentPoolTick = BigInt.from(123); + final currentYield0 = currentYield.copyWith( chainId: network.chainId, + v4StateView: "0x1", + poolType: PoolType.v4, token0: TokenDto.fixture().copyWith(addresses: {network.chainId: token0Address}), token1: TokenDto.fixture().copyWith(addresses: {network.chainId: token1Address}), ); @@ -1265,7 +1314,6 @@ void main() { maxAmount0ToDeposit: amount0Max, maxAmount1ToDeposit: amount1Max, recipient: recipient, - currentPoolTick: currentPoolTick, ); verify( @@ -1293,9 +1341,11 @@ void main() { final recipient = await signer.address; final tickLower = BigInt.from(321); final tickUpper = BigInt.from(1222); - final currentPoolTick = BigInt.from(123); + final currentYield0 = currentYield.copyWith( chainId: network.chainId, + v4StateView: "0x1", + poolType: PoolType.v4, token0: TokenDto.fixture().copyWith(addresses: {network.chainId: token0Address}), token1: TokenDto.fixture().copyWith(addresses: {network.chainId: token1Address}), ); @@ -1319,7 +1369,6 @@ void main() { maxAmount0ToDeposit: amount0Max, maxAmount1ToDeposit: amount1Max, recipient: recipient, - currentPoolTick: currentPoolTick, ); verify( @@ -1331,4 +1380,128 @@ void main() { ).called(1); }, ); + + test( + """"When calling `getPoolTick` and the yield protocol is pancakeswap infinity cl, + it should use the pancakeswap inifity cl pool manager to get the tick""", + () async { + final expectedTick = BigInt.from(318675); + + when(() => pancakeSwapInfinityCLPoolManager.fromRpcProvider( + contractAddress: any(named: "contractAddress"), rpcUrl: any(named: "rpcUrl"))).thenReturn( + pancakeSwapInfinityCLPoolManagerImpl, + ); + + when(() => pancakeSwapInfinityCLPoolManagerImpl.getSlot0(id: any(named: "id"))).thenAnswer((_) async => ( + sqrtPriceX96: BigInt.from(0), + tick: expectedTick, + protocolFee: BigInt.from(0), + lpFee: BigInt.from(0), + )); + + final yield0 = currentYield.copyWith( + protocol: ProtocolDto.fixture().copyWith(id: ProtocolId.pancakeSwapInfinityCL), + v4PoolManager: "0x0000001", + ); + + final receivedPoolTick = await sut.getPoolTick(yield0); + expect(receivedPoolTick, expectedTick); + + verify(() => pancakeSwapInfinityCLPoolManagerImpl.getSlot0(id: yield0.poolAddress)).called(1); + }, + ); + + test("""When calling `getSqrtPriceX96` and the yield protocol is pancakeswap infinity cl, + it should use the pancakeswap inifity cl pool manager to get the sqrtPriceX96 from the + slot0""", () async { + final expectedSqrtPriceX96 = BigInt.parse("3256723627823257362"); + + final yield0 = currentYield.copyWith( + protocol: ProtocolDto.fixture().copyWith(id: ProtocolId.pancakeSwapInfinityCL), + v4PoolManager: "0x0000001", + ); + + when(() => pancakeSwapInfinityCLPoolManager.fromRpcProvider( + contractAddress: any(named: "contractAddress"), rpcUrl: any(named: "rpcUrl"))).thenReturn( + pancakeSwapInfinityCLPoolManagerImpl, + ); + + when(() => pancakeSwapInfinityCLPoolManagerImpl.getSlot0(id: any(named: "id"))).thenAnswer((_) async => ( + sqrtPriceX96: expectedSqrtPriceX96, + tick: BigInt.from(0), + protocolFee: BigInt.from(0), + lpFee: BigInt.from(0), + )); + + final receivedSqrtPriceX96 = await sut.getSqrtPriceX96(yield0); + + expect(receivedSqrtPriceX96, expectedSqrtPriceX96); + }); + + test("""When calling `getSqrtPriceX96` and the yield pool is v4, + it should use the v4 state view get the sqrtPriceX96 from the + slot0""", () async { + final expectedSqrtPriceX96 = BigInt.parse("1216426515276100"); + + final yield0 = currentYield.copyWith( + poolType: PoolType.v4, + v4StateView: "0x0000001", + ); + + when(() => stateView.fromRpcProvider(contractAddress: any(named: "contractAddress"), rpcUrl: any(named: "rpcUrl"))) + .thenReturn( + stateViewImpl, + ); + + when(() => stateViewImpl.getSlot0(poolId: any(named: "poolId"))).thenAnswer((_) async => ( + sqrtPriceX96: expectedSqrtPriceX96, + tick: BigInt.from(0), + protocolFee: BigInt.from(0), + lpFee: BigInt.from(0), + )); + + final receivedSqrtPriceX96 = await sut.getSqrtPriceX96(yield0); + + expect(receivedSqrtPriceX96, expectedSqrtPriceX96); + }); + + test("""When calling `getSqrtPriceX96` and the yield pool is v3, + it should use the pool contract get the sqrtPriceX96 from the + slot0""", () async { + final expectedSqrtPriceX96 = BigInt.parse("907219862715267517621"); + + final yield0 = currentYield.copyWith( + poolType: PoolType.v3, + poolAddress: "0x0000001", + ); + + when(() => + uniswapV3Pool.fromRpcProvider(contractAddress: any(named: "contractAddress"), rpcUrl: any(named: "rpcUrl"))) + .thenReturn( + uniswapV3PoolImpl, + ); + + when(() => uniswapV3PoolImpl.slot0()).thenAnswer((_) async => ( + feeProtocol: BigInt.from(0), + observationCardinality: BigInt.from(0), + observationCardinalityNext: BigInt.from(0), + observationIndex: BigInt.from(0), + sqrtPriceX96: expectedSqrtPriceX96, + tick: BigInt.from(0), + unlocked: true, + )); + + final receivedSqrtPriceX96 = await sut.getSqrtPriceX96(yield0); + + expect(receivedSqrtPriceX96, expectedSqrtPriceX96); + }); + + test("""When calling `getSqrtPriceX96` and the yield pool type + is unknown, it should throw an error""", () async { + final yield0 = currentYield.copyWith( + poolType: PoolType.unknown, + ); + + expect(() async => await sut.getSqrtPriceX96(yield0), throwsA(isA())); + }); } diff --git a/test/core/repositories/protocol_repository_test.dart b/test/core/repositories/protocol_repository_test.dart new file mode 100644 index 0000000..a0cf821 --- /dev/null +++ b/test/core/repositories/protocol_repository_test.dart @@ -0,0 +1,50 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:zup_app/core/dtos/protocol_dto.dart'; +import 'package:zup_app/core/repositories/protocol_repository.dart'; + +import '../../mocks.dart'; + +void main() { + late Dio zupApiDio; + late ProtocolRepository sut; + + setUp(() { + zupApiDio = DioMock(); + sut = ProtocolRepository(zupApiDio: zupApiDio); + }); + + test("When calling `getAllSupportedProtocols` it should call the correct endpoint", () async { + when(() => zupApiDio.get(any())).thenAnswer( + (_) async => Response( + data: [ProtocolDto.fixture().toJson()], + statusCode: 200, + requestOptions: RequestOptions(), + ), + ); + + await sut.getAllSupportedProtocols(); + + verify(() => zupApiDio.get("/protocols")).called(1); + }); + + test("When calling `getAllSupportedProtocols` it should return a list of protocols", () async { + final protocols = [ + ProtocolDto.fixture().copyWith(rawId: "1", name: "LULU", logo: "LALA.png", url: "LALA.com"), + ProtocolDto.fixture().copyWith(rawId: "2", name: "LALA", logo: "LULU.png", url: "LULU.com"), + ]; + + when(() => zupApiDio.get(any())).thenAnswer( + (_) async => Response( + data: protocols.map((protocol) => protocol.toJson()..["id"] = protocol.rawId.toString()).toList(), + statusCode: 200, + requestOptions: RequestOptions(), + ), + ); + + final result = await sut.getAllSupportedProtocols(); + + expect(result, protocols); + }); +} diff --git a/test/core/repositories/yield_repository_test.dart b/test/core/repositories/yield_repository_test.dart index 2962c19..78355f8 100644 --- a/test/core/repositories/yield_repository_test.dart +++ b/test/core/repositories/yield_repository_test.dart @@ -33,8 +33,10 @@ void main() { const network = AppNetworks.sepolia; const minTvlUsd = 1213; final searchSettings = PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: minTvlUsd); + final blockedProtocolsIds = ["1", "2", "3"]; await sut.getSingleNetworkYield( + blockedProtocolIds: blockedProtocolsIds, token0Address: token0Address, token1Address: token1Address, network: network, @@ -48,6 +50,7 @@ void main() { }, data: { "filters": { "minTvlUsd": searchSettings.minLiquidityUSD, + "blockedProtocols": blockedProtocolsIds, "allowedPoolTypes": [ "V3", "V4", @@ -72,8 +75,10 @@ void main() { const token1Address = "0x456"; const network = AppNetworks.sepolia; final searchSettings = PoolSearchSettingsDto.fixture().copyWith(allowV3Search: false); + final blockedProtocolsIds = ["1", "2", "3"]; await sut.getSingleNetworkYield( + blockedProtocolIds: blockedProtocolsIds, token0Address: token0Address, token1Address: token1Address, network: network, @@ -87,6 +92,7 @@ void main() { }, data: { "filters": { "minTvlUsd": searchSettings.minLiquidityUSD, + "blockedProtocols": blockedProtocolsIds, "allowedPoolTypes": [ "V4", ], @@ -110,8 +116,10 @@ void main() { const token1Address = "0x456"; const network = AppNetworks.sepolia; final searchSettings = PoolSearchSettingsDto.fixture().copyWith(allowV4Search: false); + final blockedProtocolIds = ["1", "2", "3"]; await sut.getSingleNetworkYield( + blockedProtocolIds: blockedProtocolIds, token0Address: token0Address, token1Address: token1Address, network: network, @@ -124,6 +132,7 @@ void main() { "token1Address": token1Address }, data: { "filters": { + "blockedProtocols": blockedProtocolIds, "minTvlUsd": searchSettings.minLiquidityUSD, "allowedPoolTypes": ["V3"], } @@ -147,6 +156,7 @@ void main() { const network = AppNetworks.sepolia; final response = await sut.getSingleNetworkYield( + blockedProtocolIds: [], token0Address: token0Address, token1Address: token1Address, network: network, @@ -171,8 +181,10 @@ void main() { const token1Id = "0x456"; const minTvlUsd = 1213; final searchSettings = PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: minTvlUsd); + final blockedProtocolsIds = ["1", "2", "3"]; await sut.getAllNetworksYield( + blockedProtocolIds: blockedProtocolsIds, token0InternalId: token0Id, token1InternalId: token1Id, searchSettings: searchSettings, @@ -187,6 +199,7 @@ void main() { "filters": { "testnetMode": true, "minTvlUsd": searchSettings.minLiquidityUSD, + "blockedProtocols": blockedProtocolsIds, "allowedPoolTypes": [ "V3", "V4", @@ -213,8 +226,10 @@ void main() { const token1Id = "0x456"; const minTvlUsd = 1213; final searchSettings = PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: minTvlUsd, allowV4Search: false); + final blockedProtocolsIds = ["ai", "be", "cd"]; await sut.getAllNetworksYield( + blockedProtocolIds: blockedProtocolsIds, token0InternalId: token0Id, token1InternalId: token1Id, searchSettings: searchSettings, @@ -229,6 +244,7 @@ void main() { "filters": { "testnetMode": true, "minTvlUsd": searchSettings.minLiquidityUSD, + "blockedProtocols": blockedProtocolsIds, "allowedPoolTypes": [ "V3", ], @@ -254,8 +270,10 @@ void main() { const token1Id = "0x456"; const minTvlUsd = 1213; final searchSettings = PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: minTvlUsd, allowV3Search: false); + final blockedProtocolsIds = ["ai", "be", "cd"]; await sut.getAllNetworksYield( + blockedProtocolIds: blockedProtocolsIds, token0InternalId: token0Id, token1InternalId: token1Id, searchSettings: searchSettings, @@ -270,6 +288,7 @@ void main() { "filters": { "testnetMode": true, "minTvlUsd": searchSettings.minLiquidityUSD, + "blockedProtocols": blockedProtocolsIds, "allowedPoolTypes": [ "V4", ], @@ -293,6 +312,7 @@ void main() { const token1Id = "0x456"; final response = await sut.getAllNetworksYield( + blockedProtocolIds: [], token0InternalId: token0Id, token1InternalId: token1Id, searchSettings: PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: 0), diff --git a/test/core/string_extension_test.dart b/test/core/string_extension_test.dart index 0cc62e4..276a8e7 100644 --- a/test/core/string_extension_test.dart +++ b/test/core/string_extension_test.dart @@ -11,6 +11,10 @@ void main() { expect("0".isEmptyOrZero, true); }); + test("`isEmptyOrZero` should return true if the string is zero in double ", () { + expect("0.00000".isEmptyOrZero, true); + }); + test("`isNotEmptyOrZero` should return false if the string is literally empty", () { expect("".isNotEmptyOrZero, false); }); diff --git a/test/mocks.dart b/test/mocks.dart index f167e89..38d52f0 100644 --- a/test/mocks.dart +++ b/test/mocks.dart @@ -12,6 +12,7 @@ import 'package:url_launcher_platform_interface/url_launcher_platform_interface. import 'package:web3kit/core/dtos/transaction_response.dart'; import 'package:web3kit/web3kit.dart'; import 'package:zup_app/abis/erc_20.abi.g.dart'; +import 'package:zup_app/abis/pancake_swap_infinity_cl_pool_manager.abi.g.dart'; import 'package:zup_app/abis/uniswap_permit2.abi.g.dart'; import 'package:zup_app/abis/uniswap_v3_pool.abi.g.dart'; import 'package:zup_app/abis/uniswap_v3_position_manager.abi.g.dart'; @@ -24,6 +25,7 @@ import 'package:zup_app/core/cache.dart'; import 'package:zup_app/core/debouncer.dart'; import 'package:zup_app/core/pool_service.dart'; import 'package:zup_app/core/repositories/positions_repository.dart'; +import 'package:zup_app/core/repositories/protocol_repository.dart'; import 'package:zup_app/core/repositories/tokens_repository.dart'; import 'package:zup_app/core/repositories/yield_repository.dart'; import 'package:zup_app/core/zup_analytics.dart'; @@ -87,6 +89,10 @@ class UniswapV4PositionManagerMock extends Mock implements UniswapV4PositionMana class UniswapV4PositionManagerImplMock extends Mock implements UniswapV4PositionManagerImpl {} +class PancakeSwapInfinityCLPoolManagerMock extends Mock implements PancakeSwapInfinityClPoolManager {} + +class PancakeSwapInfinityCLPoolManagerImplMock extends Mock implements PancakeSwapInfinityClPoolManagerImpl {} + class UniswapV3PoolImplMock extends Mock implements UniswapV3PoolImpl {} class UniswapV3PoolMock extends Mock implements UniswapV3Pool {} @@ -113,6 +119,8 @@ class FirebaseAnalyticsMock extends Mock implements FirebaseAnalytics {} class ZupHolderMock extends Mock implements ZupHolder {} +class ProtocolRepositoryMock extends Mock implements ProtocolRepository {} + class ChangeNotifierMock extends Mock with ChangeNotifier { void notify() => notifyListeners(); } @@ -172,7 +180,7 @@ ZupCachedImage mockZupCachedImage() { width: any(named: "width"), radius: any(named: "radius"), errorWidget: any(named: "errorWidget"), - loadingBuilder: any(named: "loadingBuilder"), + placeholder: any(named: "placeholder"), )).thenReturn(const SizedBox(child: Text("IMAGE"))); return zupCachedImage; diff --git a/test/widgets/token_selector_modal/token_selector_modal_cubit_test.dart b/test/widgets/token_selector_modal/token_selector_modal_cubit_test.dart index 3f2868a..0ac65fb 100644 --- a/test/widgets/token_selector_modal/token_selector_modal_cubit_test.dart +++ b/test/widgets/token_selector_modal/token_selector_modal_cubit_test.dart @@ -279,8 +279,8 @@ void main() { test("""When calling 'searchToken' and all the tokens in the list returned does not have name and symbol, it should emit the search not found state""", () async { final returnedList = [ - const TokenDto(name: "", symbol: "", decimals: 0, logoUrl: "", addresses: {}), - const TokenDto(name: "", symbol: "", decimals: 0, logoUrl: "", addresses: {}), + TokenDto.fixture().copyWith(name: "", symbol: "", logoUrl: "", addresses: {}), + TokenDto.fixture().copyWith(name: "", symbol: "", decimals: {}, logoUrl: "", addresses: {}), ]; when(() => tokensRepository.searchToken(any(), any())).thenAnswer((_) async => returnedList); @@ -294,8 +294,8 @@ void main() { it should emit the search sucesss state, without the tokens without name and symbol""", () async { final namedToken = TokenDto.fixture(); final returnedList = [ - const TokenDto(name: "", symbol: "", decimals: 0, logoUrl: "", addresses: {}), - const TokenDto(name: "", symbol: "", decimals: 0, logoUrl: "", addresses: {}), + TokenDto.fixture().copyWith(name: "", symbol: "", logoUrl: "", addresses: {}), + TokenDto.fixture().copyWith(name: "", symbol: "", logoUrl: "", addresses: {}), namedToken, ]; diff --git a/web/index.html b/web/index.html index 852bc74..92d4cee 100644 --- a/web/index.html +++ b/web/index.html @@ -61,6 +61,6 @@ inject(); - +