# @uniswap-widget/react [![npm version](https://img.shields.io/npm/v/@uniswap-widget/react.svg)](https://www.npmjs.com/package/@uniswap-widget/react) [![npm downloads](https://img.shields.io/npm/dm/@uniswap-widget/react.svg)](https://www.npmjs.com/package/@uniswap-widget/react) [![license](https://img.shields.io/npm/l/@uniswap-widget/react.svg)](https://github.com/houtanrocky/uniswap-widget/blob/main/LICENSE) image A React component package for easily integrating Uniswap swap functionality into your dApp with maximum dev flexibility no token limitations, no warnings, and no added fee. > Looking for a runnable end-to-end setup? See [`examples/basic`](https://github.com/houtanrocky/uniswap-widget/tree/main/examples/basic) in the monorepo. ## Installation The widget keeps its heavy dependencies as **peer dependencies**, so you install them alongside it (and stay in control of their versions): ```bash pnpm add @uniswap-widget/react \ @reown/appkit @reown/appkit-adapter-wagmi wagmi viem \ @tanstack/react-query ethers \ @uniswap/sdk-core @uniswap/v3-sdk @uniswap/v3-core # or: npm install … / yarn add … ``` > Using a different wallet library? `@reown/appkit` + `@reown/appkit-adapter-wagmi` are **optional** — see [Wallet adapters](#wallet-adapters-plugin). ## Host-app requirements The widget makes three assumptions about the app embedding it. Satisfy all three or it won't render/behave correctly. (These are tracked for removal see the [decoupling spec](https://github.com/houtanrocky/uniswap-widget/blob/main/docs/0004-core-deps-decoupling.md).) ### 1. Tailwind CSS The widget is styled with **Tailwind utility classes** and ships **no CSS of its own**. Your app must run Tailwind and include the package in its `content` globs so those classes are generated: ```js // tailwind.config.js export default { content: [ './src/**/*.{js,ts,jsx,tsx}', './node_modules/@uniswap-widget/react/dist/*.js', // generate the widget's classes ], } ``` ### 2. API proxy The widget calls these **relative** paths, which your app must proxy: | Path | Method | Purpose | Required when | |------|--------|---------|---------------| | `/api/base-rpc` | POST | Base-chain JSON-RPC reads (balances, pool reserves, quotes) | Always | | `/api/uniswap/v2/Search.v1.SearchService/SearchTokens` | POST | Token search | `searchConfig.enabled` is `true` | See [`examples/basic/vite.config.ts`](https://github.com/houtanrocky/uniswap-widget/blob/main/examples/basic/vite.config.ts) (dev proxy) and [`examples/basic/api`](https://github.com/houtanrocky/uniswap-widget/tree/main/examples/basic/api) (Vercel functions) for a working reference. ### 3. Vite-style env Configuration is read from `import.meta.env.VITE_*` (with sensible fallbacks), so the widget currently expects a **Vite** host. See [Environment Variables](#environment-variables). ## Configuration ### 1. WalletConnect Project ID Get your WalletConnect v2 Project ID at https://cloud.walletconnect.com/ ### 2. Provider setup Wrap your app with the `Provider`, passing a configured `WagmiAdapter`: ```tsx import { Provider, createAppKit, WagmiAdapter, base } from '@uniswap-widget/react'; import { QueryClient } from '@tanstack/react-query'; const projectId = 'your_project_id'; const queryClient = new QueryClient(); const wagmiAdapter = new WagmiAdapter({ projectId, networks: [base], ssr: true, }); createAppKit({ adapters: [wagmiAdapter], networks: [base], projectId, metadata: { name: 'Your App Name', description: 'Your app description', url: 'https://your-domain.com', icons: ['https://your-icon-url.com'], }, features: { analytics: true, email: false, socials: [], allWallets: true, emailShowWallets: true, swaps: false, }, }); export default function App({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ### Provider props | Prop | Type | Required | Description | |------|------|----------|-------------| | `wagmiAdapter` | `WagmiAdapter` | Yes | Configured `WagmiAdapter` instance | | `queryClient` | `QueryClient` | No | React Query client (defaults to a new `QueryClient`) | | `walletAdapter` | `WalletAdapter` | No | Wallet integration (defaults to the Reown adapter). See [Wallet adapters](#wallet-adapters-plugin) | | `children` | `ReactNode` | Yes | Child components | ## Wallet adapters (plugin) The widget is **wallet-agnostic** — it reads the connected account and an ethers `Signer` from a small adapter context, so Reown AppKit is just the default. Plug in any wallet library: - **Default (Reown AppKit):** use `` as shown above — nothing to do. - **Another wallet library:** pass a `walletAdapter` to ``, or skip `` and wrap `` in `` inside your own wallet setup. A `WalletAdapter` is a React hook returning a `WalletConnection`: ```ts interface WalletConnection { isConnected: boolean; address?: string; signer?: ethers.Signer; // the only thing the swap logic needs connect: () => void | Promise; disconnect?: () => void | Promise; AccountButton?: React.ComponentType; // optional account UI } type WalletAdapter = () => WalletConnection; ``` ### Built-in: injected wallet (no Reown, no wagmi) `useInjectedWalletAdapter` talks to `window.ethereum` (MetaMask, Rabby, Coinbase Wallet, …) using only `ethers` — with this path `@reown/appkit*` aren't needed (they're optional peers): ```tsx import { SwapWidget, WalletAdapterProvider, useInjectedWalletAdapter, } from '@uniswap-widget/react'; export default function App() { return ( ); } ``` ### Writing your own ```tsx import { useAccount, useWalletClient } from 'wagmi'; import { ethers } from 'ethers'; import type { WalletAdapter } from '@uniswap-widget/react'; // Adapt RainbowKit / ConnectKit / your own wagmi connect button, etc.: const useMyAdapter: WalletAdapter = () => { const { isConnected, address } = useAccount(); const { data: walletClient } = useWalletClient(); const signer = walletClient ? new ethers.providers.Web3Provider(walletClient.transport).getSigner( walletClient.account.address, ) : undefined; return { isConnected, address, signer, connect: () => {/* open your modal */} }; }; ``` ## Usage ```tsx import { SwapWidget } from '@uniswap-widget/react'; export default function SwapPage() { const handleSwap = async (inputAmount: string, outputAmount: string) => { console.log('Swap:', { inputAmount, outputAmount }); // Add your post-swap logic here }; return ( ); } ``` ### SwapWidget props (`SwapProps`) | Prop | Type | Required | Description | |------|------|----------|-------------| | `poolConfig` | `PoolConfig` | No | The pool to trade against (see below) | | `theme` | `Partial` | No | Override colors/spacing. `lightTheme` / `darkTheme` are exported | | `allowTokenChange` | `boolean` | No | Allow the user to switch tokens | | `onTokenSelect` | `(type: 'input' \| 'output', token: TokenInfo) => void` | No | Fired on token selection | | `onAmountChange` | `(amount: string, type: 'input' \| 'output') => void` | No | Fired on amount change | | `onSwap` | `(inputAmount: string, outputAmount: string) => Promise` | No | Called after a successful swap | | `customTokenList` | `TokenInfo[]` | No | Restrict selectable tokens to this list | | `searchConfig` | `{ enabled: boolean; chainIds?: number[] }` | No | Enable token search (requires the search proxy) | ### `PoolConfig` | Field | Type | Description | |-------|------|-------------| | `tokenIn` / `tokenOut` | `TokenInfo` | The traded tokens (`chainId`, `address`, `decimals`, `symbol`, `name`, `logoURI`) | | `poolAddress` | `string` | The pool/pair address | | `version` | `'V2' \| 'V3'` | Pool type | | `fee` | `number` | V3 fee tier (e.g. `500`, `3000`, `10000`) | ## Exports - **Components:** `SwapWidget`, `Provider` (+ `ProviderProps`) - **Wallet plugin:** `WalletAdapterProvider`, `useWallet`, `useReownWalletAdapter`, `useInjectedWalletAdapter`, and types `WalletConnection`, `WalletAdapter` - **AppKit/wagmi re-exports:** `createAppKit`, `useAppKit`, `WagmiAdapter`, `CreateConnectorFn` - **Networks:** `base`, `mainnet`, `polygon`, `optimism`, `arbitrum`, `avalanche`, `fantom`, `moonbeam`, `solana` - **Themes:** `lightTheme`, `darkTheme` - **Constants:** `VIRTUAL_PROTOCOL_TOKEN`, `DEFAULT_SLIPPAGE`, `DEFAULT_DEADLINE_MINUTES`, `VritualProtocolTokenInfo`, `SolaceTokenInfo` - **Types:** `SwapProps`, `ThemeConfig`, `TokenInfo`, `PoolConfig`, `SwapState`, `AppKitNetwork`, `AppKitFeatures`, `AppKitMetadata` ## Requirements - React 18 or higher - A Vite-based host app (for `import.meta.env`) - A valid WalletConnect v2 Project ID - The API proxy described above ## Environment Variables ```env VITE_REOWN_PROJECT_ID=your_project_id VITE_APP_NAME=Your App Name VITE_APP_DESCRIPTION=Your app description VITE_APP_URL=https://your-domain.com VITE_APP_ICON=https://your-icon-url.com # Optional token-change controls (default: true) VITE_ALLOW_SELL_TOKEN_CHANGE=true VITE_ALLOW_BUY_TOKEN_CHANGE=true ``` ## License MIT