From c4b69864ae45da036c3453271a3d4403bb55c2cb Mon Sep 17 00:00:00 2001 From: Enes Date: Wed, 8 Jul 2026 14:02:06 +0300 Subject: [PATCH 1/3] docs(headless-sdk): refactor implementation for zero-config AppKit Wallet setup is now zero-config: the SDK owns the entire Reown AppKit wiring, so a host installs only @walletconnect/pay-* and never touches @reown/*, wagmi, or viem. Reflects buyer-experience PRs #956 + #972. - implementation: replace manual createAppKit + Wagmi/Query provider wiring with (React) / createPayAppKit (JS); drop the hand-written signer for the built-in createAppKitSigner; show the AppKit setup and checkout assembly as React/JavaScript CodeGroups. - packages-reference: document createPayAppKit, createAppKitSigner, SUPPORTED_NETWORKS, and the /react PayAppKitProvider / usePayAppKit / getPayAppKitInstance exports. - how-it-works: point the Signer seam at createAppKitSigner. Co-Authored-By: Claude Opus 4.8 (1M context) --- payments/psps/headless-sdk/how-it-works.mdx | 4 +- payments/psps/headless-sdk/implementation.mdx | 315 +++++++----------- .../psps/headless-sdk/packages-reference.mdx | 25 +- 3 files changed, 150 insertions(+), 194 deletions(-) diff --git a/payments/psps/headless-sdk/how-it-works.mdx b/payments/psps/headless-sdk/how-it-works.mdx index 48986ee..b0fcae2 100644 --- a/payments/psps/headless-sdk/how-it-works.mdx +++ b/payments/psps/headless-sdk/how-it-works.mdx @@ -69,12 +69,12 @@ The five seams the runtime depends on: | ---------------- | ------------------------------------------ | ---------------------------------------------------- | | `Transport` | Engine HTTP calls | `pay-core` `createHttpTransport` → your server route | | `WalletProvider` | connect / accounts / provider / switch | `pay-appkit`, or your own wallet integration | -| `Signer` | sign a payment option's wallet-RPC actions | `pay-state` signing strategies over the wallet seam | +| `Signer` | sign a payment option's wallet-RPC actions | `pay-appkit` `createAppKitSigner`, or `pay-state` strategies for a custom wallet | | `Clock` | intervals + page visibility (for polling) | browser timers (the SDK ships a default) | | `Telemetry` | analytics breadcrumbs | your analytics pipeline (optional) | - `@walletconnect/pay-state` ships browser-ready `Clock` and `Telemetry` defaults (`browserClock`, `noopTelemetry`). In practice a React/Next.js gateway wires three seams itself — a `Transport` (pointed at your server route), a `WalletProvider` (the AppKit adapter), and a `Signer` (built from the wallet) — and reuses `browserClock` for the rest. `Telemetry` is optional. + `@walletconnect/pay-state` ships browser-ready `Clock` and `Telemetry` defaults (`browserClock`, `noopTelemetry`). In practice a React/Next.js gateway wires just the `Transport` (pointed at your server route); the `WalletProvider` and `Signer` come ready-made from `pay-appkit` (`` + `createAppKitSigner`), and it reuses `browserClock` for the rest. `Telemetry` is optional. ## The Engine API key never reaches the browser diff --git a/payments/psps/headless-sdk/implementation.mdx b/payments/psps/headless-sdk/implementation.mdx index 74f08cc..1376af2 100644 --- a/payments/psps/headless-sdk/implementation.mdx +++ b/payments/psps/headless-sdk/implementation.mdx @@ -3,19 +3,20 @@ title: "Implementation" sidebarTitle: "Implementation" metatags: - description: "Build a branded WalletConnect Pay checkout with the @walletconnect/pay-* Headless SDK — a step-by-step React / Next.js walkthrough: server proxy, transport, AppKit, wallet seam, signer, and rendering the snapshot." + description: "Build a branded WalletConnect Pay checkout with the @walletconnect/pay-* Headless SDK. A step-by-step walkthrough — server proxy, transport, zero-config AppKit, and rendering the snapshot — with React and JavaScript examples side by side." --- -This page walks through building a complete checkout in **React / Next.js** on the Headless SDK. New to the SDK? Read [How it works](/payments/psps/headless-sdk/how-it-works) first for the architecture and the role of each seam. +This page walks through building a complete checkout on the Headless SDK, with **React / Next.js** and **JavaScript** examples side by side. New to the SDK? Read [How it works](/payments/psps/headless-sdk/how-it-works) first for the architecture and the role of each seam. -You build four things, all shown below: +Wallet connection is now **zero-config**: the SDK owns the entire Reown AppKit setup. You install only the `@walletconnect/pay-*` packages and never touch `@reown/*`, `wagmi`, or `viem` directly. -1. A **server proxy** — Route Handlers that forward to the Engine with your secret key. +You build three things: + +1. A **server proxy** — routes that forward to the Engine with your secret key. 2. A **browser transport** — points the runtime at those routes. -3. A **wallet integration** — Reown AppKit, adapted into the `WalletProvider` seam. -4. A **signer** — turns the connected wallet into signing capability. +3. The **AppKit provider** — one component (`` in React) or one factory call (`createPayAppKit` in JavaScript). -Then `usePaymentSession` ties them together and gives you a snapshot to render. +The wallet seam, the signer, and the clock all come from the SDK. Then `usePaymentSession` (React) or `createPaymentController` (JavaScript) ties everything together and gives you a snapshot to render. The browser never holds the Engine API key. It talks to *your* server, and your server talks to the WalletConnect Pay Engine — see [The Engine API key never reaches the browser](/payments/psps/headless-sdk/how-it-works#the-engine-api-key-never-reaches-the-browser). @@ -23,29 +24,25 @@ Then `usePaymentSession` ties them together and gives you a snapshot to render. ## Prerequisites -- **Node 18+** and a React 18/19 app. This guide uses **Next.js** (App Router). +- **Node 18+**. The React example uses **Next.js** (App Router); the JavaScript example is framework-neutral. - A **Reown Project ID** — create one at [dashboard.reown.com](https://dashboard.reown.com). Enable the **headless** feature on the project. - A **WalletConnect Pay Gateway API key** for the Engine (server-side). [Talk to us](https://share.hsforms.com/1XsMCkUxFT2Cte8SCeAh89wnxw6s) to get onboarded. ## Install +Install only the Headless SDK. Wallet connectivity (`@reown/appkit`, `wagmi`, `viem`, `@solana/web3.js`, `@tanstack/react-query`) comes transitively through `@walletconnect/pay-appkit` — you don't add or configure any of it. + ```bash -# The Headless SDK npm install @walletconnect/pay-core @walletconnect/pay-state \ - @walletconnect/pay-react @walletconnect/pay-appkit + @walletconnect/pay-appkit -# Reown AppKit + adapters for wallet connection (EVM via Wagmi, plus Solana) -npm install @reown/appkit @reown/appkit-adapter-wagmi @reown/appkit-adapter-solana \ - wagmi viem @solana/web3.js @tanstack/react-query +# React hosts also need the hook binding: +npm install @walletconnect/pay-react ``` - - `@walletconnect/pay-appkit` requires a Reown AppKit version that exposes the WalletConnect URI on its public state (used to render the pairing QR). Pin `@reown/appkit` (and its adapters) to **the same version**, at or above the SDK's peer range — check the [`pay-appkit` peer dependencies](https://www.npmjs.com/package/@walletconnect/pay-appkit) for the current minimum. All `@reown/appkit*` packages must share one version. - - ## Step 1 — Server proxy (keep the API key server-side) -Create a server-only module that constructs the Engine client once and forwards calls. The key comes from server env and never ships to the browser. +Create a server-only module that constructs the Engine client once and forwards calls. The key comes from server env and never ships to the browser. This is framework-agnostic — any server works; the example uses Next.js Route Handlers. ```typescript lib/server/engine.ts import 'server-only' @@ -80,7 +77,7 @@ export async function callEngine( } ``` -Then expose one Route Handler per Engine call under `/api/wcp/payment/[id]`. The browser transport (Step 2) calls exactly these paths: +Then expose one route per Engine call under `/api/wcp/payment/[id]`. The browser transport (Step 2) calls exactly these paths: ```typescript app/api/wcp/payment/[id]/options/route.ts import { callEngine } from '@/lib/server/engine' @@ -111,6 +108,10 @@ Create the same handler for each route the transport uses: | `app/api/wcp/payment/[id]/confirm/route.ts` | `POST` | `confirmPayment` | | `app/api/wcp/payment/[id]/status/route.ts` | `GET` | `getPaymentStatus` | + + These proxy routes are a **starting point**, not production-ready — add your own origin allowlist, rate limiting, and auth before shipping. Their only job here is to keep the Engine key off the browser. + + ## Step 2 — Browser transport On the client, point the runtime at your proxy. `createHttpTransport` issues requests to `${baseUrl}/payment/:id/...`, matching the routes above. @@ -123,172 +124,90 @@ const transport = createHttpTransport({ baseUrl: '/api/wcp' }) That's the entire `Transport` seam. It speaks the same five methods as the server client, but routes through your origin — no key, no CORS. -## Step 3 — Initialize Reown AppKit (headless) +## Step 3 — Set up AppKit (zero-config) -Construct an AppKit instance once on the client, in headless mode (no built-in modal — you render your own wallet picker). Pass the **same `networks`** array to AppKit so a connected wallet's address expands across the chains the Engine quotes against. +The SDK constructs the AppKit instance, the Wagmi/Solana adapters, and the WalletConnect-owned network set for you, in headless mode (no built-in modal — you render your own wallet picker). You supply only your `projectId` and `metadata`. -```tsx components/providers.tsx -'use client' +In **React**, render `` once near the root. It owns AppKit's client-only construction, the `WagmiProvider` + `QueryClientProvider` tree, and an SSR-safe context. In **JavaScript**, call `createPayAppKit` and `await` its async construction. -import type { AppKit } from '@walletconnect/pay-appkit' // type re-exported by the SDK -import { SolanaAdapter } from '@reown/appkit-adapter-solana' -import { WagmiAdapter } from '@reown/appkit-adapter-wagmi' -import { mainnet, polygon, arbitrum, optimism, base, solana, type AppKitNetwork } from '@reown/appkit/networks' -import { createAppKit } from '@reown/appkit/react' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { createContext, useContext, useEffect, useState } from 'react' -import { WagmiProvider } from 'wagmi' + +```tsx React — components/providers.tsx +'use client' -export const networks: [AppKitNetwork, ...AppKitNetwork[]] = [ - mainnet, polygon, arbitrum, optimism, base, solana -] +import { PayAppKitProvider } from '@walletconnect/pay-appkit/react' const projectId = process.env.NEXT_PUBLIC_APPKIT_PROJECT_ID ?? '' -let wagmiAdapter: WagmiAdapter | null = null -let appKitInstance: AppKit | null = null - -function initAppKit() { - if (wagmiAdapter && appKitInstance) return { adapter: wagmiAdapter, appKit: appKitInstance } - if (typeof window === 'undefined' || !projectId) return null - - wagmiAdapter = new WagmiAdapter({ networks, projectId }) - appKitInstance = createAppKit({ - adapters: [wagmiAdapter, new SolanaAdapter()], - networks, - projectId, - features: { headless: true }, // no modal — you own the picker - metadata: { - name: 'Acme Pay', - description: 'Headless checkout', - url: typeof window !== 'undefined' ? window.location.origin : 'https://example.com', - icons: [] - } - }) - return { adapter: wagmiAdapter, appKit: appKitInstance } -} - -const AppKitContext = createContext(null) -export function useAppKit(): AppKit { - const appKit = useContext(AppKitContext) - if (!appKit) throw new Error('useAppKit must be used within ') - return appKit -} - export function Providers({ children }: { children: React.ReactNode }) { - const [state, setState] = useState<{ adapter: WagmiAdapter; appKit: AppKit } | null>(null) - const [queryClient] = useState(() => new QueryClient()) - - useEffect(() => setState(initAppKit()), []) - if (!state) return
Initializing…
- return ( - - - {children} - - + + {children} + ) } ``` - - Call `createAppKit` **once** at module scope (guarded as above), never inside a component render. Keep every `@reown/appkit*` package on the same version. - - -## Step 4 — The wallet seam - -`@walletconnect/pay-appkit/react` turns the AppKit instance into the runtime's `WalletProvider` seam **and** a ready-made wallet-picker controller (list, search, pagination, the pairing QR URI). One hook gives you both. +```typescript JavaScript — appkit.ts +import { createPayAppKit } from '@walletconnect/pay-appkit' -```tsx -import { useAppKitWalletProvider } from '@walletconnect/pay-appkit/react' - -const { - wallet, // ← the WalletProvider seam to hand the runtime - wallets, // wallet list for your picker - connectedWallets, // currently connected, per namespace - supportedNamespaces, - wcUri, // WalletConnect URI for the QR - getWcUri, // generate a generic pairing URI eagerly - searchQuery, setSearchQuery, - hasMore, loadMore, - isFetchingWallets, - isInitialized -} = useAppKitWalletProvider(appKit, { - wcPayUrl: typeof window !== 'undefined' ? window.location.href : undefined +const payAppKit = createPayAppKit({ + projectId: import.meta.env.VITE_APPKIT_PROJECT_ID ?? '', + metadata: { + name: 'Acme Pay', + description: 'Headless checkout', + url: window.location.origin, + icons: [] + } }) -``` -## Step 5 — The signer +// Construction is client-only and async — await it before reading the instance. +await payAppKit.whenReady() +export const appKit = payAppKit.getInstance() +``` +
-The signing strategies (EVM + Solana) live in `@walletconnect/pay-state`; they just need the wallet's providers, which only your host can supply. Wire them into a `Signer`: +`` accepts an optional `queryClient` (a host with its own passes it to share one cache; omit it for a fully internal one) and optional `themeVariables` (e.g. a host font). Both `createPayAppKit` and the provider load the Reown modules through a client-only dynamic import, so AppKit's UI never enters your SSR bundle. -```typescript lib/signer.ts -import { loadSolanaWeb3 } from '@walletconnect/pay-appkit' -import { - EvmSigningStrategy, - SolanaSigningStrategy, - signOptionActions, - type ActionRange, - type EIP1193Provider, - type PaymentOptionExtended, - type Signer, - type SignPaymentResult, - type SigningStrategy, - type SolanaProvider, - type WalletProvider -} from '@walletconnect/pay-state' - -export function createAppKitSigner(wallet: WalletProvider): Signer { - return { - signActions(option: PaymentOptionExtended, range?: ActionRange): Promise { - const strategies: SigningStrategy[] = [] - - const evm = wallet.getProvider({ namespace: 'eip155', chainId: '1' }) - if (evm) { - strategies.push( - new EvmSigningStrategy(evm as EIP1193Provider, { - switchNetwork: caip => wallet.switchNetwork(caip), - getActiveChainId: () => { - const caip = wallet.getAccounts().eip155?.caipAddress - const [ns, chain] = caip?.split(':') ?? [] - return chain ? `${ns}:${chain}` : undefined - } - }) - ) - } - - const sol = wallet.getProvider({ namespace: 'solana', chainId: 'mainnet' }) - strategies.push(new SolanaSigningStrategy(sol as SolanaProvider | null, { loadWeb3: loadSolanaWeb3 })) - - return signOptionActions(option, strategies, range) - } - } -} -``` +## Step 4 — Build the checkout -## Step 6 — Drive the session +Assemble the seams and drive the session. The **wallet seam** comes from the SDK's wallet-list hook/controller, and the **signer** is a single built-in call — `createAppKitSigner(wallet)` — so you no longer wire up signing strategies by hand. `clock` is `browserClock`. -Assemble the seams and call `usePaymentSession`. It returns the public `snapshot` plus named actions — no XState, no `send`. +In React, `useAppKitWalletProvider` turns the AppKit instance into the `WalletProvider` seam **and** a ready-made picker controller (list, search, pagination, the pairing QR URI). Read the instance from `getPayAppKitInstance()` once `usePayAppKit().isReady` is true. In JavaScript, `createAppKitWalletList` is the framework-neutral equivalent. -```tsx components/checkout.tsx + +```tsx React — components/checkout.tsx 'use client' import { createHttpTransport } from '@walletconnect/pay-core' -import { useAppKitWalletProvider } from '@walletconnect/pay-appkit/react' -import { browserClock, type PaymentOptionExtended } from '@walletconnect/pay-state' +import { createAppKitSigner } from '@walletconnect/pay-appkit' +import { + getPayAppKitInstance, + useAppKitWalletProvider, + usePayAppKit +} from '@walletconnect/pay-appkit/react' +import { browserClock } from '@walletconnect/pay-state' import { usePaymentSession } from '@walletconnect/pay-react' import { useMemo } from 'react' -import { useAppKit } from '@/components/providers' -import { createAppKitSigner } from '@/lib/signer' export function Checkout({ paymentId }: { paymentId: string }) { - const appKit = useAppKit() + // The provider constructs AppKit asynchronously; read the instance once it's ready. + const { isReady } = usePayAppKit() + const appKit = isReady ? getPayAppKitInstance() : undefined + + // The wallet seam + a ready-made picker (list, search, pagination, QR URI). const { wallet, wallets, wcUri, getWcUri } = useAppKitWalletProvider(appKit, { wcPayUrl: typeof window !== 'undefined' ? window.location.href : undefined }) - // Assemble the runtime seams. `signer` lives inside `seams`; `wallet` is passed separately. + // Assemble the runtime seams. The signer is one built-in call. const seams = useMemo( () => ({ transport: createHttpTransport({ baseUrl: '/api/wcp' }), @@ -298,14 +217,49 @@ export function Checkout({ paymentId }: { paymentId: string }) { [wallet] ) - const { snapshot, connectWallet, selectOption, confirmSelection, submitInfoCapture } = - usePaymentSession({ paymentId, seams, wallet }) + const { + snapshot, + connectWallet, + disconnectWallet, + selectOption, + confirmSelection, + submitInfoCapture + } = usePaymentSession({ paymentId, seams, wallet }) - return
{/* render per snapshot.state — see Step 7 */}
+ return
{/* render per snapshot.state — see Step 5 */}
} ``` -Render it from a route, wrapped in your providers: +```typescript JavaScript — main.ts +import { createHttpTransport } from '@walletconnect/pay-core' +import { createAppKitSigner, createAppKitWalletList } from '@walletconnect/pay-appkit' +import { browserClock, createPaymentController } from '@walletconnect/pay-state' + +import { appKit } from './appkit' + +// The framework-neutral wallet-list controller: list / search / paginate / QR URI / +// connect — and `walletList.wallet`, the seam the runtime drives. +const walletList = createAppKitWalletList(appKit, { + wcPayUrl: window.location.href +}) +const wallet = walletList.wallet + +const controller = createPaymentController({ + paymentId, + wallet, + seams: { + transport: createHttpTransport({ baseUrl: '/api/wcp' }), + clock: browserClock, + signer: createAppKitSigner(wallet) + } +}) + +controller.subscribe(() => render(controller.getSnapshot())) +controller.start() +``` +
+ +In React, render the checkout from a route wrapped in your providers: ```tsx app/[paymentId]/page.tsx import { Checkout } from '@/components/checkout' @@ -317,15 +271,14 @@ export default async function PaymentPage({ params }: { params: Promise<{ paymen } ``` -## Step 7 — Render the snapshot +## Step 5 — Render the snapshot -`snapshot.state` is a single string you switch on. Each state maps to one piece of UI; the named actions advance the flow. +`snapshot.state` is a single string you switch on. Each state maps to one piece of UI; the named actions advance the flow. The logic is the same for React and JavaScript — the only difference is where the snapshot comes from (`usePaymentSession` vs `controller.getSnapshot()`). ```tsx switch (snapshot.state) { case 'ReadyForWallet': - // Show the QR (snapshot is connecting-agnostic; QR comes from getWcUri/wcUri) - // and a wallet picker. On pick: + // Show the QR (from getWcUri/wcUri) and a wallet picker. On pick: return connectWallet(w, w.namespaces[0])} /> case 'ConnectingWallet': @@ -375,7 +328,7 @@ switch (snapshot.state) { } ``` -That's a full gateway. Connect → options → (optional KYC) → confirm → sign → settle, all driven by the runtime; you only render and call actions. +That's a full gateway. Connect → options → (optional KYC) → confirm → sign → settle, all driven by the runtime; you only render and call actions. Once a wallet is connected, `disconnectWallet(namespace?)` drops one namespace or all of them. ## Environment variables @@ -388,34 +341,20 @@ WCP_API_URL=https://staging.api.pay.walletconnect.org WCP_WALLET_API_KEY= ``` -## Without React (framework-neutral) - -Every React API here wraps a framework-agnostic core. A vanilla / Vue / Svelte host uses `createPaymentController` (pay-state) + `createAppKitWalletList` (pay-appkit) directly: - -```typescript -import { createHttpTransport } from '@walletconnect/pay-core' -import { createAppKitWalletList } from '@walletconnect/pay-appkit' -import { browserClock, createPaymentController } from '@walletconnect/pay-state' - -const walletList = createAppKitWalletList(appKit, { wcPayUrl: window.location.href }) - -const controller = createPaymentController({ - paymentId, - wallet: walletList.wallet, - seams: { - transport: createHttpTransport({ baseUrl: '/api/wcp' }), - clock: browserClock, - signer: createAppKitSigner(walletList.wallet) - } -}) + + In a Vite / non-Next.js host, expose the project ID under that toolchain's client env convention (e.g. `VITE_APPKIT_PROJECT_ID`) and keep `WCP_WALLET_API_KEY` on the server only. + -controller.subscribe(() => render(controller.getSnapshot())) -controller.start() -``` +## Reference apps - - The same checkout with no framework — `createPaymentController` + manual subscribe + imperative render. - + + + The full React checkout — `` + `usePaymentSession`, no `@reown/*` in the app. + + + The same checkout with no framework — `createPaymentController` + manual subscribe + imperative render. + + ## Next steps diff --git a/payments/psps/headless-sdk/packages-reference.mdx b/payments/psps/headless-sdk/packages-reference.mdx index d7f6f60..89cad25 100644 --- a/payments/psps/headless-sdk/packages-reference.mdx +++ b/payments/psps/headless-sdk/packages-reference.mdx @@ -167,6 +167,8 @@ import { browserClock, noopTelemetry, browserDefaults } from '@walletconnect/pay ### Signing strategies +AppKit hosts don't need these directly — `@walletconnect/pay-appkit` exports a zero-config `createAppKitSigner(wallet)` that wraps them with the bundled Solana codec. These are the low-level primitives, for a custom wallet integration. + ```typescript import { EvmSigningStrategy, SolanaSigningStrategy, signOptionActions } from '@walletconnect/pay-state' @@ -230,32 +232,47 @@ Plus a **host-orchestration channel** for signals the runtime can't observe itse ## `@walletconnect/pay-appkit` -The Reown AppKit adapter — implements the `WalletProvider` seam over an AppKit instance, and ships a headless wallet-picker controller. The main entry is framework-neutral; the React hook lives on `/react`. +The Reown AppKit adapter — it owns the entire AppKit setup so a host stays `@reown/*`-free. It constructs the instance, implements the `WalletProvider` seam over it, provides a zero-config `Signer`, and ships a headless wallet-picker controller. The main entry is framework-neutral; the React provider + hooks live on `/react`. ### Main entry — `@walletconnect/pay-appkit` ```typescript import { - createAppKitWalletProvider, // (appKit, options?) => WalletProvider + createPayAppKit, // ({ projectId, metadata, themeVariables? }) => PayAppKit (one-call setup) + createAppKitSigner, // (wallet) => Signer (zero-config, bundles the Solana codec) createAppKitWalletList, // (appKit, options?) => AppKitWalletList (framework-neutral picker) + createAppKitWalletProvider, // (appKit, options?) => WalletProvider + SUPPORTED_NETWORKS, EVM_NETWORKS, // the WC-owned network set (baked in, not host config) loadSolanaWeb3, // lazy @solana/web3.js codec loader for the Solana signing strategy applyPlacements, resolvePlacements, // wallet-ordering helpers type AppKit, // re-exported AppKit instance type — don't import @reown/appkit directly + type PayAppKit, type CreatePayAppKitOptions, type PayAppKitMetadata, type WalletListItem, type ConnectedWallet, type WalletListState, type WalletListOptions } from '@walletconnect/pay-appkit' ``` +`createPayAppKit` builds the instance with the WC-owned networks + adapters in headless mode. It's **client-only and async** — `await payAppKit.whenReady()`, then read `getInstance()` / `getWagmiConfig()` / `getHooks()` / `getError()` / `isInitialized()`. + `createAppKitWalletList` returns a controller — `wallet` (the seam), `getState()`, `subscribe()`, `fetchWallets()`, `search()`, `loadMore()`, `getWcUri()` — for non-React hosts. ### React entry — `@walletconnect/pay-appkit/react` +The zero-config provider that owns AppKit's construction + the Wagmi/Query tree + an SSR-safe context, plus the state hook and the imperative instance accessor: + ```typescript -import { useAppKitWalletProvider, type AppKitWalletProviderHandle, type UseAppKitWalletProviderOptions } from '@walletconnect/pay-appkit/react' +import { + PayAppKitProvider, // + usePayAppKit, // SSR-safe hook: isReady / account / network / walletConnection / connect / disconnect / … + getPayAppKitInstance, // module-level accessor to the same AppKit instance (for non-hook consumers) + useAppKitWalletProvider, + type AppKitWalletProviderHandle, + type UseAppKitWalletProviderOptions +} from '@walletconnect/pay-appkit/react' function useAppKitWalletProvider(appKit: AppKit | undefined, options?: UseAppKitWalletProviderOptions): AppKitWalletProviderHandle ``` -The handle extends `WalletListState` and adds `wallet` (the seam to hand `usePaymentSession`), `searchQuery`/`setSearchQuery` (debounced), `fetchWallets`, `loadMore`, and `getWcUri` for the pairing QR. +`useAppKitWalletProvider`'s handle extends `WalletListState` and adds `wallet` (the seam to hand `usePaymentSession`), `searchQuery`/`setSearchQuery` (debounced), `fetchWallets`, `loadMore`, and `getWcUri` for the pairing QR. ## Next steps From 71525bc49f0f9bbbc57330f5a1138a008c31f975 Mon Sep 17 00:00:00 2001 From: Enes Date: Wed, 8 Jul 2026 14:21:36 +0300 Subject: [PATCH 2/3] docs(headless-sdk): address PR feedback + point examples to new repo - install: keep pay-react on the same line as the other packages - providers metadata url: simplify to https://example.com - point example-app links (implementation + overview) to WalletConnect/walletconnect-pay-examples/tree/main/gateway Co-Authored-By: Claude Opus 4.8 (1M context) --- payments/psps/headless-sdk/implementation.mdx | 13 ++++++------- payments/psps/headless-sdk/overview.mdx | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/payments/psps/headless-sdk/implementation.mdx b/payments/psps/headless-sdk/implementation.mdx index 1376af2..a25597a 100644 --- a/payments/psps/headless-sdk/implementation.mdx +++ b/payments/psps/headless-sdk/implementation.mdx @@ -34,12 +34,11 @@ Install only the Headless SDK. Wallet connectivity (`@reown/appkit`, `wagmi`, `v ```bash npm install @walletconnect/pay-core @walletconnect/pay-state \ - @walletconnect/pay-appkit - -# React hosts also need the hook binding: -npm install @walletconnect/pay-react + @walletconnect/pay-appkit @walletconnect/pay-react ``` +`@walletconnect/pay-react` is the React hook binding — omit it if you're not using React. + ## Step 1 — Server proxy (keep the API key server-side) Create a server-only module that constructs the Engine client once and forwards calls. The key comes from server env and never ships to the browser. This is framework-agnostic — any server works; the example uses Next.js Route Handlers. @@ -145,7 +144,7 @@ export function Providers({ children }: { children: React.ReactNode }) { metadata={{ name: 'Acme Pay', description: 'Headless checkout', - url: typeof window !== 'undefined' ? window.location.origin : 'https://example.com', + url: 'https://example.com', icons: [] }} > @@ -348,10 +347,10 @@ WCP_WALLET_API_KEY= ## Reference apps - + The full React checkout — `` + `usePaymentSession`, no `@reown/*` in the app. - + The same checkout with no framework — `createPaymentController` + manual subscribe + imperative render. diff --git a/payments/psps/headless-sdk/overview.mdx b/payments/psps/headless-sdk/overview.mdx index 58e0d2e..b26f47b 100644 --- a/payments/psps/headless-sdk/overview.mdx +++ b/payments/psps/headless-sdk/overview.mdx @@ -47,7 +47,7 @@ The Headless SDK supports the full WalletConnect Pay token and network coverage The fastest way to learn the SDK is to read the reference checkout it's extracted from. Every snippet in these docs comes from it. - + A complete, branded Next.js checkout built on the four packages. From 625410d7a59870ea16a929e534bef1986052139c Mon Sep 17 00:00:00 2001 From: Enes Date: Wed, 8 Jul 2026 14:32:21 +0300 Subject: [PATCH 3/3] docs(headless-sdk): clarify Signer seam has one entrypoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit createAppKitSigner is the single entrypoint for AppKit hosts; it wraps pay-state's signing internally. The raw strategies are only the low-level fallback for a custom, non-AppKit wallet — reword so they don't read as two co-equal options. Co-Authored-By: Claude Opus 4.8 (1M context) --- payments/psps/headless-sdk/how-it-works.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payments/psps/headless-sdk/how-it-works.mdx b/payments/psps/headless-sdk/how-it-works.mdx index b0fcae2..ac1c6a6 100644 --- a/payments/psps/headless-sdk/how-it-works.mdx +++ b/payments/psps/headless-sdk/how-it-works.mdx @@ -69,7 +69,7 @@ The five seams the runtime depends on: | ---------------- | ------------------------------------------ | ---------------------------------------------------- | | `Transport` | Engine HTTP calls | `pay-core` `createHttpTransport` → your server route | | `WalletProvider` | connect / accounts / provider / switch | `pay-appkit`, or your own wallet integration | -| `Signer` | sign a payment option's wallet-RPC actions | `pay-appkit` `createAppKitSigner`, or `pay-state` strategies for a custom wallet | +| `Signer` | sign a payment option's wallet-RPC actions | `pay-appkit` `createAppKitSigner` — one call (wraps `pay-state`'s signing; drop to its raw strategies only for a custom, non-AppKit wallet) | | `Clock` | intervals + page visibility (for polling) | browser timers (the SDK ships a default) | | `Telemetry` | analytics breadcrumbs | your analytics pipeline (optional) |