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/create/deposit/deposit_page.dart b/lib/app/create/deposit/deposit_page.dart index 50dcf05..9227e25 100644 --- a/lib/app/create/deposit/deposit_page.dart +++ b/lib/app/create/deposit/deposit_page.dart @@ -123,8 +123,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; @@ -215,14 +215,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() { @@ -756,8 +756,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; @@ -826,8 +826,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, @@ -873,8 +873,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, 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 855b04c..a0eb653 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 @@ -120,17 +120,17 @@ class _PreviewDepositModalState extends State with V3PoolCo double get quoteTokenAmount => isReversedLocal ? widget.token0DepositAmount : widget.token1DepositAmount; PreviewDepositModalCubit get cubit => context.read(); BigInt get token0DepositAmount => - widget.token0DepositAmount.parseTokenAmount(decimals: widget.currentYield.token0.decimals); + widget.token0DepositAmount.parseTokenAmount(decimals: widget.currentYield.token0NetworkDecimals); BigInt get token1DepositAmount => - widget.token1DepositAmount.parseTokenAmount(decimals: widget.currentYield.token1.decimals); + widget.token1DepositAmount.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 +142,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 +163,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; 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..9de2af3 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 @@ -175,8 +175,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 +194,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, ); } @@ -253,8 +253,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) { 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/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button.dart b/lib/app/create/widgets/exchanges_filter_dropdown_button/exchanges_filter_dropdown_button.dart index 88ca06a..0092138 100644 --- 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 @@ -27,7 +27,7 @@ class _ExchangesFilterDropdownButtonState extends State cubit!.protocols .where( - (protocol) => !cache.blockedProtocolsIds.contains(protocol.id), + (protocol) => !cache.blockedProtocolsIds.contains(protocol.rawId), ) .length; @@ -122,10 +122,10 @@ class _ExchangesFilterDropdownButtonState extends State ZupCheckboxItem( - id: protocol.id, + id: protocol.rawId, title: protocol.name, icon: zupCachedImage.build(protocol.logo, radius: 50), - isChecked: !cache.blockedProtocolsIds.contains(protocol.id), + isChecked: !cache.blockedProtocolsIds.contains(protocol.rawId), isDisabled: false, ), ) diff --git a/lib/core/dtos/protocol_dto.dart b/lib/core/dtos/protocol_dto.dart index 1825eeb..3c23722 100644 --- a/lib/core/dtos/protocol_dto.dart +++ b/lib/core/dtos/protocol_dto.dart @@ -1,15 +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("") String id, + @Default("") @JsonKey(readValue: _readRawProtocolId) String rawId, + @Default(ProtocolId.unknown) @JsonKey(unknownEnumValue: ProtocolId.unknown) ProtocolId id, @Default("") String name, @Default("") String url, @Default("") String logo, @@ -18,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..df29c23 100644 --- a/lib/core/dtos/yield_dto.dart +++ b/lib/core/dtos/yield_dto.dart @@ -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 e39cae7..ac83d41 100644 --- a/lib/core/dtos/yields_dto.dart +++ b/lib/core/dtos/yields_dto.dart @@ -4,6 +4,7 @@ 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'; @@ -33,21 +34,21 @@ class YieldsDto with _$YieldsDto { factory YieldsDto.fixture() => YieldsDto( filters: PoolSearchFiltersDto.fixture(), pools: [ - const YieldDto( + 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", @@ -62,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..c891f44 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/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/injections.dart b/lib/core/injections.dart index 284f4e5..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'; @@ -121,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( diff --git a/lib/core/pool_service.dart b/lib/core/pool_service.dart index 8a85d1a..f02deae 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!, 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 83b566c..1e167ae 100644 --- a/test/app/create/create_page_select_token_stage_test.dart +++ b/test/app/create/create_page_select_token_stage_test.dart @@ -92,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], @@ -112,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/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/preview_deposit_modal_cubit_test.dart b/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit_test.dart index 7334694..d6da623 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); 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..f96918d 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 @@ -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); @@ -541,7 +541,7 @@ void main() { verify( () => cubit.approveToken( currentYield.token0, - depositAmount.parseTokenAmount(decimals: currentYield.token0.decimals), + depositAmount.parseTokenAmount(decimals: currentYield.token0NetworkDecimals), ), ).called(1); }, @@ -558,14 +558,14 @@ 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, )); }); @@ -593,14 +593,14 @@ 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, )); }); @@ -617,7 +617,7 @@ void main() { verify( () => cubit.approveToken( currentYield.token1, - depositAmount.parseTokenAmount(decimals: currentYield.token1.decimals), + depositAmount.parseTokenAmount(decimals: currentYield.token1NetworkDecimals), ), ).called(1); }, @@ -629,8 +629,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; @@ -663,8 +663,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; @@ -716,8 +716,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 +730,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 +748,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 +768,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, ); 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/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 index 7b4c942..073d8ae 100644 --- 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 @@ -68,10 +68,10 @@ void main() { when(() => cache.blockedProtocolsIds).thenReturn(blockedProtocolsIds); when(() => protocolRepository.getAllSupportedProtocols()).thenAnswer((_) async => [ - ProtocolDto(name: "C", id: blockedProtocolsIds[0]), - ProtocolDto(name: "A", id: blockedProtocolsIds[1]), - ProtocolDto(name: "B", id: blockedProtocolsIds[2]), - const ProtocolDto(name: "A", id: "some other id not blocked"), + 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()); @@ -90,7 +90,6 @@ void main() { 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""", @@ -139,10 +138,10 @@ void main() { when(() => protocolRepository.getAllSupportedProtocols()).thenAnswer( (_) async => [ - ProtocolDto(name: "A", id: blockedProtocolsIds[0]), - const ProtocolDto(name: "B", id: "some other id not blocked"), - ProtocolDto(name: "C", id: blockedProtocolsIds[2]), - const ProtocolDto(name: "D", id: "some other id not blocked"), + 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"), ], ); @@ -161,10 +160,10 @@ void main() { (tester) async { final blockedProtocolsIds = ["32983", "sag", "nnnn", "dale"]; final allProtocols = [ - ProtocolDto(name: "A", id: blockedProtocolsIds[0]), - const ProtocolDto(name: "B", id: "some other id not blocked"), - ProtocolDto(name: "C", id: blockedProtocolsIds[2]), - const ProtocolDto(name: "D", id: "some other id not blocked"), + 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( @@ -199,10 +198,10 @@ void main() { (tester) async { final blockedProtocolsIds = ["32983", "sag", "nnnn", "dale"]; final allProtocols = [ - ProtocolDto(name: "A", id: blockedProtocolsIds[0]), - const ProtocolDto(name: "B", id: "some other id not blocked"), - ProtocolDto(name: "C", id: blockedProtocolsIds[2]), - const ProtocolDto(name: "D", id: "some other id not blocked"), + 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( 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 index e03da5b..cf703ac 100644 Binary files a/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_error_click.png and b/test/app/create/widgets/exchanges_filter_dropdown_button/goldens/exchanges_filter_dropdown_button_error_click.png differ 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..d0ca826 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/pool_service_test.dart b/test/core/pool_service_test.dart index c19d037..e511d08 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,7 +76,14 @@ void main() { currentYield = YieldDto.fixture(); - sut = PoolService(stateView, uniswapV3Pool, positionManagerV3, positionManagerV4, ethereumAbiCoder); + sut = PoolService( + stateView, + uniswapV3Pool, + positionManagerV3, + positionManagerV4, + ethereumAbiCoder, + pancakeSwapInfinityCLPoolManager, + ); when(() => stateView.fromRpcProvider(contractAddress: any(named: "contractAddress"), rpcUrl: any(named: "rpcUrl"))) .thenReturn(stateViewImpl); @@ -110,6 +125,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 +275,7 @@ void main() { recipient: recipient, tickLower: tickLower, tickUpper: tickUpper, - token0: network.wrappedNative.addresses[network.chainId]!, + token0: network.wrappedNativeTokenAddress, token1: token1Address, )), ).called(1); @@ -1331,4 +1352,34 @@ 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); + }, + ); } diff --git a/test/core/repositories/protocol_repository_test.dart b/test/core/repositories/protocol_repository_test.dart index 79a8cf2..a0cf821 100644 --- a/test/core/repositories/protocol_repository_test.dart +++ b/test/core/repositories/protocol_repository_test.dart @@ -31,12 +31,13 @@ void main() { test("When calling `getAllSupportedProtocols` it should return a list of protocols", () async { final protocols = [ - ProtocolDto.fixture().copyWith(id: "1", name: "LULU", logo: "LALA.png", url: "LALA.com"), - ProtocolDto.fixture().copyWith(id: "2", name: "LALA", logo: "LULU.png", url: "LULU.com"), + 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()).toList(), + data: protocols.map((protocol) => protocol.toJson()..["id"] = protocol.rawId.toString()).toList(), statusCode: 200, requestOptions: RequestOptions(), ), diff --git a/test/mocks.dart b/test/mocks.dart index ecbade7..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'; @@ -88,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 {} 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, ];