From 1eb3b2621381bef179d8b898f9d4820950903d31 Mon Sep 17 00:00:00 2001 From: Innokentiy Mazhara Date: Mon, 7 Jun 2021 14:59:42 +0300 Subject: [PATCH] 'Swap' form: fix Kalamint exchanging + implement selecting another initial asset than TEZ (#356) * Add a new quipuswap contracts factory * Implement choosing the initial asset which the user viewed before --- src/app/PageRouter.tsx | 5 +- src/app/pages/Explore.tsx | 2 +- src/app/pages/Swap.tsx | 8 +- src/app/templates/SwapForm.tsx | 28 +++++- .../templates/SwapForm/useSwappableAssets.ts | 95 ++++++++++++------- .../use-page-router-analytics.hook.ts | 18 ++-- src/lib/temple/front/swap.ts | 26 +++-- 7 files changed, 124 insertions(+), 58 deletions(-) diff --git a/src/app/PageRouter.tsx b/src/app/PageRouter.tsx index 6d645e323..9a47d5a08 100644 --- a/src/app/PageRouter.tsx +++ b/src/app/PageRouter.tsx @@ -85,7 +85,10 @@ const ROUTE_MAP = Woozie.Router.createMap([ "/send/:assetSlug?", onlyReady(({ assetSlug }) => ), ], - ["/swap", onlyReady(() => )], + [ + "/swap/:assetSlug?", + onlyReady(({ assetSlug }) => ), + ], ["/delegate", onlyReady(() => )], ["/dapps", onlyReady(() => )], ["/manage-assets", onlyReady(() => )], diff --git a/src/app/pages/Explore.tsx b/src/app/pages/Explore.tsx index 042902696..9bb54e1b8 100644 --- a/src/app/pages/Explore.tsx +++ b/src/app/pages/Explore.tsx @@ -133,7 +133,7 @@ const Explore: FC = ({ assetSlug }) => { } Icon={SwapIcon} - href="/swap" + href={asset ? `/swap/${getAssetKey(asset)}` : "/swap"} disabled={!canSend} tippyProps={tippyProps} /> diff --git a/src/app/pages/Swap.tsx b/src/app/pages/Swap.tsx index 7437ee59e..b301544ba 100644 --- a/src/app/pages/Swap.tsx +++ b/src/app/pages/Swap.tsx @@ -5,7 +5,11 @@ import PageLayout from "app/layouts/PageLayout"; import SwapForm from "app/templates/SwapForm"; import { t } from "lib/i18n/react"; -const Swap: React.FC = () => ( +type SwapProps = { + assetSlug?: string | null; +}; + +const Swap: React.FC = ({ assetSlug }) => ( @@ -15,7 +19,7 @@ const Swap: React.FC = () => ( >
- +
diff --git a/src/app/templates/SwapForm.tsx b/src/app/templates/SwapForm.tsx index 4dd5503c8..4ea5a2fa1 100644 --- a/src/app/templates/SwapForm.tsx +++ b/src/app/templates/SwapForm.tsx @@ -44,6 +44,8 @@ import { TEZ_ASSET, assetAmountToUSD, ExchangerType, + useAssetBySlug, + TempleAsset, } from "lib/temple/front"; import useTippy from "lib/ui/useTippy"; @@ -58,7 +60,12 @@ type SwapFormValues = { const maxTolerancePercentage = 30; -const SwapFormWrapper: React.FC = () => { +type SwapFormWrapperProps = { + assetSlug?: string | null; +}; + +const SwapFormWrapper: React.FC = ({ assetSlug }) => { + const defaultAsset = useAssetBySlug(assetSlug) ?? undefined; const { isSupportedNetwork } = useSwappableAssets(); if (!isSupportedNetwork) { @@ -69,14 +76,18 @@ const SwapFormWrapper: React.FC = () => { ); } - return ; + return ; }; export default SwapFormWrapper; const feeLabel = `${toLocalFixed(new BigNumber("0.3"))}%`; -const SwapForm: React.FC = () => { +type SwapFormProps = { + defaultAsset?: TempleAsset; +}; + +const SwapForm: React.FC = ({ defaultAsset }) => { const { assets, quipuswapTokensWhitelist, tokensExchangeData, tezUsdPrice } = useSwappableAssets(); const { getInputAssetAmount, getOutputAssetAmounts } = useSwapCalculations(); @@ -94,10 +105,19 @@ const SwapForm: React.FC = () => { ) ? "quipuswap" : "dexter"; + const initialAsset = useMemo(() => { + if ( + defaultAsset && + assets.some((asset) => assetsAreSame(asset, defaultAsset)) + ) { + return defaultAsset; + } + return assets[0]; + }, [assets, defaultAsset]); const formContextValues = useForm({ defaultValues: { exchanger: defaultExchanger, - input: { asset: assets[0] }, + input: { asset: initialAsset }, output: {}, tolerancePercentage: 1, }, diff --git a/src/app/templates/SwapForm/useSwappableAssets.ts b/src/app/templates/SwapForm/useSwappableAssets.ts index de970cc2d..af8a544bf 100644 --- a/src/app/templates/SwapForm/useSwappableAssets.ts +++ b/src/app/templates/SwapForm/useSwappableAssets.ts @@ -152,13 +152,21 @@ function getTokenId(token: TempleToken, replacer?: number) { return token.type === TempleAssetType.FA2 ? token.id : replacer; } -async function getTokensToExchange( - contractAddress: string, +async function getTokensToExchangeBigmaps( + contractsAddresses: string[], tezos: TezosToolkit -): Promise { - const tokenListContract = await loadContract(tezos, contractAddress, false); - const tokenListStorage = await tokenListContract.storage(); - return tokenListStorage.token_to_exchange; +): Promise { + return Promise.all( + contractsAddresses.map(async (contractAddress) => { + const tokenListContract = await loadContract( + tezos, + contractAddress, + false + ); + const tokenListStorage = await tokenListContract.storage(); + return tokenListStorage.token_to_exchange; + }) + ); } export const getAssetExchangeData = ( @@ -361,14 +369,14 @@ export default function useSwappableAssets( const result: Record> = {}; const whitelist = quipuswapTokenWhitelists?.get(chainId) ?? []; const quipuswapContracts = QUIPUSWAP_CONTRACTS.get(chainId)!; - const fa12FactoryAddress = quipuswapContracts.fa12Factory; - const fa2FactoryAddress = quipuswapContracts.fa2Factory; - const fa2TokensToExchange = fa2FactoryAddress - ? await getTokensToExchange(fa2FactoryAddress, tezos) - : undefined; - const fa12TokensToExchange = fa12FactoryAddress - ? await getTokensToExchange(fa12FactoryAddress, tezos) - : undefined; + const fa12FactoriesAddresses = quipuswapContracts.fa12Factory; + const fa2FactoriesAddresses = quipuswapContracts.fa2Factory; + const fa2TokensToExchangeBigmaps = fa2FactoriesAddresses + ? await getTokensToExchangeBigmaps(fa2FactoriesAddresses, tezos) + : []; + const fa12TokensToExchangeBigmaps = fa12FactoriesAddresses + ? await getTokensToExchangeBigmaps(fa12FactoriesAddresses, tezos) + : []; const initialExchangableTokens = [ ...whitelist, ...qsStoredTokens.filter( @@ -383,14 +391,21 @@ export default function useSwappableAssets( const tokenId = getTokenId(token); let exchangeContractAddress: string | undefined; if (token.type === TempleAssetType.FA2) { - // @ts-ignore - exchangeContractAddress = - fa2TokensToExchange && - (await fa2TokensToExchange.get([token.address, token.id]))!; + exchangeContractAddress = ( + await Promise.all( + fa2TokensToExchangeBigmaps.map((bigMap) => + bigMap.get([token.address, token.id]) + ) + ) + ).find((value) => value !== undefined); } else { - exchangeContractAddress = - fa12TokensToExchange && - ((await fa12TokensToExchange.get(token.address))! as string); + exchangeContractAddress = ( + await Promise.all( + fa12TokensToExchangeBigmaps.map((bigMap) => + bigMap.get(token.address) + ) + ) + ).find((value) => value !== undefined); } if (!result[token.address]) { result[token.address] = {}; @@ -497,21 +512,23 @@ export default function useSwappableAssets( let isFA2Token = false; let fa2IdIsCorrect = false; const quipuswapContracts = QUIPUSWAP_CONTRACTS.get(chainId)!; - const fa2FactoryAddress = quipuswapContracts.fa2Factory; - const fa12FactoryAddress = quipuswapContracts.fa12Factory; - const fa2TokensList = fa2FactoryAddress - ? await getTokensToExchange(fa2FactoryAddress, tezos) - : undefined; + const fa2FactoriesAddresses = quipuswapContracts.fa2Factory; + const fa12FactoriesAddresses = quipuswapContracts.fa12Factory; + const fa2TokensToExchangeBigmaps = fa2FactoriesAddresses + ? await getTokensToExchangeBigmaps(fa2FactoriesAddresses, tezos) + : []; let exchangeContractAddress: string | undefined; try { await assertFA2TokenContract(contract); isFA2Token = true; if (tokenId !== undefined) { - // @ts-ignore - exchangeContractAddress = await fa2TokensList.get([ - searchStr, - tokenId, - ]); + exchangeContractAddress = ( + await Promise.all( + fa2TokensToExchangeBigmaps.map((bigMap) => + bigMap.get([searchStr, tokenId]) + ) + ) + ).find((value) => value !== undefined); fa2IdIsCorrect = !!exchangeContractAddress; } } catch (e) {} @@ -523,11 +540,17 @@ export default function useSwappableAssets( } if (!isFA2Token) { try { - const tokensList = fa12FactoryAddress - ? await getTokensToExchange(fa12FactoryAddress, tezos) - : undefined; - if (tokensList) { - exchangeContractAddress = await tokensList.get(searchStr); + const fa12TokensToExchangeBigmaps = fa12FactoriesAddresses + ? await getTokensToExchangeBigmaps(fa12FactoriesAddresses, tezos) + : []; + if (fa12TokensToExchangeBigmaps.length > 0) { + exchangeContractAddress = ( + await Promise.all( + fa12TokensToExchangeBigmaps.map((bigMap) => + bigMap.get(searchStr) + ) + ) + ).find((value) => value !== undefined); } } catch (e) {} } diff --git a/src/lib/analytics/use-page-router-analytics.hook.ts b/src/lib/analytics/use-page-router-analytics.hook.ts index 5f4d063c6..fe53ad52b 100644 --- a/src/lib/analytics/use-page-router-analytics.hook.ts +++ b/src/lib/analytics/use-page-router-analytics.hook.ts @@ -1,10 +1,14 @@ -import { useEffect } from 'react'; +import { useEffect } from "react"; -import { useAnalytics } from './use-analytics.hook'; +import { useAnalytics } from "./use-analytics.hook"; -const pageRoutesWithToken = ['/explore', '/send']; +const pageRoutesWithToken = ["/explore", "/send", "/swap"]; -export const usePageRouterAnalytics = (pathname: string, search: string, isContextReady: boolean) => { +export const usePageRouterAnalytics = ( + pathname: string, + search: string, + isContextReady: boolean +) => { const { pageEvent } = useAnalytics(); useEffect(() => { @@ -12,9 +16,9 @@ export const usePageRouterAnalytics = (pathname: string, search: string, isConte return void pageEvent("/welcome", search); } - if (pageRoutesWithToken.some(route => pathname.startsWith(route))) { - const [, route = '', tokenSlug = 'tez'] = pathname.split('/'); - const [tokenAddress, tokenId = '0'] = tokenSlug.split('_'); + if (pageRoutesWithToken.some((route) => pathname.startsWith(route))) { + const [, route = "", tokenSlug = "tez"] = pathname.split("/"); + const [tokenAddress, tokenId = "0"] = tokenSlug.split("_"); return void pageEvent(`/${route}`, search, tokenAddress, tokenId); } diff --git a/src/lib/temple/front/swap.ts b/src/lib/temple/front/swap.ts index ec4c31635..3e2992af1 100644 --- a/src/lib/temple/front/swap.ts +++ b/src/lib/temple/front/swap.ts @@ -75,27 +75,39 @@ export const DEXTER_EXCHANGE_CONTRACTS = new Map< export const QUIPUSWAP_CONTRACTS = new Map< string, - Partial> + Partial> >([ [ TempleChainId.Edo2net, { - fa12Factory: "KT1WEcbPNGZNe6d5pm3eNufqe7cHS77DBG2G", - fa2Factory: "KT1KGdYTfLdzTKpyQbKkHJ2ASmBYa84hnCiQ", + fa12Factory: ["KT1WEcbPNGZNe6d5pm3eNufqe7cHS77DBG2G"], + fa2Factory: ["KT1KGdYTfLdzTKpyQbKkHJ2ASmBYa84hnCiQ"], }, ], [ TempleChainId.Florencenet, { - fa12Factory: "KT1We4CHneKjnCkovTDV34qc4W7xzWbn5LwY", - fa2Factory: "KT1SQX24W2v6D5sgihznax1eBykEGQNc7UpD", + fa12Factory: [ + "KT195gyo5G7pay2tYweWDeYFkGLqcvQTXoCW", + "KT1We4CHneKjnCkovTDV34qc4W7xzWbn5LwY", + ], + fa2Factory: [ + "KT1HjLwPC3sbh6W5HjaKBsiVPTgptcNbnXnc", + "KT1SQX24W2v6D5sgihznax1eBykEGQNc7UpD", + ], }, ], [ TempleChainId.Mainnet, { - fa12Factory: "KT1Lw8hCoaBrHeTeMXbqHPG4sS4K1xn7yKcD", - fa2Factory: "KT1SwH9P1Tx8a58Mm6qBExQFTcy2rwZyZiXS", + fa12Factory: [ + "KT1FWHLMk5tHbwuSsp31S4Jum4dTVmkXpfJw", + "KT1Lw8hCoaBrHeTeMXbqHPG4sS4K1xn7yKcD", + ], + fa2Factory: [ + "KT1PvEyN1xCFCgorN92QCfYjw3axS6jawCiJ", + "KT1SwH9P1Tx8a58Mm6qBExQFTcy2rwZyZiXS", + ], }, ], ]);