Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/checkout-foundation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lifi/widget-checkout": minor
---

Add the `@lifi/widget-checkout` package scaffolding, providers, stores and the source/amount selection flow (select source, token and cash currency, enter amount, route selection).
123 changes: 123 additions & 0 deletions packages/widget-checkout/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<div align="center">

[![license](https://img.shields.io/github/license/lifinance/widget)](/LICENSE)
[![npm latest package](https://img.shields.io/npm/v/@lifi/widget-checkout/latest.svg)](https://www.npmjs.com/package/@lifi/widget-checkout)

</div>

<h1 align="center">LI.FI Widget Checkout</h1>

[**LI.FI Widget Checkout**](https://docs.li.fi/checkout/overview) (`@lifi/widget-checkout`) is a prebuilt, modal checkout experience built on top of [`@lifi/widget`](https://www.npmjs.com/package/@lifi/widget). It lets users fund a single destination token from whatever they have: a connected wallet, crypto held elsewhere, a centralized exchange account, or cash (card / bank transfer).

Use it when you want a focused "deposit into X" flow (a vault, a game balance, a perps account, …) rather than a general-purpose swap/bridge UI.

## Features

- A single `LifiWidgetCheckout` component you can drive with controlled `open` / `onClose` props or an imperative ref (`open()`, `close()`, `isOpen()`)
- Four funding sources: pay from wallet, transfer crypto, cash, and exchanges
- On-ramp providers shipped as **optional, tree-shakeable packages**: you only bundle what you pass in
- Pending-checkout persistence so an interrupted deposit can resume on the next mount
- Reuses the LI.FI Widget for theming, i18n, wallet management, and SDK execution

## Installation

```sh
pnpm add @lifi/widget-checkout @lifi/widget @tanstack/react-query
```

On-ramp providers are optional and installed separately:

```sh
# Cash (card / bank transfer), powered by Transak
pnpm add @lifi/widget-provider-transak

# Exchange transfers, powered by Mesh
pnpm add @lifi/widget-provider-mesh
```

Each provider SDK stays out of your bundle unless you call its factory and pass it to `onRampProviders`.

## Usage

```tsx
import { LifiWidgetCheckout } from '@lifi/widget-checkout'
import { transakProvider } from '@lifi/widget-provider-transak'
import { meshProvider } from '@lifi/widget-provider-mesh'
import { useState } from 'react'

export const CheckoutButton = () => {
const [open, setOpen] = useState(false)

return (
<>
<button onClick={() => setOpen(true)}>Deposit</button>
<LifiWidgetCheckout
integrator="Your dApp/company name"
open={open}
onClose={() => setOpen(false)}
onRampProviders={[transakProvider(), meshProvider()]}
config={{
toChain: 1,
toToken: '0x...',
apiKey: '<YOUR_LIFI_API_KEY>',
}}
onSuccess={(result) => console.warn('Checkout completed', result)}
onError={(error) => console.error('Checkout failed', error)}
/>
</>
)
}
```

You can also control the modal imperatively via a ref:

```tsx
import { LifiWidgetCheckout, type CheckoutModalRef } from '@lifi/widget-checkout'
import { useRef } from 'react'

export const CheckoutButton = () => {
const ref = useRef<CheckoutModalRef>(null)

return (
<>
<button onClick={() => ref.current?.open()}>Deposit</button>
<LifiWidgetCheckout
ref={ref}
integrator="Your dApp/company name"
config={{
toChain: 1,
toToken: '0x...',
apiKey: '<YOUR_LIFI_API_KEY>',
}}
/>
</>
)
}
```

The ref exposes `open()`, `close()`, and `isOpen()`.

## Key props

| Prop | Type | Description |
| --- | --- | --- |
| `integrator` | `string` | Your integrator id (defaults to `lifi-widget-checkout`). |
| `config` | `Partial<WidgetConfig>` | Widget overrides: destination token (`toChain`/`toToken`, required), `apiKey`, theme, allow/deny lists. |
| `onRampProviders` | `OnRampProvider[]` | On-ramp providers to enable (`transakProvider()`, `meshProvider()`). Defaults to `[]`. |
| `onSuccess` | `(result: CheckoutResult) => void` | Called when a deposit completes. |
| `onError` | `(error: CheckoutError) => void` | Called when a deposit fails. |
| `resumePending` | `boolean` | Persist and resume pending checkouts. Defaults to `true`. |
| `open` / `onClose` | `boolean` / `() => void` | Controlled modal state. |
| `ref` | `Ref<CheckoutModalRef>` | Imperative `isOpen()` / `open()` / `close()`. |

> The cash and exchange funding sources (Transak and Mesh) require `config.apiKey`, generated by creating an integration in the [LI.FI Partner Portal](https://portal.li.fi/). Pay-from-wallet and transfer-crypto need neither.

## Funding sources

- **Pay from wallet** and **Transfer crypto** are built in and need no provider package.
- **Cash** (via Transak) supports cards, Apple Pay, Google Pay, and bank transfers; the exact methods depend on the user's region. See [Cash](https://docs.li.fi/checkout/funding-sources/use-cash).
- **Exchanges** (via Mesh) supports transfers from Coinbase, Binance, Bitfinex, Robinhood, Uphold, and more. See [Exchanges](https://docs.li.fi/checkout/funding-sources/connect-exchange).

## Documentation

- Partner docs: https://docs.li.fi/checkout/overview
81 changes: 81 additions & 0 deletions packages/widget-checkout/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"name": "@lifi/widget-checkout",
"version": "4.0.0-beta.17",
"description": "LI.FI Widget Checkout — on-ramp and cross-chain checkout flow built on @lifi/widget.",
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.d.ts",
"exports": {
".": "./src/index.ts"
},
"sideEffects": false,
"scripts": {
"watch": "tsdown --watch",
"build": "pnpm clean && tsdown",
"build:prerelease": "node ../../scripts/prerelease.js",
"build:postrelease": "node ../../scripts/postrelease.js",
"release:build": "pnpm build",
"clean": "rm -rf dist",
"check:types": "tsc --noEmit",
"check:circular-deps": "madge --circular $(find ./src -name '*.ts' -o -name '*.tsx')",
"check:circular-deps-graph": "madge --circular $(find ./src -name '*.ts' -o -name '*.tsx') --image graph.svg",
"test": "vitest run"
},
"author": "Eugene Chybisov <eugene@li.finance>",
"homepage": "https://github.com/lifinance/widget",
"repository": {
"type": "git",
"url": "https://github.com/lifinance/widget.git",
"directory": "packages/widget-checkout"
},
"bugs": {
"url": "https://github.com/lifinance/widget/issues"
},
"license": "Apache-2.0",
"files": [
"dist",
"src",
"README.md"
],
"keywords": [
"widget",
"lifi-widget",
"checkout",
"onramp",
"fiat",
"cross-chain",
"lifi"
],
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@lifi/sdk": "^4.0.0",
"@lifi/wallet-management": "workspace:*",
"@lifi/widget": "workspace:*",
"@lifi/widget-provider": "workspace:*",
"@lifi/widget-provider-mesh": "workspace:*",
"@mui/icons-material": "^9.0.1",
"@mui/material": "^9.0.1",
"@mui/system": "^9.0.1",
"@tanstack/react-router": "^1.169.2",
"i18next": "^26.1.0",
"react-i18next": "^17.0.7",
"zustand": "^5.0.12"
},
"devDependencies": {
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.1.0",
"@types/node": "^25.5.2",
"happy-dom": "^15.11.7",
"madge": "^8.0.0",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"typescript": "^6.0.2",
"vitest": "^4.1.4"
},
"peerDependencies": {
"@tanstack/react-query": ">=5.90.0",
"react": ">=18",
"react-dom": ">=18"
}
}
27 changes: 27 additions & 0 deletions packages/widget-checkout/src/CheckoutLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
createElementId,
ElementId,
useWidgetConfig,
} from '@lifi/widget/shared'
import { Outlet } from '@tanstack/react-router'
import { CheckoutToastHost } from './components/CheckoutToastHost.js'
import { Container, ExpandedContainer } from './components/Container.js'
import { Header } from './components/Header.js'
import { OnRampHostedModals } from './components/OnRampHostedModals.js'

export const CheckoutLayout: React.FC = () => {
const { elementId } = useWidgetConfig()

return (
<ExpandedContainer
id={createElementId(ElementId.AppExpandedContainer, elementId)}
>
<Container>
<Header />
<Outlet />
</Container>
<OnRampHostedModals />
<CheckoutToastHost />
</ExpandedContainer>
)
}
Loading