From bdcdeabd05efe7d18faf7b5f287456f483126675 Mon Sep 17 00:00:00 2001 From: Altynbek Orumbayev Date: Sun, 23 Apr 2023 23:26:11 +0200 Subject: [PATCH] feat: telegram webapps support (#158) --- api/storage/package.json | 2 +- api/storage/validate-hash.ts | 74 +++++++++ api/storage/yarn.lock | 83 ++++++---- package.json | 4 +- src/common/constants.ts | 3 +- src/components/Dialogs/ConfirmDialog.test.tsx | 4 +- src/components/Dialogs/ConfirmDialog.tsx | 144 ++++++++++++++++-- src/components/Dialogs/ManageSwapDialog.tsx | 123 +++++++-------- src/components/Footers/Footer.tsx | 1 + src/components/Footers/TelegramFooter.tsx | 31 ++++ src/components/Layouts/Layout.tsx | 34 ++++- src/components/Tables/AssetsTable.tsx | 3 +- src/components/Tables/MySwapsTable.tsx | 3 +- src/pages/_app.page.tsx | 6 +- src/pages/_document.page.tsx | 6 + src/pages/swaps/asa-to-asa.page.tsx | 53 +++---- src/pages/swaps/asas-to-algo.page.tsx | 53 +++---- src/pages/swaps/my-swaps.page.tsx | 8 +- src/redux/slices/applicationSlice.ts | 6 + src/types/globals.d.ts | 16 ++ .../api/swaps/saveSwapConfigurations.test.ts | 72 +++++++-- src/utils/api/swaps/saveSwapConfigurations.ts | 12 +- yarn.lock | 10 ++ 23 files changed, 571 insertions(+), 180 deletions(-) create mode 100644 api/storage/validate-hash.ts create mode 100644 src/components/Footers/TelegramFooter.tsx create mode 100644 src/types/globals.d.ts diff --git a/api/storage/package.json b/api/storage/package.json index 155de81..2b1e858 100644 --- a/api/storage/package.json +++ b/api/storage/package.json @@ -10,6 +10,6 @@ "web3.storage": "4.5.4" }, "devDependencies": { - "@vercel/node": "2.10.3" + "@vercel/node": "2.12.0" } } diff --git a/api/storage/validate-hash.ts b/api/storage/validate-hash.ts new file mode 100644 index 0000000..dd268ac --- /dev/null +++ b/api/storage/validate-hash.ts @@ -0,0 +1,74 @@ +import { webcrypto } from 'crypto'; +import type { NextApiRequest, NextApiResponse } from 'next'; + +type Data = { ok: boolean } | { error: string }; + +async function isHashValid(data: Record, botToken: string) { + const encoder = new TextEncoder(); + + const checkString = Object.keys(data) + .filter((key) => key !== `hash`) + .map((key) => `${key}=${data[key]}`) + .sort() + .join(`\n`); + + const secretKey = await webcrypto.subtle.importKey( + `raw`, + encoder.encode(`WebAppData`), + { name: `HMAC`, hash: `SHA-256` }, + true, + [`sign`], + ); + + const secret = await webcrypto.subtle.sign( + `HMAC`, + secretKey, + encoder.encode(botToken), + ); + + const signatureKey = await webcrypto.subtle.importKey( + `raw`, + secret, + { name: `HMAC`, hash: `SHA-256` }, + true, + [`sign`], + ); + + const signature = await webcrypto.subtle.sign( + `HMAC`, + signatureKey, + encoder.encode(checkString), + ); + + const hex = Buffer.from(signature).toString(`hex`); + + return data.hash === hex; +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + if (req.method !== `POST`) { + return res.status(405).json({ error: `Method not allowed` }); + } + + if (!req.body.hash) { + return res.status(400).json({ + error: `Missing required field hash`, + }); + } + + if (!process.env.BOT_TOKEN) { + return res.status(500).json({ error: `Internal server error` }); + } + + const data = Object.fromEntries(new URLSearchParams(req.body.hash)); + const isValid = await isHashValid(data, process.env.BOT_TOKEN); + + if (isValid) { + return res.status(200).json({ ok: true }); + } + + return res.status(403).json({ error: `Invalid hash` }); +} diff --git a/api/storage/yarn.lock b/api/storage/yarn.lock index eb1c9ea..43abd39 100644 --- a/api/storage/yarn.lock +++ b/api/storage/yarn.lock @@ -35,16 +35,21 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@edge-runtime/format@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@edge-runtime/format/-/format-1.1.0.tgz#5a209221a8bae7791d6e465c480f146249d1e15f" - integrity sha512-MkLDDtPhXZIMx83NykdFmOpF7gVWIdd6GBHYb8V/E+PKWvD2pK/qWx9B30oN1iDJ2XBm0SGDjz02S8nDHI9lMQ== +"@edge-runtime/format@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@edge-runtime/format/-/format-2.0.1.tgz#765295809ff6a0938da739e13ef327d95a418395" + integrity sha512-aE+9DtBvQyg349srixtXEUNauWtIv5HTKPy8Q9dvG1NvpldVIvvhcDBI+SuvDVM8kQl8phbYnp2NTNloBCn/Yg== "@edge-runtime/primitives@2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@edge-runtime/primitives/-/primitives-2.0.0.tgz#b4bf44f9cab36aee3027fe4c3ff3cc1d5713e155" integrity sha512-AXqUq1zruTJAICrllUvZcgciIcEGHdF6KJ3r6FM0n4k8LpFxZ62tPWVIJ9HKm+xt+ncTBUZxwgUaQ73QMUQEKw== +"@edge-runtime/primitives@2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@edge-runtime/primitives/-/primitives-2.1.2.tgz#8ff657f12a7f8c7fc4e3a0c10ec19356ef2d656d" + integrity sha512-SR04SMDybALlhIYIi0hiuEUwIl0b7Sn+RKwQkX6hydg4+AKMzBNDFhj2nqHDD1+xkHArV9EhmJIb6iGjShwSzg== + "@edge-runtime/vm@2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@edge-runtime/vm/-/vm-2.0.0.tgz#9170d2d03761eff4e27687888c4b2d9af1f94c7d" @@ -52,6 +57,13 @@ dependencies: "@edge-runtime/primitives" "2.0.0" +"@edge-runtime/vm@2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@edge-runtime/vm/-/vm-2.1.2.tgz#d760ce27b659c17c470b23453321769c08d213f5" + integrity sha512-j4H5S26NJhYOyjVMN8T/YJuwwslfnEX1P0j6N2Rq1FaubgNowdYunA9nlO7lg8Rgjv6dqJ2zKuM7GD1HFtNSGw== + dependencies: + "@edge-runtime/primitives" "2.1.2" + "@ipld/car@^3.0.1", "@ipld/car@^3.1.4", "@ipld/car@^3.2.3": version "3.2.4" resolved "https://registry.yarnpkg.com/@ipld/car/-/car-3.2.4.tgz#115951ba2255ec51d865773a074e422c169fb01c" @@ -269,32 +281,41 @@ resolved "https://registry.yarnpkg.com/@vercel/build-utils/-/build-utils-6.7.1.tgz#94bccb959d9f2dcdecb7744c939b546073902373" integrity sha512-Ecc9oQBSVwk1suENcRcj1L6gQrUt4+0XA9oPFxrUpoFEk04lP/ZV3qAQPk+ex08N+vfUulYdqb+cmVTnwqsmqw== +"@vercel/error-utils@1.0.8": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@vercel/error-utils/-/error-utils-1.0.8.tgz#5cefc4142820846d011cf048ddfb0afda81d484b" + integrity sha512-s+f7jP2oH1koICbQ8e3K9hOpOeUct7rbCnF9qsNwXemq850wAh2e90tp9R6oYBM0BNpiLRRm+oG5zD2sCIm3HQ== + "@vercel/node-bridge@4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@vercel/node-bridge/-/node-bridge-4.0.1.tgz#e4f41188f61d9cd4e7c44de31d436dd447e1978c" integrity sha512-XEfKfnLGzlIBpad7eGNPql1HnMhoSTv9q3uDNC4axdaAC/kI5yvl8kXjuCPAXYvpbJnVQPpcSUC5/r5ap8F3jA== -"@vercel/node@2.10.3": - version "2.10.3" - resolved "https://registry.yarnpkg.com/@vercel/node/-/node-2.10.3.tgz#24a65d9f7e69161762c85df630601f3852308c26" - integrity sha512-R6YwD7YTV4OPEjXnthTP2Zn96ZF2TAjmBhGKfYC9ZuqlmFzSxqyuHn+RUSkknkKBO46b4OzaNdi5XVnAdJizLA== +"@vercel/node@2.12.0": + version "2.12.0" + resolved "https://registry.yarnpkg.com/@vercel/node/-/node-2.12.0.tgz#2eff4ffb04ae3ca19a7fd7e49e4f376791284abb" + integrity sha512-QItQ4DjKrHqTMk/hmtX64V5RfDdp+fDoFzbSbPUICkIOHK3EBCJ5c/392Iv05AwSv+mJIALZUGRQz5o4HKvs6A== dependencies: "@edge-runtime/vm" "2.0.0" "@types/node" "14.18.33" "@vercel/build-utils" "6.7.1" + "@vercel/error-utils" "1.0.8" "@vercel/node-bridge" "4.0.1" - "@vercel/static-config" "2.0.15" - edge-runtime "2.0.0" + "@vercel/static-config" "2.0.16" + async-listen "1.2.0" + edge-runtime "2.1.4" esbuild "0.14.47" exit-hook "2.2.1" node-fetch "2.6.7" + path-to-regexp "6.2.1" + ts-morph "12.0.0" ts-node "10.9.1" typescript "4.3.4" -"@vercel/static-config@2.0.15": - version "2.0.15" - resolved "https://registry.yarnpkg.com/@vercel/static-config/-/static-config-2.0.15.tgz#a1e4d052e50cb9493071aaa2b4ca5e05c054fada" - integrity sha512-A/N3ZGiOOMql9JArwBTIfhFngFtmVC7ndKQKp0FoFq8MO79AS5qBBtdpILS5QA71M5v+9CPjVkHxN6QweU55Xg== +"@vercel/static-config@2.0.16": + version "2.0.16" + resolved "https://registry.yarnpkg.com/@vercel/static-config/-/static-config-2.0.16.tgz#2495325056e62b94925d8432b703bbf5625b06e5" + integrity sha512-lULo+NWBMpTJb9kR4AwYYK/2e7wknTJO2iFxgYYOkG5i12WHgPhMnXDKrEOcotxctd0yPKx3TsWVGEXniNm63g== dependencies: ajv "8.6.3" json-schema-to-ts "1.6.4" @@ -430,6 +451,16 @@ arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== +async-listen@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/async-listen/-/async-listen-1.2.0.tgz#861ab6f92e1703ba54498b10ddb9b5da7b69f363" + integrity sha512-CcEtRh/oc9Jc4uWeUwdpG/+Mb2YUHKmdaTf0gUr7Wa+bfp4xx70HOb3RuSTJMvqKNB1TkdTfjLdrcz2X4rkkZA== + +async-listen@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/async-listen/-/async-listen-2.0.3.tgz#be1be5a1b15e6007123e67275450fc4e84955e61" + integrity sha512-WVLi/FGIQaXyfYyNvmkwKT1RZbkzszLLnmW/gFCc5lbVvN/0QQCWpBwRBk2OWSdkkmKRBc8yD6BrKsjA3XKaSw== + available-typed-arrays@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" @@ -633,15 +664,15 @@ dns-over-http-resolver@^1.2.3: native-fetch "^3.0.0" receptacle "^1.3.2" -edge-runtime@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/edge-runtime/-/edge-runtime-2.0.0.tgz#4739e1c8f4237db8ad8a16db5f0e28cc6de16aab" - integrity sha512-TmRJhKi4mlM1e+zgF4CSzVU5gJ1sWj7ia+XhVgZ8PYyYUxk4PPjJU8qScpSLsAbdSxoBghLxdMuwuCzdYLd1sQ== +edge-runtime@2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/edge-runtime/-/edge-runtime-2.1.4.tgz#aea4e103897f451f98a9094df26b07c4d1eb75b3" + integrity sha512-SertKByzAmjm+MkLbFl1q0ko+/90V24dhZgQM8fcdguQaDYVEVtb6okEBGeg8IQgL1/JUP8oSlUIxSI/bvsVRQ== dependencies: - "@edge-runtime/format" "1.1.0" - "@edge-runtime/vm" "2.0.0" + "@edge-runtime/format" "2.0.1" + "@edge-runtime/vm" "2.1.2" + async-listen "2.0.3" exit-hook "2.2.1" - http-status "1.5.3" mri "1.2.0" picocolors "1.0.0" pretty-bytes "5.6.0" @@ -1033,11 +1064,6 @@ hosted-git-info@^4.0.1: dependencies: lru-cache "^6.0.0" -http-status@1.5.3: - version "1.5.3" - resolved "https://registry.yarnpkg.com/http-status/-/http-status-1.5.3.tgz#9d1f6adcd1a609f535679f6e1b82811b96c3306e" - integrity sha512-jCClqdnnwigYslmtfb28vPplOgoiZ0siP2Z8C5Ua+3UKbx410v+c+jT+jh1bbI4TvcEySuX0vd/CfFZFbDkJeQ== - iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -1842,6 +1868,11 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-to-regexp@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5" + integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw== + peer-id@^0.16.0: version "0.16.0" resolved "https://registry.yarnpkg.com/peer-id/-/peer-id-0.16.0.tgz#0913062cfa4378707fe69c949b5720b3efadbf32" diff --git a/package.json b/package.json index 2261f32..0b48864 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "algoworld-swapper", "description": "Free and trustless ASA swapper, powered by Algorand ⚡️", - "version": "0.9.4", + "version": "1.0.1", "private": true, "author": "AlgoWorld ", "license": "GPL-3.0", @@ -48,6 +48,7 @@ "@randlabs/myalgo-connect": "1.4.2", "@reduxjs/toolkit": "1.9.3", "@txnlab/use-wallet": "1.2.11", + "@vkruglikov/react-telegram-web-app": "1.8.0", "@walletconnect/client": "1.8.0", "algorand-walletconnect-qrcode-modal": "1.8.0", "algosdk": "2.2.0", @@ -66,6 +67,7 @@ "react-tsparticles": "2.9.3", "react-use": "17.4.0", "swr": "1.3.0", + "telegram-webapps-types": "1.0.5", "tsparticles": "2.9.3" }, "devDependencies": { diff --git a/src/common/constants.ts b/src/common/constants.ts index 508dcee..1386dd3 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -18,7 +18,7 @@ import { ChainType } from '@/models/Chain'; import { IpfsGateway } from '@/models/Gateway'; -import { PROVIDER_ID } from '@txnlab/use-wallet'; +import { PROVIDER_ID as WALLET_PROVIDER_ID } from '@txnlab/use-wallet'; export const CHAIN_TYPE: ChainType = process.env.NEXT_PUBLIC_CHAIN_TYPE ? (process.env.NEXT_PUBLIC_CHAIN_TYPE.trim().toLowerCase() as ChainType) @@ -99,4 +99,5 @@ export const PERFORM_SWAP_PERFORM_BUTTON_ID = `AWPerformSwapPerformButton`; export const PUBLIC_SWAPS_SEARCH_FIELD_ID = `AWPublicSwapsSearchField`; export const PUBLIC_SWAPS_SEARCH_BUTTON_ID = `AWPublicSwapsSearchButton`; +export const PROVIDER_ID = WALLET_PROVIDER_ID; export const WALLET_PROVIDER_IDS = [PROVIDER_ID.MYALGO, PROVIDER_ID.PERA]; diff --git a/src/components/Dialogs/ConfirmDialog.test.tsx b/src/components/Dialogs/ConfirmDialog.test.tsx index 141554f..666bb4c 100644 --- a/src/components/Dialogs/ConfirmDialog.test.tsx +++ b/src/components/Dialogs/ConfirmDialog.test.tsx @@ -1,13 +1,13 @@ import * as React from 'react'; -import { render } from '@testing-library/react'; import ConfirmDialog from './ConfirmDialog'; +import renderWithProviders from '@/__utils__/renderWithProviders'; describe(`ConfirmDialog`, () => { it(`renders the correct content and handles the buttons correctly`, () => { const setOpenMock = jest.fn(); const onConfirmMock = jest.fn(); const onSwapVisibilityChangeMock = jest.fn(); - const { getByText } = render( + const { getByText } = renderWithProviders( { + const { gateway, chain, proxy } = useAppSelector( + (state) => state.application, + ); + const [loading, setLoading] = useState(false); + + const { activeAddress, signTransactions } = useWallet(); + const address = useMemo(() => { + return activeAddress || ``; + }, [activeAddress]); + + const existingSwaps = useAppSelector((state) => state.application.swaps); + + const proxyExistsState = useAsyncRetry(async () => { + return await accountExists(chain, proxy.address()); + }, [proxy]); + + const proxyExists = useMemo(() => { + if (proxyExistsState.loading || proxyExistsState.error) return false; + + return proxyExistsState.value; + }, [proxyExistsState]); + + const swapConfigsState = useAsyncRetry(async () => { + return await loadSwapConfigurations(chain, gateway, proxy.address()); + }, [proxy]); + + const hasSwapConfigurations = useMemo(() => { + console.log(swapConfigsState); + if (swapConfigsState.loading || swapConfigsState.error) return false; + + console.log(swapConfigsState); + const allConfigs = swapConfigsState.value; + + if (!allConfigs) return false; + + return allConfigs.length > 0; + }, [swapConfigsState]); + + const signAndSendSaveSwapConfigTxns = async (proxy: LogicSigAccount) => { + if (!address) { + return undefined; + } + + const cidData = await saveSwapConfigurations([...existingSwaps]); + + if (cidData) { + const saveSwapConfigTxns = await createSaveSwapConfigTxns( + chain, + address, + proxy, + (await accountExists(chain, proxy.address())) ? 10_000 : 110_000, + cidData, + ); + const signedSaveSwapConfigTxns = await processTransactions( + saveSwapConfigTxns, + signTransactions, + ).catch(() => { + toast.error(TXN_SIGNING_CANCELLED_MESSAGE); + return undefined; + }); + + if (!signedSaveSwapConfigTxns) { + return undefined; + } + + const saveSwapConfigResponse = await submitTransactions( + chain, + signedSaveSwapConfigTxns, + ); + + return saveSwapConfigResponse.txId; + } + }; + + const handleInitSwapConfig = async () => { + setLoading(true); + + const saveSwapTxnId = await signAndSendSaveSwapConfigTxns(proxy); + if (!saveSwapTxnId) { + setLoading(false); + toast.error(TXN_SUBMISSION_FAILED_MESSAGE); + return; + } + + swapConfigsState.retry(); + proxyExistsState.retry(); + + setLoading(false); + }; + return ( setOpen(false)} + disabled={loading} color="secondary" > Cancel - - - + {!proxyExists && !hasSwapConfigurations ? ( + + { + handleInitSwapConfig(); + }} + > + Initialize + + + ) : ( + + + + )} ); diff --git a/src/components/Dialogs/ManageSwapDialog.tsx b/src/components/Dialogs/ManageSwapDialog.tsx index 171073b..994107c 100644 --- a/src/components/Dialogs/ManageSwapDialog.tsx +++ b/src/components/Dialogs/ManageSwapDialog.tsx @@ -266,74 +266,75 @@ const ManageSwapDialog = ({ open, onClose, onShare }: Props) => { const newSwapConfigs = swaps.filter((swapConfig) => { return swapConfig.escrow !== selectedManageSwap.escrow; }); - const cidResponse = await saveSwapConfigurations(newSwapConfigs); - const cidData = await cidResponse.data; + const cidData = await saveSwapConfigurations(newSwapConfigs); - toast.info(`Open your wallet to sign the delete transaction 2 of 2...`); + if (cidData) { + toast.info(`Open your wallet to sign the delete transaction 2 of 2...`); - const saveSwapConfigTxns = await createSaveSwapConfigTxns( - chain, - selectedManageSwap.creator, - proxy, - (await accountExists(chain, proxy.address())) ? 10_000 : 110_000, - cidData, - ); + const saveSwapConfigTxns = await createSaveSwapConfigTxns( + chain, + selectedManageSwap.creator, + proxy, + (await accountExists(chain, proxy.address())) ? 10_000 : 110_000, + cidData, + ); - const signedSaveSwapConfigTxns = await processTransactions( - saveSwapConfigTxns, - signTransactions, - ).catch(() => { - setDeleteLoading(false); - toast.error(TXN_SIGNING_CANCELLED_MESSAGE); - return; - }); + const signedSaveSwapConfigTxns = await processTransactions( + saveSwapConfigTxns, + signTransactions, + ).catch(() => { + setDeleteLoading(false); + toast.error(TXN_SIGNING_CANCELLED_MESSAGE); + return; + }); - if (!signedSaveSwapConfigTxns) { - return; - } + if (!signedSaveSwapConfigTxns) { + return; + } - const saveSwapConfigResponse = await submitTransactions( - chain, - signedSaveSwapConfigTxns, - ); - const saveSwapConfigResponseTxn = saveSwapConfigResponse.txId; - if (!saveSwapConfigResponseTxn) { - setDeleteLoading(false); - toast.error(TXN_SUBMISSION_FAILED_MESSAGE); - return; - } + const saveSwapConfigResponse = await submitTransactions( + chain, + signedSaveSwapConfigTxns, + ); + const saveSwapConfigResponseTxn = saveSwapConfigResponse.txId; + if (!saveSwapConfigResponseTxn) { + setDeleteLoading(false); + toast.error(TXN_SUBMISSION_FAILED_MESSAGE); + return; + } - toast.success( - <> - {`${SWAP_REMOVED_FROM_PROXY_MESSAGE}\n`} - - View on AlgoExplorer - - , - ); + toast.success( + <> + {`${SWAP_REMOVED_FROM_PROXY_MESSAGE}\n`} + + View on AlgoExplorer + + , + ); - toast.success( - <> - {`${SWAP_DEACTIVATION_PERFORMED_MESSAGE}\n`} - - View on AlgoExplorer - - , - ); + toast.success( + <> + {`${SWAP_DEACTIVATION_PERFORMED_MESSAGE}\n`} + + View on AlgoExplorer + + , + ); + } setDeleteLoading(false); }; diff --git a/src/components/Footers/Footer.tsx b/src/components/Footers/Footer.tsx index 2ba2719..9e54164 100644 --- a/src/components/Footers/Footer.tsx +++ b/src/components/Footers/Footer.tsx @@ -98,6 +98,7 @@ const Footer = () => { <> { + const dispatch = useAppDispatch(); + const { activeAddress } = useWallet(); + const router = useRouter(); + + return !activeAddress ? ( + { + dispatch(setIsWalletPopupOpen(true)); + }} + /> + ) : ( + { + if (router.pathname !== `/swaps/my-swaps`) { + router.push(`/swaps/my-swaps`); + } + }} + /> + ); +}; + +export default TelegramFooter; diff --git a/src/components/Layouts/Layout.tsx b/src/components/Layouts/Layout.tsx index 7bfe1b8..8329034 100644 --- a/src/components/Layouts/Layout.tsx +++ b/src/components/Layouts/Layout.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/quotes */ /** * AlgoWorld Swapper * Copyright (C) 2022 AlgoWorld @@ -25,7 +26,13 @@ import { Box } from '@mui/material'; import { useAppDispatch, useAppSelector } from '@/redux/store/hooks'; import LoadingBackdrop from '../Backdrops/Backdrop'; import AboutDialog from '../Dialogs/AboutDialog'; -import { setIsAboutPopupOpen } from '@/redux/slices/applicationSlice'; +import { + setIsAboutPopupOpen, + setIsLoadedFromTelegram, +} from '@/redux/slices/applicationSlice'; +import TelegramFooter from '../Footers/TelegramFooter'; +import axios from 'axios'; +import { useDispatch } from 'react-redux'; type Props = { children?: ReactNode; @@ -42,12 +49,33 @@ const Layout = ({ children, title = `This is the default title` }: Props) => { (state) => state.application.isAboutPopupOpen, ); + const isLoadedFromTelegram = useAppSelector( + (state) => state.application.isLoadedFromTelegram, + ); + + const dispath = useDispatch(); + + React.useEffect(() => { + if (typeof window !== `undefined`) { + if (Object.hasOwnProperty.call(window, `Telegram`)) { + // eslint-disable-next-line react-hooks/exhaustive-deps + axios + .post('/api/storage/validate-hash', { + hash: window.Telegram.WebApp.initData, + }) + .then(() => dispath(setIsLoadedFromTelegram(true))) + .catch(() => { + dispath(setIsLoadedFromTelegram(false)); + }); + } + } + }, [dispath]); + return ( { {children} -