@smd00/x402
v0.1.0
Published
V2-compliant x402 payment protocol building blocks: types, merchant helpers, and Solana exact-scheme verification/settlement.
Maintainers
Readme
@smd00/x402
V2 wire-format compatible x402 payment protocol building blocks for TypeScript, focused on Solana merchants using the exact scheme.
Why does this exist?
The official Solana x402 package (@x402/svm) is the canonical implementation — and it's the right choice for most new projects. This package covers a specific gap:
- Works with
@solana/web3.jsv1.x. The official@x402/svmrequires@solana/kitv6+, a newer SDK generation. If your project is already on web3.js v1, this package lets you adopt V2 x402 without migrating your chain stack. - Zero runtime dependencies beyond
@solana/web3.js. No zod, no viem, no@solana-program/*bundles. If you only need the Solanaexact-scheme merchant path, the surface is small. - Wire-format compatible. The
PAYMENT-REQUIRED/PAYMENT-SIGNATURE/PAYMENT-RESPONSEheaders and payload shapes interoperate with any spec-conformant V2 merchant or client — including@x402/svm.
What this is not: a fork of coinbase/x402. A competitor. A complete x402 library. EVM payments, browser wallet adapters, and facilitator-as-a-service are intentionally out of scope — use @x402/svm, @x402/evm, or @x402/fetch for those.
Features
- V2 headers (
PAYMENT-REQUIRED,PAYMENT-SIGNATURE,PAYMENT-RESPONSE) - Signed-transaction payment flow (client signs, merchant submits + confirms)
- Framework-agnostic merchant helpers (Next.js, Express, raw http)
- DB-agnostic replay protection via a minimal
ReplayStoreinterface - Base58 signature encoding with no extra deps
Install
npm i @smd00/x402 @solana/web3.jsMerchant: require + verify + settle
import {
buildPaymentRequired,
verifyAndSettleSolanaExact,
HEADER_LC,
InvalidPaymentError,
ReplayError,
} from "@smd00/x402";
import { Connection } from "@solana/web3.js";
const connection = new Connection(process.env.SOLANA_RPC!, "confirmed");
// Plug in any DB (Prisma, Supabase, Redis, in-memory) that implements ReplayStore.
const replayStore = myReplayStore;
app.post("/gated", async (req, res) => {
const sigHeader = req.headers[HEADER_LC.paymentSignature] as string | undefined;
if (!sigHeader) {
const { status, headers, body } = buildPaymentRequired({
accepts: [{
scheme: "exact",
network: "solana",
maxAmountRequired: "1000000", // 0.001 SOL in lamports
resource: "https://example.com/gated",
description: "One gated request",
payTo: "YourMerchantWalletPubkey",
maxTimeoutSeconds: 120,
asset: "SOL",
}],
});
for (const [k, v] of Object.entries(headers)) res.setHeader(k, v);
return res.status(status).json(body);
}
try {
const { headerName, headerValue } = await verifyAndSettleSolanaExact({
paymentSignatureHeader: sigHeader,
expectedPayTo: "YourMerchantWalletPubkey",
expectedAmount: "1000000",
expectedAsset: "SOL",
expectedNetwork: "solana",
connection,
replayStore,
});
res.setHeader(headerName, headerValue);
return res.json({ ok: true });
} catch (e) {
if (e instanceof InvalidPaymentError) return res.status(402).json({ error: e.message });
if (e instanceof ReplayError) return res.status(409).json({ error: e.message });
return res.status(500).json({ error: (e as Error).message });
}
});Client: build a signed payment header
The package ships a minimal Solana client helper for server-to-server flows where the payer is a Keypair you already hold (e.g. a bot-controlled treasury). Browser + wallet-adapter integrations live in @x402/svm.
import { buildSolSignedPayment } from "@smd00/x402";
import { Connection, Keypair } from "@solana/web3.js";
const connection = new Connection(rpc, "confirmed");
const payer = Keypair.fromSecretKey(secretKeyBytes);
const { headerValue } = await buildSolSignedPayment({
connection,
payer,
payTo: "MerchantWalletPubkey",
amountLamports: 1_000_000n,
network: "solana",
});
const res = await fetch("https://merchant.example/gated", {
method: "POST",
headers: {
"Content-Type": "application/json",
"PAYMENT-SIGNATURE": headerValue,
},
body: JSON.stringify({ /* … */ }),
});ReplayStore
The package never picks a database. Implement the ReplayStore interface in your app:
import type { ReplayStore, Network } from "@smd00/x402";
export const prismaReplayStore: ReplayStore = {
async hasBeenUsed(network, txHash) {
return !!(await prisma.x402Redemption.findUnique({
where: { network_txHash: { network, txHash } },
}));
},
async markUsed(network, txHash, metadata) {
await prisma.x402Redemption.create({
data: { network, txHash, metadata: metadata ?? {} },
});
},
};Compatibility
| Field | Value |
| -------------- | ------------------------------------------------------------------ |
| x402 version | 2 |
| Schemes | exact |
| Networks | solana, solana-devnet |
| Module format | ESM only. Requires Node 18+ or a modern bundler (Next.js, Vite). |
| EVM support | Not in this package. Use @x402/evm or add your own EVM adapter. |
| Facilitator | Inlined in verifyAndSettleSolanaExact — no separate service. |
CommonJS consumers can still import via dynamic import(), but there is no require() build.
License
MIT
