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 local tarballs and install them there. The packages are not on npm; publishing is optional for this demo.
Build and install
# In the Lycoris repository
bun install
bun run pack:settle-kit
# 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/dist/settle-kit/core.tgz",
"@settle-kit/react": "file:/path/to/lycoris/dist/settle-kit/react.tgz",
"react": "^19.2.0",
"viem": "^2"
},
"overrides": {
"@settle-kit/core": "file:/path/to/lycoris/dist/settle-kit/core.tgz"
}
}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 agents.tgz or server.tgz dependency 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 and pack 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, and a quote endpoint can return its own recipient. 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 amountUsdc="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. PaymentSigner needs an address and sendTransaction; provide getChainId so the SDK can check the network before sending. The buyer needs test USDC and Base Sepolia ETH for gas.
The public demo uses a separate sponsored adapter: a server wallet pays the fixed merchant with test funds. Visitors never connect a wallet. This browser-wallet example is for apps where customers pay from their own balances.
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,
getChainId: () => wallet.getChainId(),
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. begin selects USDC and requests a quote; pay submits only after the buyer confirms. 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, begin, pay, reset, retryConfirmation } = useCheckout();
switch (state.status) {
case "idle":
return <button onClick={() => void begin({ amountUsdc: "0.1" })}>
Buy report
</button>;
case "quoting":
return <p>Preparing payment…</p>;
case "awaiting_payment":
return <button onClick={() => void pay()}>Pay {state.quote.amountUsdc} 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 |
|---|---|
| begin({ amountUsdc, title?, destination? }) | Starts a purchase and quotes USDC. Existing in-flight payments cannot be replaced. |
| pay() | Requires awaiting_payment. Checks quote expiry, network, and USDC balance before submission. |
| 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. |
| canPay / isBusy | UI conveniences. They do not replace the checks performed by pay(). |
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", controlBorderRadius: "8px" },
elements: { primaryButton: "your-button-class" },
}}>
<Checkout amountUsdc="0.1" labels={{ buy: "Review order" }} />
</SettleProvider>variables set inline CSS custom properties; elements add classes to slots such as card and primaryButton. For a CSP that forbids style attributes, use classes and an external stylesheet instead.
Understand the payment state
| State | Meaning |
|---|---|
| idle | No active purchase. Show the product and Buy action. |
| quoting | Locking the amount and validating the quote. |
| awaiting_payment | The quote is ready. Let the buyer review and confirm. |
| settling | Wallet interaction or receipt lookup is in progress. A transaction hash alone does not mean success. |
| settled | A successful receipt was observed. onSettled fires here. |
| 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 the payment actions from your host UI. Unlike React’s begin, the core API requires selectMethod("usdc") before pay().
payment.ts
import { createCheckout, createSettleConfig, type PaymentSigner } from "@settle-kit/core";
export function preparePayment(getSigner: () => Promise<PaymentSigner>) {
const config = createSettleConfig({
getSigner,
destination: {
targetChain: 84532,
targetAsset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
recipient: "0x1111111111111111111111111111111111111111", // replace
},
});
const checkout = createCheckout(config, { amountUsdc: "0.1" });
return checkout;
}
// Subscribe to getState(), then await checkout.selectMethod("usdc").
// After the buyer confirms your review UI, await checkout.pay().
// Unsubscribe when the host view is disposed.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, credentials, and optional preclear. 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 from a dedicated demo wallet. Its lifetime budget is ten purchases (1 test USDC); 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.