programmaman/d402: A decentralized alternative to x402 that scales! Contribute to programmaman/d402 development by crea

github.com · 2026-08-03 · by Long Nguyen ·github
A decentralized alternative to x402 that scales! Contribute to programmaman/d402 development by creating an account on GitHub.

programmaman/d402

A decentralized alternative to x402 that scales! Contribute to programmaman/d402 development by creating an account on GitHub.

0 stars · 0 forks · 0 watchers

README

d402 d402 adds verifiable on-chain payments to HTTP. A server returns a 402 challenge, the client creates the matching dPayment, and the server authorizes the retried request from its proof. Install npm install d402 ethers 1. Protect a complete route Use payable() when the protected handler can be expressed as a Fetch-style route: import { JsonRpcProvider, Wallet } from "ethers"; import { payable } from "d402/server";

const provider = new JsonRpcProvider(process.env.RPC_URL); // Any ethers Signer works here, including a KMS or custody-backed signer. // Signer is only needed if the server performs an on-chain action during the request (for example, Once consumption or refunds). const payee = new Wallet(process.env.PAYEE_PRIVATE_KEY, provider); const terms = { chainId: 100, payeeAddress: payee.address, tokenAddress: null, netAmount: "1000000000000000", agreement: { id: "monthly-report:v1" }, expiresAtUnixSec: Math.floor(Date.now() / 1000) + 300, };

export const GET = payable({ paymentConfig: { provider, signer: payee, settlementWindow: 3600, }, terms, handler: (_request, payment) => Response.json({ report: "protected data", paymentId: payment.payment.paymentId, }), }); The request URL is the payment resource by default. terms may also be an async function when price or agreement data depends on the request. 2. Authorize inside a controller Use PaymentAuthorizer when your framework or application owns the controller: import { PaymentAuthorizer } from "d402/server";

const reportPayment = new PaymentAuthorizer({ paymentConfig: { provider, // This may also be a KMS or custody-backed ethers Signer. signer: payee, settlementWindow: 3600, }, terms, });

export async function getReport(request: Request): Promise<Response> { const authorization = await reportPayment.authorize(request);

if (authorization.response !== undefined) { return authorization.response; }

const report = await loadReport();

return Response.json({ report, paymentId: authorization.context.payment.paymentId, }); } authorize() returns a protocol response when the request needs a challenge, retry, or rejection. It returns context only after authorization succeeds. Express middleware, Nest guards, and other framework adapters can translate their native request into a Fetch Request and use this same API. 3. Make each payment single-use Routes are reusable by default. Add Once when one payment should authorize at most one protected operation: import { Once, payable, paymentActions } from "d402/server";

const actions = paymentActions({ provider, // Use the payee's Wallet, KMS, or custody-backed ethers Signer. signer: payee, });

const download = payable({ paymentConfig: { provider, signer: payee, settlementWindow: 3600, }, terms, consumer: Once(actions), handler: async () => Response.json({ downloadUrl: await createDownloadUrl() }), }); Once is an on-chain, at-most-once claim. If work must survive a crash or lost HTTP response, store the result under context.payment.paymentId. Pay for an order Use the default server identity when payment terms represent a stable order or invoice. Put the order ID in the terms so retries reconstruct the same payment: const orderRoute = payable({ paymentConfig: { provider, // This may also be a KMS or custody-backed ethers Signer. signer: payee, }, terms: (request) => { const orderId = new URL(request.url).pathname.split("/").at(-1);

return { chainId: 100, payeeAddress: payee.address, tokenAddress: null, netAmount: "1000000000000000", agreement: { id: `order:${orderId}:v1` }, expiresAtUnixSec: Math.floor(Date.now() / 1000) + 300, }; }, handler, }); The same payer, order terms, and server identity produce the same payment identity. This is useful for orders, invoices, and reusable entitlements. Choose independent payment identity Use client identity for independent per-request payments. This is closer to an x402-style flow, where each request or access attempt should create a fresh payment instead of reusing an order identity: import { Once, payable, paymentActions } from "d402/server";

const actions = paymentActions({ provider, // Use the payee's Wallet, KMS, or custody-backed ethers Signer. signer: payee, });

const independentPaymentRoute = payable({ paymentConfig: { provider, signer: payee, identifier: "client", }, terms, consumer: Once(actions), handler, }); Client identity is separate from single-use access. Use Once when a payment may authorize only one operation, and use client identity when each payment attempt should have an independent payment identity. They can be used together when every new payment should authorize at most one operation. Pay a protected route For a server, agent, or other unattended client, connect an ethers Wallet to the provider: import { JsonRpcProvider, Wallet } from "ethers"; import { createD402Client } from "d402/client";

const provider = new JsonRpcProvider(process.env.RPC_URL); const signer = new Wallet(process.env.PAYER_PRIVATE_KEY, provider); const client = await createD402Client({ provider, signer });

const response = await client.fetch( "https://api.example.com/reports/monthly", ); In a browser, use the signer exposed by MetaMask or another EIP-1193 wallet: import { BrowserProvider } from "ethers"; import { createD402Client } from "d402/client";

const provider = new BrowserProvider(window.ethereum); await provider.send("eth_requestAccounts", []); const signer = await provider.getSigner(); const client = await createD402Client({ provider, signer });

const response = await client.fetch( "https://api.example.com/reports/monthly", ); The client signer is always the payer. Browser wallets prompt the user when d402 sends a payment transaction; ERC-20 payments may also require a separate token approval transaction. Use client.d402Fetch() when the completed payment attempt must be persisted for application recovery or later lifecycle decisions. Choose a payment flow The most important integration guide is Payment flows. It shows when to use reusable access, single-use access, stable orders, jobs, credits, and deposits. Documentation

ELI5 protocol overview Payment flows Protocol diagrams API reference Advanced configuration HTTP and framework integration Scaling and stateless deployment Upcoming features Signing modes Refunds Disputes Testing Examples

License Apache-2.0

source

github.com · 115 links

Comments (0)

No comments yet