yeetful 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +307 -0
- package/dist/client.cjs +138 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +40 -0
- package/dist/client.d.ts +40 -0
- package/dist/client.js +134 -0
- package/dist/client.js.map +1 -0
- package/dist/express.cjs +224 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.d.cts +32 -0
- package/dist/express.d.ts +32 -0
- package/dist/express.js +221 -0
- package/dist/express.js.map +1 -0
- package/dist/index.cjs +301 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +290 -0
- package/dist/index.js.map +1 -0
- package/dist/next.cjs +195 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +23 -0
- package/dist/next.d.ts +23 -0
- package/dist/next.js +192 -0
- package/dist/next.js.map +1 -0
- package/dist/server.cjs +176 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +60 -0
- package/dist/server.d.ts +60 -0
- package/dist/server.js +172 -0
- package/dist/server.js.map +1 -0
- package/dist/types-DzGpKiV3.d.cts +83 -0
- package/dist/types-DzGpKiV3.d.ts +83 -0
- package/package.json +92 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// src/facilitator.ts
|
|
2
|
+
var Facilitator = class {
|
|
3
|
+
url;
|
|
4
|
+
authHeader;
|
|
5
|
+
fetcher;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.url = config.url.replace(/\/$/, "");
|
|
8
|
+
this.authHeader = config.authHeader;
|
|
9
|
+
this.fetcher = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
10
|
+
}
|
|
11
|
+
async verify(payment, requirement) {
|
|
12
|
+
return this.post("/verify", { paymentPayload: payment, paymentRequirements: requirement });
|
|
13
|
+
}
|
|
14
|
+
async settle(payment, requirement) {
|
|
15
|
+
return this.post("/settle", { paymentPayload: payment, paymentRequirements: requirement });
|
|
16
|
+
}
|
|
17
|
+
async post(path, body) {
|
|
18
|
+
const res = await this.fetcher(this.url + path, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
headers: {
|
|
21
|
+
"content-type": "application/json",
|
|
22
|
+
...this.authHeader ? { authorization: this.authHeader } : {}
|
|
23
|
+
},
|
|
24
|
+
body: JSON.stringify(body)
|
|
25
|
+
});
|
|
26
|
+
if (!res.ok) {
|
|
27
|
+
const text = await res.text().catch(() => "");
|
|
28
|
+
throw new Error(`Facilitator ${path} failed: ${res.status} ${text}`);
|
|
29
|
+
}
|
|
30
|
+
return await res.json();
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var DEFAULT_FACILITATOR_URL = "https://facilitator.yeetful.com";
|
|
34
|
+
|
|
35
|
+
// src/utils.ts
|
|
36
|
+
var USDC_BY_NETWORK = {
|
|
37
|
+
base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
38
|
+
"base-sepolia": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
39
|
+
ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
40
|
+
optimism: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
|
|
41
|
+
arbitrum: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
42
|
+
polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
|
|
43
|
+
};
|
|
44
|
+
var USDC_DECIMALS = 6;
|
|
45
|
+
function usdcAddress(network) {
|
|
46
|
+
return USDC_BY_NETWORK[network];
|
|
47
|
+
}
|
|
48
|
+
function usdToAtomic(amount, decimals = USDC_DECIMALS) {
|
|
49
|
+
const str = typeof amount === "number" ? amount.toString() : amount;
|
|
50
|
+
if (!/^\d+(\.\d+)?$/.test(str)) {
|
|
51
|
+
throw new Error(`Invalid amount: ${str}`);
|
|
52
|
+
}
|
|
53
|
+
const [whole, frac = ""] = str.split(".");
|
|
54
|
+
const padded = frac.slice(0, decimals).padEnd(decimals, "0");
|
|
55
|
+
return (BigInt(whole ?? "0") * 10n ** BigInt(decimals) + BigInt(padded || "0")).toString();
|
|
56
|
+
}
|
|
57
|
+
var utf8Encoder = new TextEncoder();
|
|
58
|
+
var utf8Decoder = new TextDecoder();
|
|
59
|
+
function encodePayment(value) {
|
|
60
|
+
const bytes = utf8Encoder.encode(JSON.stringify(value));
|
|
61
|
+
if (typeof Buffer !== "undefined") {
|
|
62
|
+
return Buffer.from(bytes).toString("base64");
|
|
63
|
+
}
|
|
64
|
+
let binary = "";
|
|
65
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
66
|
+
return btoa(binary);
|
|
67
|
+
}
|
|
68
|
+
function decodePayment(b64) {
|
|
69
|
+
let bytes;
|
|
70
|
+
if (typeof Buffer !== "undefined") {
|
|
71
|
+
bytes = new Uint8Array(Buffer.from(b64, "base64"));
|
|
72
|
+
} else {
|
|
73
|
+
const binary = atob(b64);
|
|
74
|
+
bytes = new Uint8Array(binary.length);
|
|
75
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
76
|
+
}
|
|
77
|
+
return JSON.parse(utf8Decoder.decode(bytes));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/server.ts
|
|
81
|
+
async function gate(request, opts) {
|
|
82
|
+
const requirements = buildRequirements(request, opts);
|
|
83
|
+
const header = request.headers.get("x-payment") ?? request.headers.get("X-PAYMENT");
|
|
84
|
+
if (!header) {
|
|
85
|
+
return { type: "paymentRequired", response: paymentRequiredResponse(requirements) };
|
|
86
|
+
}
|
|
87
|
+
let payment;
|
|
88
|
+
try {
|
|
89
|
+
payment = decodePayment(header);
|
|
90
|
+
} catch {
|
|
91
|
+
return {
|
|
92
|
+
type: "paymentRequired",
|
|
93
|
+
response: paymentRequiredResponse(requirements, "Invalid X-PAYMENT header")
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const matched = requirements.find(
|
|
97
|
+
(r) => r.network === payment.network && r.scheme === payment.scheme
|
|
98
|
+
);
|
|
99
|
+
if (!matched) {
|
|
100
|
+
return {
|
|
101
|
+
type: "paymentRequired",
|
|
102
|
+
response: paymentRequiredResponse(requirements, "Payment does not match any accepted requirement")
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (opts.facilitator === false) {
|
|
106
|
+
const payer2 = payment.payload.authorization.from;
|
|
107
|
+
return {
|
|
108
|
+
type: "ok",
|
|
109
|
+
payer: payer2,
|
|
110
|
+
settle: async () => ({
|
|
111
|
+
header: encodePayment({ success: true, network: matched.network }),
|
|
112
|
+
result: { success: true }
|
|
113
|
+
})
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const facilitator = new Facilitator(opts.facilitator ?? { url: DEFAULT_FACILITATOR_URL });
|
|
117
|
+
const verified = await facilitator.verify(payment, matched);
|
|
118
|
+
if (!verified.isValid) {
|
|
119
|
+
return {
|
|
120
|
+
type: "paymentRequired",
|
|
121
|
+
response: paymentRequiredResponse(
|
|
122
|
+
requirements,
|
|
123
|
+
verified.invalidReason ?? "Payment verification failed"
|
|
124
|
+
)
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const payer = verified.payer ?? payment.payload.authorization.from;
|
|
128
|
+
return {
|
|
129
|
+
type: "ok",
|
|
130
|
+
payer,
|
|
131
|
+
settle: async () => {
|
|
132
|
+
const result = await facilitator.settle(payment, matched);
|
|
133
|
+
return {
|
|
134
|
+
header: encodePayment({
|
|
135
|
+
success: result.success,
|
|
136
|
+
transaction: result.transaction,
|
|
137
|
+
network: result.network ?? matched.network,
|
|
138
|
+
errorReason: result.errorReason,
|
|
139
|
+
payer
|
|
140
|
+
}),
|
|
141
|
+
result
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function buildRequirements(request, opts) {
|
|
147
|
+
const networks = Array.isArray(opts.network) ? opts.network : [opts.network ?? "base"];
|
|
148
|
+
const atomic = usdToAtomic(opts.price);
|
|
149
|
+
const resource = opts.resource ?? request.url;
|
|
150
|
+
return networks.map((network) => ({
|
|
151
|
+
scheme: "exact",
|
|
152
|
+
network,
|
|
153
|
+
asset: opts.asset ?? usdcAddress(network),
|
|
154
|
+
maxAmountRequired: atomic,
|
|
155
|
+
payTo: opts.recipient,
|
|
156
|
+
description: opts.description,
|
|
157
|
+
resource,
|
|
158
|
+
maxTimeoutSeconds: opts.maxTimeoutSeconds ?? 600,
|
|
159
|
+
extra: { name: "USD Coin", version: "2" }
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
function paymentRequiredResponse(accepts, error) {
|
|
163
|
+
const body = { x402Version: 1, accepts, ...error ? { error } : {} };
|
|
164
|
+
return new Response(JSON.stringify(body), {
|
|
165
|
+
status: 402,
|
|
166
|
+
headers: { "content-type": "application/json" }
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export { DEFAULT_FACILITATOR_URL, Facilitator, gate };
|
|
171
|
+
//# sourceMappingURL=server.js.map
|
|
172
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/facilitator.ts","../src/utils.ts","../src/server.ts"],"names":["payer"],"mappings":"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","file":"server.js","sourcesContent":["import type {\n FacilitatorConfig,\n PaymentPayload,\n PaymentRequirement,\n SettleResult,\n VerifyResult,\n} from './types.js'\n\n/**\n * Thin wrapper around an x402 facilitator HTTP service.\n * The facilitator verifies signed payment authorizations and settles them on-chain.\n */\nexport class Facilitator {\n private readonly url: string\n private readonly authHeader?: string\n private readonly fetcher: typeof fetch\n\n constructor(config: FacilitatorConfig) {\n this.url = config.url.replace(/\\/$/, '')\n this.authHeader = config.authHeader\n this.fetcher = config.fetch ?? globalThis.fetch.bind(globalThis)\n }\n\n async verify(\n payment: PaymentPayload,\n requirement: PaymentRequirement,\n ): Promise<VerifyResult> {\n return this.post<VerifyResult>('/verify', { paymentPayload: payment, paymentRequirements: requirement })\n }\n\n async settle(\n payment: PaymentPayload,\n requirement: PaymentRequirement,\n ): Promise<SettleResult> {\n return this.post<SettleResult>('/settle', { paymentPayload: payment, paymentRequirements: requirement })\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const res = await this.fetcher(this.url + path, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n ...(this.authHeader ? { authorization: this.authHeader } : {}),\n },\n body: JSON.stringify(body),\n })\n if (!res.ok) {\n const text = await res.text().catch(() => '')\n throw new Error(`Facilitator ${path} failed: ${res.status} ${text}`)\n }\n return (await res.json()) as T\n }\n}\n\n/** Default hosted facilitator. Override for self-hosted deployments. */\nexport const DEFAULT_FACILITATOR_URL = 'https://facilitator.yeetful.com'\n","import type { X402Network } from './types.js'\n\nconst USDC_BY_NETWORK: Record<X402Network, `0x${string}`> = {\n base: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n 'base-sepolia': '0x036CbD53842c5426634e7929541eC2318f3dCF7e',\n ethereum: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n optimism: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85',\n arbitrum: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n polygon: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',\n}\n\nexport const USDC_DECIMALS = 6\n\n/** Returns the canonical USDC contract address for a supported network. */\nexport function usdcAddress(network: X402Network): `0x${string}` {\n return USDC_BY_NETWORK[network]\n}\n\n/**\n * Convert a human-friendly USD amount (e.g. \"0.01\") into atomic USDC units.\n * Fixed-point to avoid float drift — safer than Number math for payments.\n */\nexport function usdToAtomic(amount: string | number, decimals = USDC_DECIMALS): string {\n const str = typeof amount === 'number' ? amount.toString() : amount\n if (!/^\\d+(\\.\\d+)?$/.test(str)) {\n throw new Error(`Invalid amount: ${str}`)\n }\n const [whole, frac = ''] = str.split('.')\n const padded = frac.slice(0, decimals).padEnd(decimals, '0')\n return (BigInt(whole ?? '0') * 10n ** BigInt(decimals) + BigInt(padded || '0')).toString()\n}\n\nconst utf8Encoder = new TextEncoder()\nconst utf8Decoder = new TextDecoder()\n\n/** Base64 encode a JSON value — works in Node 18+ and browsers. */\nexport function encodePayment(value: unknown): string {\n const bytes = utf8Encoder.encode(JSON.stringify(value))\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(bytes).toString('base64')\n }\n let binary = ''\n for (const byte of bytes) binary += String.fromCharCode(byte)\n return btoa(binary)\n}\n\n/** Base64 decode a payment header value back into JSON. */\nexport function decodePayment<T = unknown>(b64: string): T {\n let bytes: Uint8Array\n if (typeof Buffer !== 'undefined') {\n bytes = new Uint8Array(Buffer.from(b64, 'base64'))\n } else {\n const binary = atob(b64)\n bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n }\n return JSON.parse(utf8Decoder.decode(bytes)) as T\n}\n\n/** Generate a random 32-byte nonce as a 0x-prefixed hex string. */\nexport function randomNonce(): `0x${string}` {\n const bytes = new Uint8Array(32)\n globalThis.crypto.getRandomValues(bytes)\n return ('0x' + Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')) as `0x${string}`\n}\n","import type { Address } from 'viem'\nimport { Facilitator, DEFAULT_FACILITATOR_URL } from './facilitator.js'\nimport type {\n FacilitatorConfig,\n PaymentPayload,\n PaymentRequirement,\n PaymentRequiredResponse,\n SettleResult,\n X402Network,\n} from './types.js'\nimport { decodePayment, encodePayment, usdcAddress, usdToAtomic } from './utils.js'\n\nexport interface RouteGateOptions {\n /** Price in USD, e.g. \"0.01\". Converted to USDC atomic units. */\n price: string | number\n /** Recipient address that receives the funds. */\n recipient: Address\n /** Network(s) to accept. Defaults to `['base']`. Pass an array for multi-chain. */\n network?: X402Network | X402Network[]\n /** Override the payment asset (defaults to USDC for the given network). */\n asset?: Address\n /** Human-readable description of the resource. */\n description?: string\n /** Seconds the signed authorization is valid. Default: 600. */\n maxTimeoutSeconds?: number\n /** Facilitator config, or `false` to skip verification/settlement (trust-mode). */\n facilitator?: FacilitatorConfig | false\n /**\n * Optional resource identifier (defaults to the request URL). Useful for\n * logging / receipts.\n */\n resource?: string\n}\n\n/**\n * Runtime-agnostic gate: given a Fetch-API `Request`, either returns a 402\n * `Response` or a `{ settle }` handle. After your handler runs, call `settle()`\n * to finalize the payment and get a `X-PAYMENT-RESPONSE` header to attach.\n *\n * This is the building block used by the framework adapters (`./next`, `./express`).\n */\nexport async function gate(\n request: Request,\n opts: RouteGateOptions,\n): Promise<\n | { type: 'paymentRequired'; response: Response }\n | { type: 'ok'; payer: Address; settle: () => Promise<{ header: string; result: SettleResult }> }\n> {\n const requirements = buildRequirements(request, opts)\n\n const header = request.headers.get('x-payment') ?? request.headers.get('X-PAYMENT')\n if (!header) {\n return { type: 'paymentRequired', response: paymentRequiredResponse(requirements) }\n }\n\n let payment: PaymentPayload\n try {\n payment = decodePayment<PaymentPayload>(header)\n } catch {\n return {\n type: 'paymentRequired',\n response: paymentRequiredResponse(requirements, 'Invalid X-PAYMENT header'),\n }\n }\n\n const matched = requirements.find(\n (r) => r.network === payment.network && r.scheme === payment.scheme,\n )\n if (!matched) {\n return {\n type: 'paymentRequired',\n response: paymentRequiredResponse(requirements, 'Payment does not match any accepted requirement'),\n }\n }\n\n if (opts.facilitator === false) {\n const payer = payment.payload.authorization.from as Address\n return {\n type: 'ok',\n payer,\n settle: async () => ({\n header: encodePayment({ success: true, network: matched.network }),\n result: { success: true },\n }),\n }\n }\n\n const facilitator = new Facilitator(opts.facilitator ?? { url: DEFAULT_FACILITATOR_URL })\n const verified = await facilitator.verify(payment, matched)\n if (!verified.isValid) {\n return {\n type: 'paymentRequired',\n response: paymentRequiredResponse(\n requirements,\n verified.invalidReason ?? 'Payment verification failed',\n ),\n }\n }\n\n const payer = (verified.payer ?? payment.payload.authorization.from) as Address\n\n return {\n type: 'ok',\n payer,\n settle: async () => {\n const result = await facilitator.settle(payment, matched)\n return {\n header: encodePayment({\n success: result.success,\n transaction: result.transaction,\n network: result.network ?? matched.network,\n errorReason: result.errorReason,\n payer,\n }),\n result,\n }\n },\n }\n}\n\nfunction buildRequirements(request: Request, opts: RouteGateOptions): PaymentRequirement[] {\n const networks = Array.isArray(opts.network) ? opts.network : [opts.network ?? 'base']\n const atomic = usdToAtomic(opts.price)\n const resource = opts.resource ?? request.url\n\n return networks.map((network) => ({\n scheme: 'exact',\n network,\n asset: opts.asset ?? usdcAddress(network),\n maxAmountRequired: atomic,\n payTo: opts.recipient,\n description: opts.description,\n resource,\n maxTimeoutSeconds: opts.maxTimeoutSeconds ?? 600,\n extra: { name: 'USD Coin', version: '2' },\n }))\n}\n\nfunction paymentRequiredResponse(\n accepts: PaymentRequirement[],\n error?: string,\n): Response {\n const body: PaymentRequiredResponse = { x402Version: 1, accepts, ...(error ? { error } : {}) }\n return new Response(JSON.stringify(body), {\n status: 402,\n headers: { 'content-type': 'application/json' },\n })\n}\n\nexport { Facilitator, DEFAULT_FACILITATOR_URL } from './facilitator.js'\n"]}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Address, Hex } from 'viem';
|
|
2
|
+
|
|
3
|
+
type X402Network = 'base' | 'base-sepolia' | 'ethereum' | 'optimism' | 'arbitrum' | 'polygon';
|
|
4
|
+
type X402Scheme = 'exact';
|
|
5
|
+
/**
|
|
6
|
+
* A single acceptable way to satisfy a payment requirement.
|
|
7
|
+
* Servers may advertise multiple (e.g. USDC on Base and USDC on Optimism).
|
|
8
|
+
*/
|
|
9
|
+
interface PaymentRequirement {
|
|
10
|
+
scheme: X402Scheme;
|
|
11
|
+
network: X402Network;
|
|
12
|
+
/** Smart contract address of the payment asset (e.g. USDC). */
|
|
13
|
+
asset: Address;
|
|
14
|
+
/** Atomic units owed (e.g. 10000 = 0.01 USDC at 6 decimals). */
|
|
15
|
+
maxAmountRequired: string;
|
|
16
|
+
/** Recipient address that receives the payment. */
|
|
17
|
+
payTo: Address;
|
|
18
|
+
/** Human-readable description of what the buyer is paying for. */
|
|
19
|
+
description?: string;
|
|
20
|
+
/** Resource URL being paid for. */
|
|
21
|
+
resource?: string;
|
|
22
|
+
/** Seconds the signed authorization remains valid. */
|
|
23
|
+
maxTimeoutSeconds?: number;
|
|
24
|
+
/** Optional scheme-specific extra data. */
|
|
25
|
+
extra?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The JSON body returned with an HTTP 402 response.
|
|
29
|
+
* Matches the x402 spec's discovery document.
|
|
30
|
+
*/
|
|
31
|
+
interface PaymentRequiredResponse {
|
|
32
|
+
x402Version: 1;
|
|
33
|
+
accepts: PaymentRequirement[];
|
|
34
|
+
error?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A signed payment payload the client sends back in the `X-PAYMENT` header.
|
|
38
|
+
* The body is a base64-encoded JSON of this shape.
|
|
39
|
+
*/
|
|
40
|
+
interface PaymentPayload {
|
|
41
|
+
x402Version: 1;
|
|
42
|
+
scheme: X402Scheme;
|
|
43
|
+
network: X402Network;
|
|
44
|
+
payload: ExactEvmPayload;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* EIP-3009 transferWithAuthorization payload for the `exact` scheme.
|
|
48
|
+
*/
|
|
49
|
+
interface ExactEvmPayload {
|
|
50
|
+
signature: Hex;
|
|
51
|
+
authorization: {
|
|
52
|
+
from: Address;
|
|
53
|
+
to: Address;
|
|
54
|
+
value: string;
|
|
55
|
+
validAfter: string;
|
|
56
|
+
validBefore: string;
|
|
57
|
+
nonce: Hex;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** Result of a facilitator verify call. */
|
|
61
|
+
interface VerifyResult {
|
|
62
|
+
isValid: boolean;
|
|
63
|
+
invalidReason?: string;
|
|
64
|
+
payer?: Address;
|
|
65
|
+
}
|
|
66
|
+
/** Result of a facilitator settle call. */
|
|
67
|
+
interface SettleResult {
|
|
68
|
+
success: boolean;
|
|
69
|
+
transaction?: Hex;
|
|
70
|
+
network?: X402Network;
|
|
71
|
+
errorReason?: string;
|
|
72
|
+
payer?: Address;
|
|
73
|
+
}
|
|
74
|
+
/** Config for talking to an x402 facilitator service. */
|
|
75
|
+
interface FacilitatorConfig {
|
|
76
|
+
url: string;
|
|
77
|
+
/** Optional auth header value, e.g. `Bearer <token>`. */
|
|
78
|
+
authHeader?: string;
|
|
79
|
+
/** Pluggable fetcher (defaults to global fetch). */
|
|
80
|
+
fetch?: typeof fetch;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type { ExactEvmPayload as E, FacilitatorConfig as F, PaymentPayload as P, SettleResult as S, VerifyResult as V, X402Network as X, PaymentRequiredResponse as a, PaymentRequirement as b, X402Scheme as c };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Address, Hex } from 'viem';
|
|
2
|
+
|
|
3
|
+
type X402Network = 'base' | 'base-sepolia' | 'ethereum' | 'optimism' | 'arbitrum' | 'polygon';
|
|
4
|
+
type X402Scheme = 'exact';
|
|
5
|
+
/**
|
|
6
|
+
* A single acceptable way to satisfy a payment requirement.
|
|
7
|
+
* Servers may advertise multiple (e.g. USDC on Base and USDC on Optimism).
|
|
8
|
+
*/
|
|
9
|
+
interface PaymentRequirement {
|
|
10
|
+
scheme: X402Scheme;
|
|
11
|
+
network: X402Network;
|
|
12
|
+
/** Smart contract address of the payment asset (e.g. USDC). */
|
|
13
|
+
asset: Address;
|
|
14
|
+
/** Atomic units owed (e.g. 10000 = 0.01 USDC at 6 decimals). */
|
|
15
|
+
maxAmountRequired: string;
|
|
16
|
+
/** Recipient address that receives the payment. */
|
|
17
|
+
payTo: Address;
|
|
18
|
+
/** Human-readable description of what the buyer is paying for. */
|
|
19
|
+
description?: string;
|
|
20
|
+
/** Resource URL being paid for. */
|
|
21
|
+
resource?: string;
|
|
22
|
+
/** Seconds the signed authorization remains valid. */
|
|
23
|
+
maxTimeoutSeconds?: number;
|
|
24
|
+
/** Optional scheme-specific extra data. */
|
|
25
|
+
extra?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The JSON body returned with an HTTP 402 response.
|
|
29
|
+
* Matches the x402 spec's discovery document.
|
|
30
|
+
*/
|
|
31
|
+
interface PaymentRequiredResponse {
|
|
32
|
+
x402Version: 1;
|
|
33
|
+
accepts: PaymentRequirement[];
|
|
34
|
+
error?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A signed payment payload the client sends back in the `X-PAYMENT` header.
|
|
38
|
+
* The body is a base64-encoded JSON of this shape.
|
|
39
|
+
*/
|
|
40
|
+
interface PaymentPayload {
|
|
41
|
+
x402Version: 1;
|
|
42
|
+
scheme: X402Scheme;
|
|
43
|
+
network: X402Network;
|
|
44
|
+
payload: ExactEvmPayload;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* EIP-3009 transferWithAuthorization payload for the `exact` scheme.
|
|
48
|
+
*/
|
|
49
|
+
interface ExactEvmPayload {
|
|
50
|
+
signature: Hex;
|
|
51
|
+
authorization: {
|
|
52
|
+
from: Address;
|
|
53
|
+
to: Address;
|
|
54
|
+
value: string;
|
|
55
|
+
validAfter: string;
|
|
56
|
+
validBefore: string;
|
|
57
|
+
nonce: Hex;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** Result of a facilitator verify call. */
|
|
61
|
+
interface VerifyResult {
|
|
62
|
+
isValid: boolean;
|
|
63
|
+
invalidReason?: string;
|
|
64
|
+
payer?: Address;
|
|
65
|
+
}
|
|
66
|
+
/** Result of a facilitator settle call. */
|
|
67
|
+
interface SettleResult {
|
|
68
|
+
success: boolean;
|
|
69
|
+
transaction?: Hex;
|
|
70
|
+
network?: X402Network;
|
|
71
|
+
errorReason?: string;
|
|
72
|
+
payer?: Address;
|
|
73
|
+
}
|
|
74
|
+
/** Config for talking to an x402 facilitator service. */
|
|
75
|
+
interface FacilitatorConfig {
|
|
76
|
+
url: string;
|
|
77
|
+
/** Optional auth header value, e.g. `Bearer <token>`. */
|
|
78
|
+
authHeader?: string;
|
|
79
|
+
/** Pluggable fetcher (defaults to global fetch). */
|
|
80
|
+
fetch?: typeof fetch;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type { ExactEvmPayload as E, FacilitatorConfig as F, PaymentPayload as P, SettleResult as S, VerifyResult as V, X402Network as X, PaymentRequiredResponse as a, PaymentRequirement as b, X402Scheme as c };
|
package/package.json
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yeetful",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Drop-in x402 payments for APIs and clients. Gate routes behind stablecoin micropayments in a few lines.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"x402",
|
|
7
|
+
"payments",
|
|
8
|
+
"stablecoin",
|
|
9
|
+
"usdc",
|
|
10
|
+
"micropayments",
|
|
11
|
+
"http-402",
|
|
12
|
+
"next",
|
|
13
|
+
"express",
|
|
14
|
+
"viem",
|
|
15
|
+
"yeetful"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://yeetful.com",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/yeetful/yeetful.git",
|
|
21
|
+
"directory": "sdk"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "Yeetful",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"main": "./dist/index.cjs",
|
|
27
|
+
"module": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js",
|
|
33
|
+
"require": "./dist/index.cjs"
|
|
34
|
+
},
|
|
35
|
+
"./client": {
|
|
36
|
+
"types": "./dist/client.d.ts",
|
|
37
|
+
"import": "./dist/client.js",
|
|
38
|
+
"require": "./dist/client.cjs"
|
|
39
|
+
},
|
|
40
|
+
"./server": {
|
|
41
|
+
"types": "./dist/server.d.ts",
|
|
42
|
+
"import": "./dist/server.js",
|
|
43
|
+
"require": "./dist/server.cjs"
|
|
44
|
+
},
|
|
45
|
+
"./next": {
|
|
46
|
+
"types": "./dist/next.d.ts",
|
|
47
|
+
"import": "./dist/next.js",
|
|
48
|
+
"require": "./dist/next.cjs"
|
|
49
|
+
},
|
|
50
|
+
"./express": {
|
|
51
|
+
"types": "./dist/express.d.ts",
|
|
52
|
+
"import": "./dist/express.js",
|
|
53
|
+
"require": "./dist/express.cjs"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"files": [
|
|
57
|
+
"dist",
|
|
58
|
+
"README.md",
|
|
59
|
+
"LICENSE"
|
|
60
|
+
],
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "tsup",
|
|
63
|
+
"dev": "tsup --watch",
|
|
64
|
+
"typecheck": "tsc --noEmit",
|
|
65
|
+
"test": "vitest run",
|
|
66
|
+
"prepublishOnly": "npm run build"
|
|
67
|
+
},
|
|
68
|
+
"peerDependencies": {
|
|
69
|
+
"viem": "^2.21.0"
|
|
70
|
+
},
|
|
71
|
+
"peerDependenciesMeta": {
|
|
72
|
+
"viem": {
|
|
73
|
+
"optional": false
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"@types/express": "^4.17.21",
|
|
78
|
+
"@types/node": "^22.0.0",
|
|
79
|
+
"express": "^4.19.2",
|
|
80
|
+
"next": "^15.0.0",
|
|
81
|
+
"tsup": "^8.3.0",
|
|
82
|
+
"typescript": "^5.6.0",
|
|
83
|
+
"viem": "^2.21.0",
|
|
84
|
+
"vitest": "^2.1.0"
|
|
85
|
+
},
|
|
86
|
+
"engines": {
|
|
87
|
+
"node": ">=18"
|
|
88
|
+
},
|
|
89
|
+
"publishConfig": {
|
|
90
|
+
"access": "public"
|
|
91
|
+
}
|
|
92
|
+
}
|