diff --git a/cspell.json b/cspell.json
index 52c87c7ab5f..409710d0e12 100644
--- a/cspell.json
+++ b/cspell.json
@@ -255,6 +255,7 @@
"cashtags",
"celo",
"endregion",
+ "frameable",
"Hrefs",
"linkedin",
"luma",
diff --git a/packages/plugins/Transak/src/SiteAdaptor/BuyTokenDialog.tsx b/packages/plugins/Transak/src/SiteAdaptor/BuyTokenDialog.tsx
index 3e66d74e180..df0ab09d37d 100644
--- a/packages/plugins/Transak/src/SiteAdaptor/BuyTokenDialog.tsx
+++ b/packages/plugins/Transak/src/SiteAdaptor/BuyTokenDialog.tsx
@@ -1,8 +1,8 @@
import { InjectedDialog } from '@masknet/shared'
-import { DialogContent, IconButton } from '@mui/material'
-import { makeStyles } from '@masknet/theme'
import { Close as CloseIcon } from '@mui/icons-material'
-import { useTransakURL } from '../hooks/useTransakURL.js'
+import { DialogContent, IconButton, Stack, Typography } from '@mui/material'
+import { LoadingBase, makeStyles } from '@masknet/theme'
+import { useTransakWidgetURL } from '../hooks/useTransakWidgetURL.js'
const useStyles = makeStyles()((theme) => ({
dialogPaper: {
@@ -37,6 +37,11 @@ const useStyles = makeStyles()((theme) => ({
border: 0,
borderRadius: 12,
},
+ status: {
+ height: 630,
+ padding: theme.spacing(2),
+ boxSizing: 'border-box',
+ },
}))
interface BuyTokenDialogProps extends withClasses<'root'> {
@@ -50,7 +55,11 @@ export function BuyTokenDialog(props: BuyTokenDialogProps) {
const { classes } = useStyles(undefined, { props })
const { code, address, open, onClose } = props
- const transakURL = useTransakURL({
+ const {
+ data: widgetUrl,
+ isLoading: loading,
+ error,
+ } = useTransakWidgetURL({
defaultCryptoCurrency: code,
walletAddress: address,
})
@@ -69,10 +78,18 @@ export function BuyTokenDialog(props: BuyTokenDialogProps) {
- {transakURL ?
+ {loading ?
+
+
+
+ : error || !widgetUrl ?
+
+
+ Transak is temporarily unavailable. Please try again later.
+
+
// eslint-disable-next-line react/dom/no-missing-iframe-sandbox
-
- : null}
+ : }
diff --git a/packages/plugins/Transak/src/constants.ts b/packages/plugins/Transak/src/constants.ts
index 81abfce878d..56c48b7b44d 100644
--- a/packages/plugins/Transak/src/constants.ts
+++ b/packages/plugins/Transak/src/constants.ts
@@ -3,6 +3,9 @@ import { PluginID } from '@masknet/shared-base'
export const TRANSAK_API_KEY_PRODUCTION = '253be1f0-c6d8-46e7-9d80-38f33bf973e2'
export const TRANSAK_API_KEY_STAGING = '4fcd6904-706b-4aff-bd9d-77422813bbb7'
+// Worker that mints a Transak session widgetUrl (the bare apiKey URL is no longer frameable).
+export const TRANSAK_PROXY_HOST = 'https://transak-proxy.r2d2.to'
+
export const PLUGIN_ID = PluginID.Transak
export const PLUGIN_NAME = 'Transak'
export const PLUGIN_DESCRIPTION = 'The Fiat On-Ramp Aggregator on Mask Network.'
diff --git a/packages/plugins/Transak/src/hooks/useTransakURL.ts b/packages/plugins/Transak/src/hooks/useTransakURL.ts
index 187ad836875..56cf5c22661 100644
--- a/packages/plugins/Transak/src/hooks/useTransakURL.ts
+++ b/packages/plugins/Transak/src/hooks/useTransakURL.ts
@@ -1,5 +1,5 @@
import { useMemo } from 'react'
-import { rgbToHex, useTheme } from '@mui/material'
+import { rgbToHex, useTheme, type Theme } from '@mui/material'
import { TRANSAK_API_KEY_PRODUCTION, TRANSAK_API_KEY_STAGING } from '../constants.js'
import { formatEthereumAddress } from '@masknet/web3-shared-evm'
import type { TransakConfig } from '../types.js'
@@ -23,20 +23,30 @@ const DEFAULT_PARAMETERS: TransakConfig = {
excludeFiatCurrencies: 'KRW',
}
+// Query params shared by the legacy direct URL and the session proxy.
+export function buildTransakSearchParams(config?: Partial, theme?: Theme): URLSearchParams {
+ const config_: TransakConfig = {
+ ...DEFAULT_PARAMETERS,
+ referrerDomain: location.origin,
+ themeColor: theme ? rgbToHex(theme.palette.maskColor.dark).slice(1) : undefined,
+ exchangeScreenTitle:
+ config?.walletAddress ? `Buy Crypto to ${formatEthereumAddress(config.walletAddress, 4)}` : undefined,
+ ...config,
+ }
+ const params = new URLSearchParams()
+ Object.entries(config_).forEach(([key, value]) => {
+ if (value === undefined || value === null) return
+ params.append(key, String(value))
+ })
+ return params
+}
+
export function useTransakURL(config?: Partial) {
const theme = useTheme()
- const search = useMemo(() => {
- const config_: TransakConfig = {
- ...DEFAULT_PARAMETERS,
- themeColor: rgbToHex(theme.palette.maskColor.dark).slice(1),
- exchangeScreenTitle:
- config?.walletAddress ? `Buy Crypto to ${formatEthereumAddress(config.walletAddress, 4)}` : void 0,
- ...config,
- }
- const params = new URLSearchParams()
- Object.entries(config_).forEach(([key, value = '']) => params.append(key, String(value)))
- return params.toString()
+ const search = useMemo(
+ () => buildTransakSearchParams(config, theme).toString(),
// eslint-disable-next-line react-compiler/react-compiler
- }, [theme.palette.primary.main, JSON.stringify(config)])
+ [theme.palette.primary.main, JSON.stringify(config)],
+ )
return `${HOST_MAP[process.env.NODE_ENV]}?${search}`
}
diff --git a/packages/plugins/Transak/src/hooks/useTransakWidgetURL.ts b/packages/plugins/Transak/src/hooks/useTransakWidgetURL.ts
new file mode 100644
index 00000000000..d4986b3e47a
--- /dev/null
+++ b/packages/plugins/Transak/src/hooks/useTransakWidgetURL.ts
@@ -0,0 +1,30 @@
+import { useMemo } from 'react'
+import { useTheme } from '@mui/material'
+import { useQuery } from '@tanstack/react-query'
+import { TRANSAK_PROXY_HOST } from '../constants.js'
+import type { TransakConfig } from '../types.js'
+import { buildTransakSearchParams } from './useTransakURL.js'
+
+// Fetches a single-use, iframe-safe widgetUrl from the session proxy.
+export function useTransakWidgetURL(config?: Partial) {
+ const theme = useTheme()
+ const url = useMemo(
+ () => `${TRANSAK_PROXY_HOST}?${buildTransakSearchParams(config, theme).toString()}`,
+ // eslint-disable-next-line react-compiler/react-compiler
+ [theme.palette.primary.main, JSON.stringify(config)],
+ )
+ return useQuery({
+ queryKey: ['transak', 'widget-url', url],
+ queryFn: async () => {
+ const res = await fetch(url)
+ if (!res.ok) throw new Error('Failed to create a Transak session')
+ const json = (await res.json()) as { data?: { widgetUrl?: string } }
+ const widgetUrl = json.data?.widgetUrl
+ if (!widgetUrl) throw new Error('Transak session did not return a widget URL')
+ return widgetUrl
+ },
+ // widgetUrl is single-use (one sessionId, 5 min) — fetch once per open, never refetch or reuse.
+ staleTime: Infinity,
+ gcTime: 0,
+ })
+}
diff --git a/packages/plugins/Transak/src/index.ts b/packages/plugins/Transak/src/index.ts
index 32e1bfa4460..20b28c4ef4d 100644
--- a/packages/plugins/Transak/src/index.ts
+++ b/packages/plugins/Transak/src/index.ts
@@ -1,3 +1,4 @@
export { PluginTransakMessages } from './messages.js'
export { useTransakAllowanceCoin } from './hooks/useTransakAllowanceCoin.js'
export { useTransakURL } from './hooks/useTransakURL.js'
+export { useTransakWidgetURL } from './hooks/useTransakWidgetURL.js'
diff --git a/packages/plugins/Transak/src/types.ts b/packages/plugins/Transak/src/types.ts
index 0cbaa6ead37..8faedcbf5e1 100644
--- a/packages/plugins/Transak/src/types.ts
+++ b/packages/plugins/Transak/src/types.ts
@@ -6,6 +6,7 @@ export interface TransakConfig {
defaultFiatAmount?: number
defaultCryptoCurrency?: string
walletAddress?: string
+ referrerDomain?: string
themeColor?: string
countryCode?: string
fiatCurrency?: string