From dec52175415d7d3dd51e8fdf50dce9977fff3135 Mon Sep 17 00:00:00 2001 From: Kirill Ageychenko Date: Fri, 16 Aug 2024 15:25:33 +0700 Subject: [PATCH] fix(HW-599): incorrect tokens for swap (#2034) Co-authored-by: iGroza --- assets/locales/ar/ar.json | 4 +- assets/locales/en/en.json | 4 +- assets/locales/ru/ru.json | 4 +- assets/locales/tr/tr.json | 4 +- src/components/swap/swap-input.tsx | 8 +- src/components/swap/swap.tsx | 6 +- src/helpers/address-utils.ts | 4 - src/i18n.ts | 2 + src/screens/SwapStack/swap-screen.tsx | 147 +++++++++++++++----------- src/services/indexer/indexer.types.ts | 4 +- 10 files changed, 107 insertions(+), 80 deletions(-) diff --git a/assets/locales/ar/ar.json b/assets/locales/ar/ar.json index a77774e9f..5f6d2f5d0 100644 --- a/assets/locales/ar/ar.json +++ b/assets/locales/ar/ar.json @@ -1,7 +1,7 @@ { "StakingInfoUnDelegationDay": " ({{day}} يوم متبقي)", "StakingInfoUnDelegationDays": " (بقي {{days}} يوم)", - "_hash": "c7dc5902b8286a71bd492f0ceaba03d160640abf0bd7feea09cb81cf6fd9583a", + "_hash": "68c7aeb851f24ae0469422cf14a75b6816e56bdbe7fe6c11e7a2080ef9f02542", "accept": "قبول", "accountInfoNftTabTitle": "NFTs", "accountInfoTokensTabTitle": "الرموز المميزة", @@ -351,6 +351,7 @@ "no": "لا", "noInternetPopupDescription": "تأكد من أنك متصل بشبكة Wi-Fi أو شبكة خلوية", "noInternetPopupTitle": "لا يوجد إنترنت", + "noSwapRoutesFound": "لم يتم العثور على مسارات المبادلة، يرجى تجربة شبكة بلوكتشين أخرى", "noTokens": "لا توجد رموز مميزة", "notEnoughGasDescription": "في الوقت الحالي, تتطلب المعاملة **{{gasLimit}}**, ولكن **ليس لديك سوى {{currentAmount}}** في حسابك.", "notEnoughGasDescription1": "حاليًا, معاملة", @@ -744,6 +745,7 @@ "sumAmountTooLow": "يجب أن يكون مساويًا أو أكبر من {{amount}}", "sumBlockAvailable": "متاح", "sumBlockMax": "الحد الأقصى", + "swapSupportedNetworks": "الشبكات (مع دعم المبادلة الأصلي)", "termsAgreementFirst": "بالنقر على موافقة, فإنك توافق على", "termsAgreementSecond": " و", "termsOfService": "شروط الخدمة", diff --git a/assets/locales/en/en.json b/assets/locales/en/en.json index 194c3c652..d4ef69c79 100644 --- a/assets/locales/en/en.json +++ b/assets/locales/en/en.json @@ -3,7 +3,7 @@ "StakingInfoUnDelegationDay": " ({{day}} day left)", "StakingInfoUnDelegationDays": " ({{days}} days left)", "Welcome_haqq_pin": "Welcome to HAQQ Wallet", - "_hash": "fd73aced8b5e070cb2eafe5958aeec977ed37e96628fd0ab056d99cec4e542c8", + "_hash": "4fd37808dc9c0b8dd789da69c848b4957dced3343710c86f8f4034e11cd9c06e", "accept": "Accept", "accountInfoNftTabTitle": "NFTs", "accountInfoTokensTabTitle": "Tokens", @@ -390,6 +390,7 @@ "no": "No", "noInternetPopupDescription": "Make sure you are connected to Wi-Fi or a cellular network", "noInternetPopupTitle": "No Internet", + "noSwapRoutesFound": "No swap routes found, please try a different blockchain network.", "noTokens": "No Tokens", "notEnoughGasDescription": "Currently, a transaction **requires {{gasLimit}}**, but **you only have {{currentAmount}}** on your account.", "notEnoughGasDescription1": "Currently, a transaction ", @@ -821,6 +822,7 @@ "swapSettingsSlippageLabel": "Slippage Tolerance (%)", "swapSettingsSlippagePlaceholder": "from 0.05% to 50%", "swapSettingsSlippageWarning": "Your transaction may be frontrun due to high slippage tolerance", + "swapSupportedNetworks": "Networks (with native swap support)", "swapTransactionSettingsTitle": "Transaction setting", "swapWidgetDescription": "Haqq wallet finds the best price for your token swap", "swapWidgetTitle": "Swap tokens", diff --git a/assets/locales/ru/ru.json b/assets/locales/ru/ru.json index 075ef8e2d..5e4f2057a 100644 --- a/assets/locales/ru/ru.json +++ b/assets/locales/ru/ru.json @@ -1,7 +1,7 @@ { "StakingInfoUnDelegationDay": " ({{day}} день остался)", "StakingInfoUnDelegationDays": " ({{days}} дней осталось)", - "_hash": "ecc3fde1ff572bdd3b4058c9b7d45556085b2190592b6dc0b9624e40108324c8", + "_hash": "e1781c9b1fc0e6483d4eb504c9d2746470c5e35be033966652dc20b37db38068", "accept": "Принять", "accountInfoTokensTabTitle": "Токены", "accountInfoTransactionTabTitle": "Транзакции", @@ -65,6 +65,7 @@ "modalRewardErrorTitle": "Что-то пошло не так", "moreAbout": "Подробнее", "nftDetailsTokenId": "Идентификатор токена", + "noSwapRoutesFound": "Не найдено swap маршрутов, попробуйте другую блокчейн сеть.", "numericKeyboard0": "0", "numericKeyboard1": "1", "numericKeyboard2": "2", @@ -96,6 +97,7 @@ "stakingUnDelegateFormCommission": "Комиссия", "stakingUnDelegatePreviewCommission": "Комиссия", "stakingValidators": "Список валидаторов", + "swapSupportedNetworks": "Сети (с поддержкой native swap)", "transactionInfoFunctionValue": "Сумма токена", "walletCardConnectedApps": "{{count}} подключиться", "walletCardFullProtection": "Защищено", diff --git a/assets/locales/tr/tr.json b/assets/locales/tr/tr.json index 89add7d8e..ee9d69c1f 100644 --- a/assets/locales/tr/tr.json +++ b/assets/locales/tr/tr.json @@ -1,7 +1,7 @@ { "StakingInfoUnDelegationDay": "({{day}} gün kaldı)", "StakingInfoUnDelegationDays": "({{days}} gün kaldı)", - "_hash": "834be9031dde29f83fcf4d8457b3a1e6b0e21ddb6f85314cc43299b7cd2dad9e", + "_hash": "4c02ced14897d67e089d85a38662dfd9f4601080706f3828592d2b81c81012c1", "accept": "Kabul Et", "accountInfoNftTabTitle": "NFT’ler", "accountInfoTokensTabTitle": "Tokenlar", @@ -371,6 +371,7 @@ "no": "Hayır", "noInternetPopupDescription": "Wi-Fi veya hücresel ağa bağlı olduğunuzdan emin olun", "noInternetPopupTitle": "İnternet yok", + "noSwapRoutesFound": "Swap yolları bulunamadı, lütfen başka bir blockchain ağı deneyin.", "noTokens": "Token Yok", "notEnoughGasDescription": "Şu anda, bir işlem **{{gasLimit}} gerektiriyor**, ancak **hesabınızda yalnızca {{currentAmount}} mevcut**.", "notEnoughGasDescription1": "Şu anda, bir işlem ", @@ -774,6 +775,7 @@ "sumBlockAvailable": "Mevcut", "sumBlockMax": "Maks", "swapSettingsApply": "Uygula", + "swapSupportedNetworks": "Ağlar (yerel swap desteği ile)", "swapWidgetDescription": "Haqq cüzdan, token değişiminiz için en iyi fiyatı bulur", "swapWidgetTitle": "Tokenları değiştir", "termsAgreementFirst": "Bu düğmelere tıklayarak kabul edersiniz ", diff --git a/src/components/swap/swap-input.tsx b/src/components/swap/swap-input.tsx index 68329636a..9b285d332 100644 --- a/src/components/swap/swap-input.tsx +++ b/src/components/swap/swap-input.tsx @@ -8,7 +8,7 @@ import {createTheme} from '@app/helpers'; import {useSumAmount} from '@app/hooks'; import {I18N} from '@app/i18n'; import {Balance} from '@app/services/balance'; -import {IContract} from '@app/types'; +import {IToken} from '@app/types'; import {ImageWrapper} from '../image-wrapper'; import { @@ -26,7 +26,7 @@ import { import {Placeholder} from '../ui/placeholder'; export type SwapInputProps = { - token: IContract; + token: IToken; currentBalance: Balance; availableBalance: Balance; amounts: ReturnType; @@ -97,10 +97,10 @@ export const SwapInput = observer( style={styles.cryptoBlock} onPress={onPressChangeToken}> - {!!token.icon && ( + {!!token.image && ( )} ; amountsOut: ReturnType; isEstimating: boolean; diff --git a/src/helpers/address-utils.ts b/src/helpers/address-utils.ts index 9b335a65c..473449d4c 100644 --- a/src/helpers/address-utils.ts +++ b/src/helpers/address-utils.ts @@ -120,7 +120,3 @@ export class AddressUtils { export const NATIVE_TOKEN_ADDRESS = AddressUtils.toEth( '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', ); - -export const WRAPPED_TOKEN_ADDRESS = AddressUtils.toEth( - '0xeC8CC083787c6e5218D86f9FF5f28d4cC377Ac54', -); diff --git a/src/i18n.ts b/src/i18n.ts index 1c9d13872..5ef3e8e36 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -1020,6 +1020,8 @@ export enum I18N { I18N = 'I18N', feeSettingsExpectedFee = 'feeSettingsExpectedFee', transactionNetwork = 'transactionNetwork', + noSwapRoutesFound = 'noSwapRoutesFound', + swapSupportedNetworks = 'swapSupportedNetworks', } const defaultTranslation = {en}; diff --git a/src/screens/SwapStack/swap-screen.tsx b/src/screens/SwapStack/swap-screen.tsx index ce1c75520..9cd0934d1 100644 --- a/src/screens/SwapStack/swap-screen.tsx +++ b/src/screens/SwapStack/swap-screen.tsx @@ -20,13 +20,13 @@ import {awaitForWallet, showModal} from '@app/helpers'; import {AddressUtils} from '@app/helpers/address-utils'; import {awaitForEventDone} from '@app/helpers/await-for-event-done'; import {awaitForJsonRpcSign} from '@app/helpers/await-for-json-rpc-sign'; +import {awaitForProvider} from '@app/helpers/await-for-provider'; import {AwaitValue, awaitForValue} from '@app/helpers/await-for-value'; import {getRpcProvider} from '@app/helpers/get-rpc-provider'; import {useSumAmount, useTypedRoute} from '@app/hooks'; -import {I18N} from '@app/i18n'; -import {Contracts} from '@app/models/contracts'; +import {I18N, getText} from '@app/i18n'; import {Currencies} from '@app/models/currencies'; -import {RemoteProviderConfig} from '@app/models/provider'; +import {Provider, RemoteProviderConfig} from '@app/models/provider'; import {Token} from '@app/models/tokens'; import {Wallet} from '@app/models/wallet'; import {navigator} from '@app/navigator'; @@ -48,7 +48,6 @@ import {message} from '@app/services/toast'; import { HaqqCosmosAddress, HaqqEthereumAddress, - IContract, IToken, ModalType, } from '@app/types'; @@ -105,13 +104,13 @@ export const SwapScreen = observer(() => { () => poolsData.contracts?.find?.(it => AddressUtils.equals( - it.eth_address!, + it.id!, (currentRoute || poolsData.routes[0])?.token0!, ), ) || - Contracts.getAll().find(it => + Token.getAll().find(it => AddressUtils.equals( - it.eth_address!, + it.id!, (currentRoute || poolsData.routes[0])?.token0!, ), ), @@ -121,13 +120,13 @@ export const SwapScreen = observer(() => { () => poolsData.contracts?.find?.(it => AddressUtils.equals( - it.eth_address!, + it.id!, (currentRoute || poolsData.routes[0])?.token1!, ), ) || - Contracts.getAll().find(it => + Token.getAll().find(it => AddressUtils.equals( - it.eth_address!, + it.id!, (currentRoute || poolsData.routes[0])?.token1!, ), ), @@ -216,7 +215,7 @@ export const SwapScreen = observer(() => { } const tokenData = Token.tokens?.[currentWallet.address]?.find(t => - AddressUtils.equals(t.id, tokenIn.eth_address!), + AddressUtils.equals(t.id, tokenIn.id!), ); if (tokenData) { logger.log('t0 available: tokenData', tokenData.value); @@ -237,7 +236,7 @@ export const SwapScreen = observer(() => { } const tokenData = Token.tokens?.[currentWallet.address]?.find(t => - AddressUtils.equals(t.id, tokenOut.eth_address!), + AddressUtils.equals(t.id, tokenOut.id!), ); if (tokenData) { return tokenData.value; @@ -313,8 +312,8 @@ export const SwapScreen = observer(() => { const response = await Indexer.instance.sushiPoolEstimate({ amount: t0Current.toHex(), sender: currentWallet.address, - // token_in: tokenIn?.eth_address!, - // token_out: tokenOut?.eth_address!, + // token_in: tokenIn?.id!, + // token_out: tokenOut?.id!, route: currentRoute?.route_hex!, currency_id: Currencies.currency?.id, abortSignal: estimateAbortController.current.signal, @@ -354,7 +353,7 @@ export const SwapScreen = observer(() => { }; const awaitForToken = useCallback( - async (initialValue: IContract) => { + async (initialValue: IToken) => { try { logger.log('awaitForToken', Token.tokens); if (!Token.tokens?.[currentWallet.address]) { @@ -374,27 +373,21 @@ export const SwapScreen = observer(() => { const isToken0 = AddressUtils.equals( currentRoute?.token0!, - initialValue.eth_address!, + initialValue.id!, ); const tokens = Token.tokens[AddressUtils.toEth(currentWallet.address)] || []; const currentToken = { - ...tokens.find(t => - AddressUtils.equals(t.id, initialValue.eth_address!), - )!, + ...tokens.find(t => AddressUtils.equals(t.id, initialValue.id!))!, value: isToken0 ? t0Available : t1Available, - id: `${currentWallet.address}_${initialValue.id}` as HaqqCosmosAddress, - eth_address: initialValue.eth_address, + tag: `${currentWallet.address}_${initialValue.id}` as HaqqCosmosAddress, + id: initialValue.id, }; const routes = isToken0 - ? routesByToken1.current[ - AddressUtils.toHaqq(initialValue.eth_address!) - ] - : routesByToken0.current[ - AddressUtils.toHaqq(initialValue.eth_address!) - ]; + ? routesByToken1.current[AddressUtils.toHaqq(initialValue.id!)] + : routesByToken0.current[AddressUtils.toHaqq(initialValue.id!)]; const possibleRoutesForSwap = routes .map(it => { @@ -407,32 +400,26 @@ export const SwapScreen = observer(() => { if (tokenContract) { return { ...tokenContract, - id: `${currentWallet.address}_${tokenAddress}` as HaqqCosmosAddress, - eth_address: AddressUtils.toEth(tokenAddress), + tag: `${currentWallet.address}_${tokenAddress}` as HaqqCosmosAddress, + id: AddressUtils.toEth(tokenAddress), }; } const contract = poolsData.contracts?.find(c => - AddressUtils.equals( - c?.eth_address!, - isToken0 ? it.token0 : it.token1, - ), + AddressUtils.equals(c?.id!, isToken0 ? it.token0 : it.token1), ) || - Contracts.getAll().find(c => - AddressUtils.equals( - c?.eth_address!, - isToken0 ? it.token0 : it.token1, - ), + Token.getAll().find(c => + AddressUtils.equals(c?.id!, isToken0 ? it.token0 : it.token1), ); if (contract) { return { ...contract, - id: `${currentWallet.address}_${tokenAddress}` as HaqqCosmosAddress, - image: contract.icon, + tag: `${currentWallet.address}_${tokenAddress}` as HaqqCosmosAddress, + image: contract.image, value: new Balance(0, 0, contract?.symbol!), - eth_address: AddressUtils.toEth(tokenAddress), + id: AddressUtils.toEth(tokenAddress), } as unknown as IToken; } @@ -475,7 +462,8 @@ export const SwapScreen = observer(() => { value: Token.tokens[ AddressUtils.toEth(value?.wallet?.address) - ].find(c => c.id === t.id?.split?.('_')[1])?.value ?? + // @ts-ignore + ].find(c => c.id === t.tag?.split?.('_')[1])?.value ?? new Balance(0, 0, t?.symbol!), }; })} @@ -492,7 +480,7 @@ export const SwapScreen = observer(() => { value, value.tokens.findIndex( // @ts-ignore - t => t.eth_address === initialValue.eth_address, + t => t.id === initialValue.id, ) || 0, ); }} @@ -518,7 +506,7 @@ export const SwapScreen = observer(() => { wallet, token: (AddressUtils.equals(tokenAddress, generatedISLMContract.id) ? generatedISLMContract - : value?.tokens?.[index]) as IContract & {value: Balance}, + : value?.tokens?.[index]) as IToken & {value: Balance}, }; logger.log('awaitForToken', result); return result; @@ -541,9 +529,8 @@ export const SwapScreen = observer(() => { const tokenValue = // @ts-ignore t0.value || - Token.tokens?.[wallet]?.find(t => - AddressUtils.equals(t.id, t0.eth_address!), - )?.value; + Token.tokens?.[wallet]?.find(t => AddressUtils.equals(t.id, t0.id!)) + ?.value; const availableIslm = app.getAvailableBalance(wallet); const symbol = t0.symbol || app.provider.denom; @@ -590,20 +577,20 @@ export const SwapScreen = observer(() => { logger.log('token', token); const needChangeTokenOut = - AddressUtils.equals(token.eth_address!, tokenOut?.eth_address!) && + AddressUtils.equals(token.id!, tokenOut?.id!) && token.symbol === tokenOut.symbol; const filteredRoutes = poolsData.routes.filter(r => - AddressUtils.equals(r.token0!, token?.eth_address!), + AddressUtils.equals(r.token0!, token?.id!), ); if (needChangeTokenOut) { const route = filteredRoutes.find(r => - AddressUtils.equals(r.token1!, tokenIn?.eth_address!), + AddressUtils.equals(r.token1!, tokenIn?.id!), ); setCurrentRoute(route || filteredRoutes[0]); } else { const route = filteredRoutes.find(r => - AddressUtils.equals(r.token1!, tokenOut?.eth_address!), + AddressUtils.equals(r.token1!, tokenOut?.id!), ); setCurrentRoute(route! || filteredRoutes[0]); } @@ -634,21 +621,21 @@ export const SwapScreen = observer(() => { setCurrentWallet(() => wallet); const needChangeTokenIn = - AddressUtils.equals(token.eth_address!, tokenIn?.eth_address!) && + AddressUtils.equals(token.id!, tokenIn?.id!) && token.symbol === tokenIn.symbol; const filteredRoutes = poolsData.routes.filter(r => - AddressUtils.equals(r.token1!, token?.eth_address!), + AddressUtils.equals(r.token1!, token?.id!), ); if (needChangeTokenIn) { const route = filteredRoutes.find(r => - AddressUtils.equals(r.token0!, tokenOut?.eth_address!), + AddressUtils.equals(r.token0!, tokenOut?.id!), ); setCurrentRoute(route || filteredRoutes[0]); } else { const route = filteredRoutes.find(r => - AddressUtils.equals(r.token0!, tokenIn?.eth_address!), + AddressUtils.equals(r.token0!, tokenIn?.id!), ); setCurrentRoute(route! || filteredRoutes[0]); } @@ -768,11 +755,7 @@ export const SwapScreen = observer(() => { try { setApproveInProgress(() => true); const provider = await getRpcProvider(app.provider); - const erc20Token = new ethers.Contract( - tokenIn?.eth_address!, - ERC20_ABI, - provider, - ); + const erc20Token = new ethers.Contract(tokenIn?.id!, ERC20_ABI, provider); const amountBN = ethers.utils.parseUnits( amountsIn.amount, @@ -793,7 +776,7 @@ export const SwapScreen = observer(() => { params: [ { from: currentWallet.address, - to: tokenIn?.eth_address, + to: tokenIn?.id, value: '0x0', data: data, }, @@ -1042,14 +1025,15 @@ export const SwapScreen = observer(() => { Indexer.instance .sushiPools() .then(async data => { + await Token.fetchTokens(true); const tokens = Array.from( new Set([ ...data.routes.map(r => r.token0), ...data.routes.map(r => r.token1), ]), ) - .map(token => Contracts.getById(token)) - .filter(Boolean) as IContract[]; + .map(token => Token.getById(token)) + .filter(Boolean) as IToken[]; setPoolsData(() => ({ ...data, @@ -1075,6 +1059,43 @@ export const SwapScreen = observer(() => { AddressUtils.equals(r.token0, RemoteProviderConfig.wethAddress), ) || data.routes[1], ); + if (!data.pools?.length) { + showModal(ModalType.error, { + title: getText(I18N.blockRequestErrorTitle), + description: getText(I18N.noSwapRoutesFound), + close: getText(I18N.pinErrorModalClose), + async onClose() { + navigator.goBack(); + const providersIDsPromises = await Promise.allSettled( + Provider.getAll().map(async p => { + const indexer = new Indexer(p.ethChainId); + const config = await indexer.getProviderConfig(); + if (config.swap_enabled) { + return p.id; + } + return null; + }), + ); + + const providersIDs = providersIDsPromises.map(promiseData => { + if (promiseData.status === 'fulfilled' && promiseData.value) { + return promiseData.value; + } + return null; + }); + + const providerId = await awaitForProvider({ + providers: Provider.getAll().filter(p => + providersIDs.includes(p.id!), + ), + initialProviderId: app.providerId!, + title: I18N.swapSupportedNetworks, + }); + app.providerId = providerId; + await awaitForEventDone(Events.onProviderChanged); + }, + }); + } }) .catch(err => { logger.captureException(err, 'useFocusEffect:Indexer.sushiPools'); diff --git a/src/services/indexer/indexer.types.ts b/src/services/indexer/indexer.types.ts index 8b640c904..19bfbc0ba 100644 --- a/src/services/indexer/indexer.types.ts +++ b/src/services/indexer/indexer.types.ts @@ -1,4 +1,4 @@ -import {HaqqCosmosAddress, IContract} from '@app/types'; +import {HaqqCosmosAddress, IContract, IToken} from '@app/types'; import {EIP155_SIGNING_METHODS} from '@app/variables/EIP155'; export type SushiRoute = { @@ -18,7 +18,7 @@ export type SushiPool = { }; export type SushiPoolResponse = { - contracts: IContract[]; + contracts: IToken[]; routes: SushiRoute[]; pools: SushiPool[]; };