Payments, in your app.
Give your checkout a USDC payment flow. Give your agent a way to buy a resource. Keep your wallet, your UI, and your application logic.
Try the playground →Get started
You can explore the demo without installing anything. To embed the SDK in another app, build the workspace packages and install them from this repository. The packages are not on npm; publishing is optional for this demo.
Build and install
# In the Lycoris repository
bun install
bun run build --filter=@settle-kit/core --filter=@settle-kit/react
# Add the entries below to your host package.json, then run in the host:
bun installHost package.json entries
{
"dependencies": {
"@settle-kit/core": "file:/path/to/lycoris/packages/settle-kit/core",
"@settle-kit/react": "file:/path/to/lycoris/packages/settle-kit/react",
"react": "^19.2.0",
"viem": "^2"
},
"overrides": {
"@settle-kit/core": "file:/path/to/lycoris/packages/settle-kit/core"
}
}Replace /path/to/lycoris with the absolute repository path. The core override keeps transitive dependencies local while the packages are unpublished. For agents or server, add the corresponding package path with the same core override.
Use React 19 and viem 2 for checkout. The server adapter supports Next.js 16.2.6+ within 16.x. Build with Bun; the resulting ESM packages can be consumed by other package managers.
| Package | Use it for |
|---|---|
| @settle-kit/core | Headless sessions, amount validation, balance preflight, transfer and receipt confirmation. |
| @settle-kit/react | Provider, hooks, and optional Checkout UI with compiled styles. |
| @settle-kit/agents | x402 paid fetch and AP2 mandate helpers. |
| @settle-kit/server/next | Paid Next.js API routes with request-scoped mandate forwarding. |
Add a React checkout
Mount one Provider around your store. Supply a wallet signer and, optionally, a default merchant destination: each purchase can override it. Then render Checkout for each purchase. Amounts are decimal strings with at most six decimal places.
store.tsx
"use client";
import { SettleProvider, type PaymentSigner } from "@settle-kit/react";
import { Checkout } from "@settle-kit/react/ui";
import "@settle-kit/react/styles.css";
export function Store({ getSigner }: {
getSigner: () => Promise<PaymentSigner>;
}) {
return (
<SettleProvider config={{
appName: "Your store",
getSigner,
destination: {
targetChain: 84532,
targetAsset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
recipient: "0x1111111111111111111111111111111111111111", // replace
},
}}>
<Checkout amount="0.1" title="Weather report" />
</SettleProvider>
);
}Replace the example recipient with your merchant address. Import the compiled CSS once; consumers need no Tailwind setup or Next.js transpilePackages configuration.
Bring your wallet
The host owns wallet connection and the network. PaymentSigner needs an address and sendTransaction. Put the wallet on Base Sepolia before pay() — the SDK does not ask for a chain id. The buyer needs test USDC and Base Sepolia ETH for gas.
The playground has both rails. Demo pays wraps createUsdcMethod for a faucet and a 4337 receipt. Your wallet is this injected EOA signer — Coinbase Wallet or any window.ethereum provider. pay() is the same.
wallet.ts
import { createWalletClient, custom, type EIP1193Provider } from "viem";
import { baseSepolia } from "viem/chains";
import type { PaymentSigner } from "@settle-kit/react";
// Pass the EIP-1193 provider supplied by your wallet connection UI.
export async function getSigner(provider: EIP1193Provider): Promise<PaymentSigner> {
const wallet = createWalletClient({
chain: baseSepolia,
transport: custom(provider),
});
const [account] = await wallet.requestAddresses();
if (!account) throw new Error("Connect a wallet first.");
return {
address: account,
sendTransaction: ({ to, data }) => wallet.sendTransaction({
account, chain: baseSepolia, to, data,
}),
};
}Pass a callback such as getSigner: () => getSigner(provider) to your Provider configuration, using your connected wallet’s provider. The SDK sends to the USDC contract, with the merchant recipient encoded in the transfer.
Use your own components
useCheckout exposes the same session to every component under the Provider. pay prepares and submits from one click. Keep your own buttons, dialogs, and design system.
buy-report.tsx
"use client";
import { useCheckout } from "@settle-kit/react";
// Render inside the same SettleProvider.
export function BuyReport() {
const { state, pay, reset, retryConfirmation } = useCheckout();
switch (state.status) {
case "idle":
return <button onClick={() => void pay({ amount: "0.1" })}>
Pay 0.1 USDC
</button>;
case "settling":
return state.confirmationError
? <button onClick={() => void retryConfirmation()}>Check payment status</button>
: <p>Waiting for your wallet and confirmation…</p>;
case "settled":
return <><p>Payment confirmed.</p><button onClick={reset}>New purchase</button></>;
case "failed":
return <><p role="alert">{state.error.message}</p><button onClick={reset}>Reset</button></>;
}
}| API | What it does |
|---|---|
| pay({ amount, title?, destination? }) | Submits the transfer and waits for a receipt. Existing in-flight payments cannot be replaced. |
| retryConfirmation() | Checks the submitted transaction’s receipt again. Never sends another transfer. |
| reset() | Returns to idle. Refuses to reset a payment that is still settling. |
Match your app
Set appearance on the Provider or override it on Checkout. Changes apply without resetting the active payment. The optional UI supports inherited, light, and dark themes.
Appearance configuration
<SettleProvider
config={config}
appearance={{
theme: "inherit", // also "light" or "dark"
variables: { borderRadius: "16px", colorPrimary: "var(--primary)" },
}}
>
<Checkout amount="0.1" className="your-checkout" />
</SettleProvider>variables set CSS custom properties on the card. Use className for extra host styles.
Understand the payment state
| State | Meaning |
|---|---|
| idle | No active purchase. Show the product and Pay action. |
| settling | The transfer is preparing, the wallet is open, or a receipt is being looked up. A transaction hash alone does not mean success. |
| settled | A successful receipt was observed. |
| failed | A known failure. Show the error; retain a transaction link if one exists. |
A receipt timeout stays in settling with confirmationError. Show “Check payment status” and call retryConfirmation. Do not reset or resend. User errors such as insufficient_usdc, wallet_rejected, and wrong_network are state data; invalid host configuration throws.
Use the headless engine
Core works without React. Subscribe to the manager, render from getState(), and call pay() from idle to submit.
payment.ts
import { createCheckout, type PaymentSigner } from "@settle-kit/core";
export function preparePayment(getSigner: () => Promise<PaymentSigner>) {
return createCheckout({
amount: "0.1",
getSigner,
destination: {
targetChain: 84532,
targetAsset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
recipient: "0x1111111111111111111111111111111111111111", // replace
},
});
}
// checkout.subscribe(() => render(checkout.getState()));
// await checkout.pay();Let an agent buy a resource
The agents package wraps x402: request a resource, receive HTTP 402, sign the payment, and retry. Supply an x402 scheme backed by your signer and, when required, a signed AP2 mandate header.
buy-resource.ts
import { createPaidFetch, payForResource } from "@settle-kit/agents";
type Scheme = Parameters<typeof createPaidFetch>[0]["scheme"];
// Supply an x402 scheme backed by your agent's signer, and its signed mandate.
export async function buyReport(url: string, scheme: Scheme, mandateHeader: string) {
const paidFetch = createPaidFetch({
scheme,
getMandateHeader: () => mandateHeader,
});
const result = await payForResource({ url, paidFetch });
return result; // httpStatus, body, txHash, authorizationNonce, challenge
}The host owns its URL allowlist, signer, and credentials. The package also exports quoteResource, signMandate, serializeMandateHeader, and verifyMandateLocal. Payment metadata belongs to each response, so concurrent requests do not share a last-payment record.
Require payment for an API
Use the server package in a Next.js App Router route. Choose a facilitator that enforces identity, mandate, spending limits, and balance checks. The wrapper forwards the mandate; it does not validate authorization locally.
app/api/report/route.ts
// app/api/report/route.ts
import { withAgenticPayment } from "@settle-kit/server/next";
export const GET = withAgenticPayment(
async () => Response.json({ report: "Your report data" }),
{
priceUsdc: "0.1",
network: "eip155:84532",
payTo: "0x1111111111111111111111111111111111111111", // replace
facilitatorUrl: "https://your-facilitator.example.com", // replace
description: "Weather report",
},
);Your handler runs after verification but before settlement. Keep it read-only or independently idempotent. The wrapper releases the resource after settlement; it cannot undo work the handler already performed. Responses are private and not cacheable.
Know the demo boundaries
- Base Sepolia (84532), Circle test USDC only. No mainnet, cards, swaps, bridges, or fiat onramp.
- SDK sessions live in memory. The sponsored host adds purchase recovery with a browser purchase ID and a database record; other hosts must implement their own recovery.
- Balance preflight is not a lock. One confirmation is demo evidence; replacement transactions require manual inspection.
- The playground makes real Base Sepolia transfers. Demo pays uses a sponsored smart account with a 50-purchase faucet budget (5 test USDC). Your wallet is a Coinbase Wallet or other injected EOA; you send the USDC and pay gas. Report access lasts 15 minutes.
- The SDK is not published to npm. Local tarballs and an independent consumer are the distribution proof for this demo.