1Pay.ing Kit is a client-side SDK that implements the x402 payment specification proposed by Coinbase—a concrete implementation of HTTP 402—designed to radically simplify the integration and use of Web3 payments.
The x402 specification decouples backend applications from payment services by introducing the "Facilitator" role. 1Pay.ing builds on this by solving the client-side problem, acting as a "client-side facilitator" that provides a unified payment wallet and SDK. This decouples the client application from the user's specific wallet.
- Dual Decoupling: The backend doesn't need to handle payment specifics, and the client application doesn't need to know which wallet the user has.
- Permissionless: Any application can integrate it, and any user can pay, without prior registration or approval.
- Built on Web Standards: Based on the HTTP 402 status code, making integration minimally invasive to existing web architectures.
1paying-kit greatly simplifies the client-side logic for handling HTTP 402 responses.
- Request a Resource: Your client application requests a protected API as usual.
- Receive a 402 Response: Your backend service (the resource owner), following the x402 specification, generates the payment requirements and returns them to the client with an
HTTP 402 Payment Requiredresponse. - Handle Payment:
1paying-kitintercepts the 402 response, generates a1Pay.ingpayment link, and the client guides the user to the1Pay.ingwallet to complete the payment signature. - Get Payment Payload: After the application captures the payment payload via
1paying-kit, it uses the payload to request the backend again. - Settle Payment: The backend receives the resource request with the payment payload, sends the payload to the x402 Facilitator, and waits for settlement.
- Return Resource: After the Facilitator settles the payment, the backend service can return the protected resource to the client application.
This is a monorepo containing the following main parts:
ts/1paying-kit: The core TypeScript SDK (@ldclabs/1paying-kit). It provides all the tools needed to handle the HTTP 402 payment flow on the client side.examples/1paying-coffee-app: A frontend demo application built with SvelteKit. It fully demonstrates how to use1paying-kitto interact with a payment-protected backend.examples/1paying-coffee-cli: A command-line interface (CLI) demo application. It demonstrates how to use1paying-kitin a Node.js environment to handle the entire HTTP 402 payment flow from the terminal.examples/1paying-coffee-worker: A backend demo application built with Cloudflare Workers. It shows how to protect an API endpoint and verify payments by integrating with an x402 facilitator.
The fastest way to understand 1paying-kit is to run the "Buy Me a Coffee" demo project.
https://1paying-coffee.zensh.workers.dev/
-
Install Dependencies: In the repository root, run:
pnpm install
-
Start the Backend Worker: Open a new terminal and start the local development server for the Cloudflare Worker.
pnpm --filter 1paying-coffee-worker dev
The backend service will be running at
http://localhost:8787. -
Start the Frontend App: In another terminal, start the development server for the SvelteKit frontend app.
pnpm --filter 1paying-coffee-app dev
You can now open
http://localhost:5173in your browser to experience the full payment flow.
Using 1paying-kit in your own project is straightforward.
npm install @ldclabs/1paying-kit1paying-kit can automatically handle the 402 flow by intercepting fetch, or you can handle it manually.
This example is designed to be the simplest possible demonstration of a complete 1paying-kit integration.
import { payingKit } from '@ldclabs/1paying-kit'
import { stdin as input, stdout as output } from 'node:process'
import * as readline from 'node:readline/promises'
import { exec } from 'node:child_process'
import { ProxyAgent, setGlobalDispatcher } from 'undici'
const proxy = process.env.http_proxy || process.env.https_proxy
if (proxy) {
console.log(`Using proxy: ${proxy}`)
setGlobalDispatcher(new ProxyAgent(proxy))
}
// Run with: npx tsx cli.ts
async function main() {
const rl = readline.createInterface({ input, output })
const coffeeStore = 'https://1paying-coffee.zensh.workers.dev'
console.log('Welcome to the 1Paying Coffee CLI!')
let response = await fetch(`${coffeeStore}/api/make-coffee`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
})
console.log(`Initial response status: ${response.status}`)
const { payUrl, txid } = await payingKit.tryGetPayUrl(response)
if (payUrl && txid) {
// Payment is required, handle it with the kit
const _answer = await rl.question(
`Press ENTER to open in the browser...\n${payUrl} (Enter)`
)
// Redirect user to sign the payment
exec(`open "${payUrl}"`)
try {
const payloadHeader = await payingKit.waitForPaymentPayload(txid, {
onprogress: (state) => {
process.stdout.write(`\rPayment status: ${state.status}`)
}
})
// Now you can retry the original request with the payment payload
// typically in an 'Authorization' or 'X-Payment' header.
response = await fetch(`${coffeeStore}/api/make-coffee`, {
method: 'POST',
headers: {
'X-PAYMENT': payloadHeader
}
})
} catch (error) {
console.error('Payment failed or timed out:', error)
throw error
}
}
rl.close()
// Process the successful response
const data = await response.json()
console.log('Your coffee:', data)
}
main().catch((error) => {
console.error('Error in main:', error)
})Copyright © 2025 LDC Labs.
This project is licensed under the Apache-2.0 License. See the LICENSE file for details.
