diff --git a/.github/workflows/build-extension.yaml b/.github/workflows/build-extension.yaml index 738b13324..e9556a020 100644 --- a/.github/workflows/build-extension.yaml +++ b/.github/workflows/build-extension.yaml @@ -28,6 +28,7 @@ jobs: - name: Run build uses: borales/actions-yarn@v5 env: + CI: false REACT_APP_AMPLITUDE: ${{ secrets.REACT_APP_AMPLITUDE_EXTENSION }} REACT_APP_APTABASE: ${{ secrets.VITE_APP_APTABASE }} REACT_APP_APTABASE_HOST: https://anonymous-analytics.tonkeeper.com diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 587740397..d8f639eb8 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -159,6 +159,7 @@ jobs: - name: Run build uses: borales/actions-yarn@v5 env: + CI: false REACT_APP_AMPLITUDE: ${{ secrets.REACT_APP_AMPLITUDE_EXTENSION }} REACT_APP_APTABASE: ${{ secrets.VITE_APP_APTABASE }} REACT_APP_APTABASE_HOST: https://anonymous-analytics.tonkeeper.com diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index a8a3137cd..d08d544f1 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -202,6 +202,7 @@ jobs: - name: Run build uses: borales/actions-yarn@v5 env: + CI: false REACT_APP_AMPLITUDE: ${{ secrets.REACT_APP_AMPLITUDE_EXTENSION }} REACT_APP_APTABASE: ${{ secrets.VITE_APP_APTABASE }} REACT_APP_APTABASE_HOST: https://anonymous-analytics.tonkeeper.com diff --git a/apps/desktop/src/app/App.tsx b/apps/desktop/src/app/App.tsx index 3d32e51f5..0d0e55a35 100644 --- a/apps/desktop/src/app/App.tsx +++ b/apps/desktop/src/app/App.tsx @@ -261,12 +261,13 @@ export const Loader: FC = () => { const { data: auth } = useAuthState(); const { data: fiat } = useUserFiat(); - const tonendpoint = useTonendpoint( - TARGET_ENV, - sdk.version, - activeWallet?.network, - activeWallet?.lang - ); + const tonendpoint = useTonendpoint({ + targetEnv: TARGET_ENV, + build: sdk.version, + network: activeWallet?.network, + lang: activeWallet?.lang, + platform: 'desktop' + }); const { data: config } = useTonenpointConfig(tonendpoint); const navigate = useNavigate(); diff --git a/apps/desktop/src/electron/mainWindow.ts b/apps/desktop/src/electron/mainWindow.ts index 5e56ccec6..f8a3a065a 100644 --- a/apps/desktop/src/electron/mainWindow.ts +++ b/apps/desktop/src/electron/mainWindow.ts @@ -116,6 +116,19 @@ export abstract class MainWindow { }); }); + this.mainWindow.webContents.session.webRequest.onHeadersReceived((details, callback) => { + /* patch mercuryo cors */ + if (details.url.startsWith('https://api.mercuryo.io')) { + const corsHeader = + Object.keys(details.responseHeaders).find( + k => k.toLowerCase() === 'access-control-allow-origin' + ) || 'access-control-allow-origin'; + details.responseHeaders[corsHeader] = ['*']; + } + + callback(details); + }); + this.mainWindow.webContents.session.on('select-hid-device', (event, details, callback) => { event.preventDefault(); if (details.deviceList && details.deviceList.length > 0) { diff --git a/apps/extension/src/App.tsx b/apps/extension/src/App.tsx index 19f2fa903..620a0e564 100644 --- a/apps/extension/src/App.tsx +++ b/apps/extension/src/App.tsx @@ -1,5 +1,5 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { localizationFrom } from '@tonkeeper/core/dist/entries/language'; +import { Language, localizationFrom } from "@tonkeeper/core/dist/entries/language"; import { Network, getApiConfig } from '@tonkeeper/core/dist/entries/network'; import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; import { InnerBody, useWindowsScroll } from '@tonkeeper/uikit/dist/components/Body'; @@ -55,6 +55,7 @@ import { connectToBackground } from './event'; import { ExtensionAppSdk } from './libs/appSdk'; import { useAnalytics, useAppWidth } from './libs/hooks'; import { TonConnectSubscription } from "./components/TonConnectSubscription"; +import { TargetEnv } from "@tonkeeper/core/dist/AppSdk"; const ImportRouter = React.lazy(() => import('@tonkeeper/uikit/dist/pages/import')); const Settings = React.lazy(() => import('@tonkeeper/uikit/dist/pages/settings')); @@ -179,12 +180,12 @@ export const Loader: FC = React.memo(() => { const lock = useLock(sdk); const { data: account } = useAccountState(); const { data: auth } = useAuthState(); - const tonendpoint = useTonendpoint( - TARGET_ENV, - sdk.version, - activeWallet?.network, - localizationFrom(browser.i18n.getUILanguage()) - ); + const tonendpoint = useTonendpoint({ + targetEnv: TARGET_ENV, + build: sdk.version, + network: activeWallet?.network, + lang: localizationFrom(browser.i18n.getUILanguage()) + }); const { data: config } = useTonenpointConfig(tonendpoint); const { data: tracker } = useAnalytics(sdk.storage, account, activeWallet, sdk.version); diff --git a/apps/twa/src/App.tsx b/apps/twa/src/App.tsx index 92fd9c94f..a7dea9845 100644 --- a/apps/twa/src/App.tsx +++ b/apps/twa/src/App.tsx @@ -219,11 +219,12 @@ export const Loader: FC<{ sdk: IAppSdk }> = ({ sdk }) => { const { data: account } = useAccountState(); const { data: auth } = useAuthState(); - const tonendpoint = useTonendpoint( - TARGET_ENV, - sdk.version, - activeWallet?.network, - activeWallet?.lang + const tonendpoint = useTonendpoint({ + targetEnv: TARGET_ENV, + build: sdk.version, + network: activeWallet?.network, + lang: activeWallet?.lang +} ); const { data: config } = useTonenpointConfig(tonendpoint); diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index ba8f48f1c..202cd3392 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -190,12 +190,12 @@ export const Loader: FC = () => { const { data: account } = useAccountState(); const { data: auth } = useAuthState(); - const tonendpoint = useTonendpoint( - TARGET_ENV, - sdk.version, - activeWallet?.network, - activeWallet?.lang - ); + const tonendpoint = useTonendpoint({ + targetEnv: TARGET_ENV, + build: sdk.version, + network: activeWallet?.network, + lang: activeWallet?.lang + }); const { data: config } = useTonenpointConfig(tonendpoint); const navigate = useNavigate(); diff --git a/packages/core/src/tonkeeperApi/tonendpoint.ts b/packages/core/src/tonkeeperApi/tonendpoint.ts index 35c4d6c7b..7720c4aae 100644 --- a/packages/core/src/tonkeeperApi/tonendpoint.ts +++ b/packages/core/src/tonkeeperApi/tonendpoint.ts @@ -4,8 +4,8 @@ import { Network } from '../entries/network'; import { DAppTrack } from '../service/urlService'; import { FetchAPI } from '../tonApiV2'; -interface BootParams { - platform: 'ios' | 'android' | 'web'; +export interface BootParams { + platform: 'ios' | 'android' | 'web' | 'desktop'; lang: 'en' | 'ru' | string; build: string; // "2.8.0" network: Network; @@ -50,6 +50,8 @@ export interface TonendpointConfig { web_swaps_url?: string; web_swaps_referral_address?: string; + mercuryo_otc_id?: string; + /** * @deprecated use ton api */ diff --git a/packages/uikit/src/components/home/BuyItemNotification.tsx b/packages/uikit/src/components/home/BuyItemNotification.tsx index 4e21ee0bb..f5eab8ea1 100644 --- a/packages/uikit/src/components/home/BuyItemNotification.tsx +++ b/packages/uikit/src/components/home/BuyItemNotification.tsx @@ -22,6 +22,7 @@ import { Notification } from '../Notification'; import { Body1, H3, Label1 } from '../Text'; import { Button } from '../fields/Button'; import { Checkbox } from '../fields/Checkbox'; +import { useCreateMercuryoProUrl } from '../../state/tonendpoint'; const Logo = styled.img<{ large?: boolean }>` pointer-events: none; @@ -179,6 +180,29 @@ const replacePlaceholders = ( return url; }; +const Label1Styled = styled(Label1)` + display: flex; + align-items: center; + gap: 6px; +`; + +const H3Styled = styled(H3)` + display: flex; + align-items: center; + gap: 6px; +`; + +const Badge = styled.div` + background: ${p => p.theme.backgroundContentTint}; + border-radius: 3px; + padding: 2px 4px; + color: ${p => p.theme.textSecondary}; + font-size: 8.5px; + font-style: normal; + font-weight: 510; + line-height: 12px; +`; + export const BuyItemNotification: FC<{ item: TonendpoinFiatItem; kind: 'buy' | 'sell'; @@ -192,10 +216,16 @@ export const BuyItemNotification: FC<{ const { data: hided } = useShowDisclaimer(item.title, kind); const { mutate } = useHideDisclaimerMutation(item.title, kind); + const { mutateAsync: createMercuryoProUrl } = useCreateMercuryoProUrl(); - const onForceOpen = () => { + const onForceOpen = async () => { track(item.action_button.url); - sdk.openPage(replacePlaceholders(item.action_button.url, config, wallet, fiat, kind)); + + let urlToOpen = item.action_button.url; + if (item.id === 'mercuryo_pro') { + urlToOpen = await createMercuryoProUrl(item.action_button.url); + } + sdk.openPage(replacePlaceholders(urlToOpen, config, wallet, fiat, kind)); setOpen(false); }; const onOpen: React.MouseEventHandler = () => { @@ -213,7 +243,10 @@ export const BuyItemNotification: FC<{ - {item.title} + + {item.title} + {item.badge && {item.badge}} + {item.description} @@ -226,7 +259,10 @@ export const BuyItemNotification: FC<{ {() => ( -

{item.title}

+ + {item.title} + {item.badge && {item.badge}} +
{item.description}
diff --git a/packages/uikit/src/state/tonendpoint.ts b/packages/uikit/src/state/tonendpoint.ts index fcdcc1a9b..068466849 100644 --- a/packages/uikit/src/state/tonendpoint.ts +++ b/packages/uikit/src/state/tonendpoint.ts @@ -1,4 +1,4 @@ -import { useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery } from '@tanstack/react-query'; import { Language, localizationText } from '@tonkeeper/core/dist/entries/language'; import { Network } from '@tonkeeper/core/dist/entries/network'; import { @@ -6,7 +6,8 @@ import { TonendpoinFiatItem, Tonendpoint, TonendpointConfig, - getServerConfig + getServerConfig, + BootParams } from '@tonkeeper/core/dist/tonkeeperApi/tonendpoint'; import { useMemo } from 'react'; import { useAppContext } from '../hooks/appContext'; @@ -14,15 +15,25 @@ import { QueryKey, TonkeeperApiKey } from '../libs/queryKey'; import { useUserCountry } from './country'; import { TargetEnv } from '@tonkeeper/core/dist/AppSdk'; -export const useTonendpoint = ( - targetEnv: TargetEnv, - build: string, - network?: Network, - lang?: Language -) => { +export const useTonendpoint = (options: { + targetEnv: TargetEnv; + build: string; + network?: Network; + lang?: Language; + platform?: BootParams['platform']; +}) => { return useMemo(() => { - return new Tonendpoint({ build, network, lang: localizationText(lang), targetEnv }, {}); - }, [targetEnv, build, network, lang]); + return new Tonendpoint( + { + build: options.build, + network: options.network, + lang: localizationText(options.lang), + targetEnv: options.targetEnv, + platform: options.platform + }, + {} + ); + }, [options.targetEnv, options.build, options.network, options.lang, options.platform]); }; export const useTonenpointConfig = (tonendpoint: Tonendpoint) => { @@ -64,3 +75,32 @@ export const useTonendpointBuyMethods = () => { } ); }; + +export const useCreateMercuryoProUrl = () => { + const { tonendpoint } = useAppContext(); + const { data } = useTonenpointConfig(tonendpoint); + + return useMutation(async baseUrl => { + try { + if (!data?.mercuryo_otc_id) { + throw new Error('Missing mercuryo get otc url'); + } + const mercurioConfig = (await (await fetch(data.mercuryo_otc_id)).json()) as { + data: { + otc_id: string; + }; + }; + + if (!mercurioConfig.data.otc_id) { + throw new Error('Missing mercuryo otc_id'); + } + + const url = new URL(baseUrl); + url.searchParams.append('otc_id', mercurioConfig.data.otc_id); + return url.toString(); + } catch (e) { + console.error(e); + return baseUrl; + } + }); +};