Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
fa5cbf3
feat(widget-checkout): add mock onramp session API server
tomiiide Mar 17, 2026
f634e19
fix(widget-checkout): fix Transak staging API integration
tomiiide Mar 17, 2026
4e69625
build(widget-checkout): configure package for publication
tomiiide Mar 19, 2026
38e6262
feat(widget-checkout): add error simulation and Core proxy toggle
tomiiide Mar 19, 2026
da6d273
feat(widget-checkout): path v1 shell, mui router, playground toggle
tomiiide Mar 20, 2026
0513391
feat(widget-checkout): embed widget runtime and source flow
tomiiide Mar 25, 2026
81b12f7
feat(checkout): integrate Transak deposit flow via onramp session API
tomiiide Mar 25, 2026
dad52a9
feat(widget-checkout): pluggable on-ramp and remove funding picker
tomiiide Mar 25, 2026
cff3316
feat(widget-checkout): add i18n and export deepMerge from widget
tomiiide Mar 25, 2026
72a0ab1
feat(wallet-management): open wallet menu at pre-selected wallet
tomiiide Mar 25, 2026
e879a60
feat(widget-checkout): add error boundary, wallet callbacks, and fixes
tomiiide Mar 25, 2026
b12cd69
refactor(widget-checkout): restructure to match widget conventions
tomiiide Mar 25, 2026
cc439bd
refactor(widget-checkout): drop Checkout prefix, rename Drawer to Modal
tomiiide Mar 25, 2026
b8196b1
refactor(checkout): merge widget-checkout package into @lifi/widget
tomiiide Apr 1, 2026
98c4604
refactor(checkout): simplify config API to use WidgetConfig directly
tomiiide Apr 1, 2026
ae94867
feat(checkout): add onSuccess callback for completed onramp orders
tomiiide Apr 1, 2026
6639d49
refactor(checkout): simplify theme access and migrate applyStyles
tomiiide Apr 1, 2026
26f0ee7
fix(hooks): restore list height on early return in getContentHeight
tomiiide Apr 1, 2026
34c5b92
refactor(checkout): replace hardcoded rgba with CSS variable channels
tomiiide Apr 1, 2026
f60c17d
refactor(checkout): replace hardcoded rgba with CSS variable channels
tomiiide Apr 1, 2026
430e3f3
feat(checkout): add Mesh CEX on-ramp provider
tomiiide Apr 1, 2026
4bf05b8
feat(checkout): add useCheckoutUserId hook and wire userId into Mesh …
tomiiide Apr 2, 2026
29cdbd5
feat(checkout): close Transak modal on route change
tomiiide Apr 2, 2026
4cd1b88
fix(checkout): unblock isolatedDeclarations build and tidy WIP
tomiiide Apr 29, 2026
5a173a8
feat(checkout): redesign deposit UI with checkout-only pages
tomiiide May 5, 2026
aa9e9e6
feat(checkout): migrate session API to /v1/checkout/* endpoints
tomiiide May 5, 2026
0e71e58
chore(checkout): drop unused wallet rows and workspace deps
tomiiide May 5, 2026
ee639ca
refactor(widget): restructure transaction execution UI
tomiiide May 5, 2026
ed3f248
feat(checkout): refresh deposit UI and quote affordances
tomiiide May 6, 2026
d0516d2
feat(checkout): add transaction status and details pages
tomiiide May 6, 2026
bbe85aa
feat(checkout): add transfer deposit flow and frozen quote
tomiiide May 12, 2026
aacd777
feat(checkout): polish transaction UI and add details skeleton
tomiiide May 12, 2026
e1411f1
feat(checkout): redesign deposit flow with error states and search
tomiiide May 12, 2026
5bd1002
chore(checkout): rename playground checkout mode and switch to open prop
tomiiide May 13, 2026
adb8e9b
fix(checkout): scope router/stores per instance and other fixes
tomiiide May 13, 2026
0c8cc34
fix(checkout): harden onramp providers and add unit tests
tomiiide May 13, 2026
94fb210
feat(widget): add @lifi/widget/checkout subpath export
tomiiide May 13, 2026
b24a722
feat(checkout): extract onramp providers into pluggable packages
tomiiide May 13, 2026
c2e3d52
refactor(checkout): unify onramp providers via session registry
tomiiide May 13, 2026
09117d3
refactor(checkout): switch onramp session registry to zustand store
tomiiide May 13, 2026
9a4ff9c
refactor(checkout): clean up public types post-review
tomiiide May 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ next-env.d.ts

.claude/worktrees/
.omc/
.cursor/
1 change: 1 addition & 0 deletions examples/vue/src/vite-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

declare module '*.vue' {
import type { DefineComponent } from 'vue'

// biome-ignore lint/complexity/noBannedTypes: allowed in vue
const component: DefineComponent<{}, {}, any>
export default component
Expand Down
4 changes: 4 additions & 0 deletions knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
"@walletconnect/ethereum-provider",
"porto"
]
},
"packages/widget": {
"ignore": ["src/config/version.ts"],
"ignoreDependencies": ["@transak/ui-js-sdk", "@meshconnect/web-link-sdk"]
}
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"dev:dev": "pnpm --filter widget-playground-vite dev:dev",
"dev:staging": "pnpm --filter widget-playground-vite dev:staging",
"dev:local": "pnpm --filter widget-playground-vite dev:local",
"dev:mock-api": "pnpm --filter @lifi/widget dev:mock-api",
"dev:next": "pnpm --filter widget-playground-next dev",
"release": "pnpm release:version && pnpm release:build && pnpm standard-version -a -s",
"release:alpha": "pnpm release:version --preid alpha && pnpm release:build && pnpm standard-version -a -s --prerelease alpha --skip.changelog",
Expand Down
50 changes: 47 additions & 3 deletions packages/wallet-management/src/components/WalletMenuContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
List,
Typography,
} from '@mui/material'
import { useMemo, useReducer, useRef } from 'react'
import { useEffect, useMemo, useReducer, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { useAccount } from '../hooks/useAccount.js'
import type { CombinedWallet } from '../hooks/useCombinedWallets.js'
Expand Down Expand Up @@ -83,7 +83,19 @@ export const WalletMenuContent: React.FC<WalletMenuContentProps> = ({
.filter(Boolean)
}, [accounts])

const [state, dispatch] = useReducer(reducer, { view: 'wallet-list' })
const openedWithWalletId = Boolean(walletChainArgs?.walletId)

const [state, dispatch] = useReducer(reducer, undefined, (): State => {
const walletId = walletChainArgs?.walletId
if (!walletId) {
return { view: 'wallet-list' }
}
const wallet = installedWallets.find((w) => w.id === walletId)
if (!wallet) {
return { view: 'wallet-list' }
}
return { view: 'multi-ecosystem', selectedWalletId: walletId }
})

const handleMultiEcosystem = (id: string) => {
dispatch({ type: 'SHOW_MULTI_ECOSYSTEM', id })
Expand All @@ -94,7 +106,18 @@ export const WalletMenuContent: React.FC<WalletMenuContentProps> = ({
}

const handleBack = () => {
dispatch({ type: 'SHOW_WALLET_LIST' })
if (openedWithWalletId) {
if (state.view === 'connecting') {
dispatch({
type: 'SHOW_MULTI_ECOSYSTEM',
id: state.selectedWalletId!,
})
} else {
onClose()
}
} else {
dispatch({ type: 'SHOW_WALLET_LIST' })
}
}

const handleError = (id: string, error: any) => {
Expand All @@ -119,6 +142,10 @@ export const WalletMenuContent: React.FC<WalletMenuContentProps> = ({
const targetChainType =
walletChainArgs.chain?.chainType ?? walletChainArgs.chainType

if (!targetChainType) {
return installedWallets
}

return installedWallets
.map((wallet) => {
const filteredConnectors = wallet.connectors.filter(
Expand All @@ -131,6 +158,23 @@ export const WalletMenuContent: React.FC<WalletMenuContentProps> = ({
.filter(Boolean) as typeof installedWallets
}, [installedWallets, walletChainArgs])

const filteredWalletsRef = useRef(filteredWallets)
filteredWalletsRef.current = filteredWallets

useEffect(() => {
const walletId = walletChainArgs?.walletId
if (!walletId) {
dispatch({ type: 'SHOW_WALLET_LIST' })
return
}
const wallet = filteredWalletsRef.current.find((w) => w.id === walletId)
if (!wallet) {
dispatch({ type: 'SHOW_WALLET_LIST' })
return
}
dispatch({ type: 'SHOW_MULTI_ECOSYSTEM', id: walletId })
}, [walletChainArgs?.walletId])

const isMultiEcosystem = state.view === 'multi-ecosystem'
const isConnecting = state.view === 'connecting'

Expand Down
16 changes: 16 additions & 0 deletions packages/wallet-management/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import type {} from '@mui/material/themeCssVarsAugmentation'

export { BitcoinListItemButton } from './components/BitcoinListItemButton.js'
export { CardListItemButton } from './components/CardListItemButton.js'
export { EthereumListItemButton } from './components/EthereumListItemButton.js'
export { SolanaListItemButton } from './components/SolanaListItemButton.js'
export { SuiListItemButton } from './components/SuiListItemButton.js'
export * from './hooks/useAccount.js'
export * from './hooks/useAccountDisconnect.js'
export {
type CombinedWallet,
useCombinedWallets,
} from './hooks/useCombinedWallets.js'
export * from './hooks/useWalletManagementEvents.js'
export * from './icons.js'
export * from './providers/WalletManagementProvider/types.js'
Expand All @@ -10,5 +19,12 @@ export * from './providers/WalletManagementProviders.js'
export type { WalletMenuOpenArgs } from './providers/WalletMenuProvider/types.js'
export * from './providers/WalletMenuProvider/WalletMenuContext.js'
export * from './types/events.js'
export { WalletTagType } from './types/walletTagType.js'
export * from './utils/getConnectorIcon.js'
export { getConnectorId } from './utils/getConnectorId.js'
export { getSortedByTags } from './utils/getSortedByTags.js'
export * from './utils/getWalletPriority.js'
export {
getConnectorTagType,
getWalletTagType,
} from './utils/walletTags.js'
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { ChainType, ExtendedChain } from '@lifi/sdk'
export interface WalletMenuOpenArgs {
chain?: ExtendedChain
chainType?: ChainType
walletId?: string
}

export interface WalletMenuContext {
Expand Down
1 change: 1 addition & 0 deletions packages/widget-playground-next/src/app/AppProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const AppProvider = ({ children }: PropsWithChildren) => {
<EnvVariablesProvider
EVMWalletConnectId={process.env.NEXT_PUBLIC_EVM_WALLET_CONNECT!}
TVMWalletConnectId={process.env.NEXT_PUBLIC_TVM_WALLET_CONNECT!}
onrampSessionApiUrl={process.env.NEXT_PUBLIC_ONRAMP_SESSION_API_URL!}
>
<QueryClientProvider client={queryClient}>
<WidgetConfigProvider defaultWidgetConfig={defaultWidgetConfig}>
Expand Down
1 change: 1 addition & 0 deletions packages/widget-playground-vite/.env
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
VITE_EVM_WALLET_CONNECT=5432e3507d41270bee46b7b85bbc2ef8
VITE_TVM_WALLET_CONNECT=5432e3507d41270bee46b7b85bbc2ef8
VITE_API_URL=https://li.quest/v1
VITE_ONRAMP_SESSION_API_URL=http://localhost:8080
4 changes: 3 additions & 1 deletion packages/widget-playground-vite/.env.staging
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
VITE_API_URL=https://staging.li.quest/v1
# Set VITE_API_KEY in .env.staging.local (git-ignored)
# in .env.dev.local (git-ignored)
# VITE_API_KEY
# VITE_ONRAMP_SESSION_API_URL
15 changes: 15 additions & 0 deletions packages/widget-playground-vite/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,25 @@ import type { JSX, PropsWithChildren } from 'react'
const queryClient = new QueryClient()

const AppProvider = ({ children }: PropsWithChildren) => {
const checkoutToChainRaw = import.meta.env.VITE_CHECKOUT_TO_CHAIN
const checkoutToChain = checkoutToChainRaw
? Number(checkoutToChainRaw)
: undefined

return (
<EnvVariablesProvider
EVMWalletConnectId={import.meta.env.VITE_EVM_WALLET_CONNECT}
TVMWalletConnectId={import.meta.env.VITE_TVM_WALLET_CONNECT}
onrampSessionApiUrl={
import.meta.env.VITE_CHECKOUT_API_BASE_URL ||
import.meta.env.VITE_ONRAMP_SESSION_API_URL ||
undefined
}
checkoutIntegrator={import.meta.env.VITE_CHECKOUT_INTEGRATOR || undefined}
checkoutToChain={
Number.isNaN(checkoutToChain) ? undefined : checkoutToChain
}
checkoutToToken={import.meta.env.VITE_CHECKOUT_TO_TOKEN || undefined}
>
<QueryClientProvider client={queryClient}>
<WidgetConfigProvider defaultWidgetConfig={defaultWidgetConfig}>
Expand Down
2 changes: 2 additions & 0 deletions packages/widget-playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
"@lifi/widget": "workspace:*",
"@lifi/widget-provider-bitcoin": "workspace:*",
"@lifi/widget-provider-ethereum": "workspace:*",
"@lifi/widget-provider-mesh": "workspace:*",
"@lifi/widget-provider-solana": "workspace:*",
"@lifi/widget-provider-sui": "workspace:*",
"@lifi/widget-provider-transak": "workspace:*",
"@lifi/widget-provider-tron": "workspace:*",
"@metamask/connect-evm": "^0.10.0",
"@monaco-editor/react": "^4.7.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { JSX, SyntheticEvent } from 'react'
import type { PlaygroundWidgetMode } from '../../../store/widgetConfig/types.js'
import { useConfigActions } from '../../../store/widgetConfig/useConfigActions.js'
import { usePlaygroundWidgetMode } from '../../../store/widgetConfig/useConfigValues.js'
import { CardValue } from '../../Card/Card.style.js'
import { ExpandableCard } from '../../Card/ExpandableCard.js'
import { Tab, Tabs } from '../../Tabs/Tabs.style.js'

const modeLabels: Record<PlaygroundWidgetMode, string> = {
swap: 'Swap / bridge',
checkout: 'Checkout',
}

export const WidgetModeControl = (): JSX.Element => {
const { playgroundWidgetMode } = usePlaygroundWidgetMode()
const { setPlaygroundWidgetMode } = useConfigActions()

const handleChange = (_: SyntheticEvent, value: PlaygroundWidgetMode) => {
if (value === 'swap' || value === 'checkout') {
setPlaygroundWidgetMode(value)
}
}

return (
<ExpandableCard
title="Widget mode"
value={
<CardValue sx={{ textTransform: 'none' }}>
{modeLabels[playgroundWidgetMode]}
</CardValue>
}
>
<Tabs
value={playgroundWidgetMode}
aria-label="Playground widget mode"
indicatorColor="primary"
onChange={handleChange}
sx={{ mt: 0.5 }}
orientation="vertical"
>
<Tab label={modeLabels.swap} value="swap" disableRipple />
<Tab label={modeLabels.checkout} value="checkout" disableRipple />
</Tabs>
</ExpandableCard>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { ThemeControl } from './DesignControls/ThemeControl.js'
import { VariantControl } from './DesignControls/VariantControl.js'
import { WalletManagementControl } from './DesignControls/WalletManagementControl.js'
import { WidgetEventControls } from './DesignControls/WidgetEventsControls.js'
import { WidgetModeControl } from './DesignControls/WidgetModeControl.js'
import {
Drawer,
DrawerContentContainer,
Expand Down Expand Up @@ -117,6 +118,7 @@ export const DrawerControls = (): JSX.Element => {
>
<ExpandableCardAccordion>
<WidgetConfigControls>
<WidgetModeControl />
<VariantControl />
<SubvariantControl />
<AppearanceControl />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { LifiWidgetCheckout } from '@lifi/widget/checkout'
import { meshProvider } from '@lifi/widget-provider-mesh'
import { transakProvider } from '@lifi/widget-provider-transak'
import { Box, Button, Typography } from '@mui/material'
import { type JSX, useCallback, useMemo, useState } from 'react'
import { widgetBaseConfig } from '../../defaultWidgetConfig.js'
import { useEnvVariables } from '../../providers/EnvVariablesProvider.js'
import { useConfig } from '../../store/widgetConfig/useConfig.js'

// TODO(cleanup-remove-playground-hardcoded-checkout-defaults): Replace these literals with
// explicit env/config contract defaults once migration is complete.
/** Default checkout target for Mesh/Transak demos (native ETH on Ethereum). Playground `config` overrides when set. */
const DEFAULT_CHECKOUT_ONRAMP_TARGET = {
toChain: 1,
toToken: '0x0000000000000000000000000000000000000000',
} as const

const DEFAULT_CHECKOUT_CORE_API_URL = 'https://develop.li.quest'
const DEFAULT_CHECKOUT_INTEGRATOR = 'local-test'

export function CheckoutWidgetView(): JSX.Element {
const { config } = useConfig()
const {
onrampSessionApiUrl,
checkoutIntegrator,
checkoutToChain,
checkoutToToken,
} = useEnvVariables()
const [open, setOpen] = useState(false)

const handleOpen = useCallback(() => {
setOpen(true)
}, [])

const handleClose = useCallback(() => {
setOpen(false)
}, [])

// TODO(cleanup-remove-integrator-override-heuristic): Remove this heuristic comparison against
// widgetBaseConfig.integrator and use a strict precedence contract.
const resolvedIntegrator =
config?.integrator && config.integrator !== widgetBaseConfig.integrator
? config.integrator
: checkoutIntegrator?.trim() || DEFAULT_CHECKOUT_INTEGRATOR

const checkoutConfig = {
...config,
providers: config?.providers ?? widgetBaseConfig.providers,
toChain:
config?.toChain ??
checkoutToChain ??
DEFAULT_CHECKOUT_ONRAMP_TARGET.toChain,
toToken:
config?.toToken ??
checkoutToToken ??
DEFAULT_CHECKOUT_ONRAMP_TARGET.toToken,
}

const onRampProviders = useMemo(() => [transakProvider(), meshProvider()], [])

return (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 2,
p: 3,
flex: 1,
minHeight: 320,
}}
>
<Typography
variant="body2"
color="text.secondary"
sx={{ textAlign: 'center' }}
>
Checkout — opens as a centered widget
</Typography>
<Button variant="contained" onClick={handleOpen}>
Deposit
</Button>
<LifiWidgetCheckout
open={open}
integrator={resolvedIntegrator}
onrampSessionApiUrl={
onrampSessionApiUrl?.trim() || DEFAULT_CHECKOUT_CORE_API_URL
}
config={checkoutConfig}
onRampProviders={onRampProviders}
onClose={handleClose}
/>
</Box>
)
}
11 changes: 11 additions & 0 deletions packages/widget-playground/src/components/Widget/WidgetView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import { useCallback, useEffect, useRef } from 'react'
import { useFormValues } from '../../store/editTools/useFormValues.js'
import { useSkeletonToolValues } from '../../store/editTools/useSkeletonToolValues.js'
import { useConfig } from '../../store/widgetConfig/useConfig.js'
import { usePlaygroundWidgetMode } from '../../store/widgetConfig/useConfigValues.js'
import { CheckoutWidgetView } from './CheckoutWidgetView.js'
import { WidgetViewContainer } from './WidgetViewContainer.js'

export function WidgetView(): JSX.Element {
const { config } = useConfig()
const { playgroundWidgetMode } = usePlaygroundWidgetMode()
const drawerRef = useRef<WidgetDrawer>(null)
const formRef = useRef<FormState>(null)
const { isSkeletonShown, isSkeletonSideBySide } = useSkeletonToolValues()
Expand All @@ -30,6 +33,14 @@ export function WidgetView(): JSX.Element {
}
}, [formValues])

if (playgroundWidgetMode === 'checkout') {
return (
<WidgetViewContainer>
<CheckoutWidgetView />
</WidgetViewContainer>
)
}

return (
<WidgetViewContainer toggleDrawer={toggleDrawer}>
{!isSkeletonShown || isSkeletonSideBySide ? (
Expand Down
Loading